Introduction
Every mobile developer knows the importance of delivering a buttery-smooth, 60fps (and increasingly 120fps) app. Flutter prides itself by being performant by default, but sometimes extra steps are needed to optimize particularly heavy UIs. We’ll take a look at once such example and solve it using the provider
package.
The code for this article can be found in the repository below:
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int counter = 0;
int items = 10;
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Text('$counter', style: TextStyle(fontSize: 20)),
SizedBox(height: 16),
Wrap(
children: Iterable.generate(items)
.map((e) => ExpensiveItem(
position: e + 1,
counter: counter,
))
.toList(),
)
],
),
),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
counter += 1;
});
},
),
);
}
}
Each item initializes with a loading spinner to simulate being expensive to create. If the number contained in the list item is a factor of the counter then its background will be randomly assigned a color, otherwise it will be white.

Currently when the counter is increased the call to setState
causes every list item to be rebuilt. This is unnecessary for items that will have the same state for consecutive values of the counter. In the next section we’ll see how the provider
package can help us optimize the rebuilding of these widgets.
Optimizing With Provider
Install The Provider Package
Add the provider package to your pubspec.yaml
file
Optimize The List
The first thing we’ll do is extract the list to its own widget with a const
constructor so it won’t be rebuilt every time we call setState
. This is because in Dart a const variable becomes a compile-time constant.
Now we’ll wrap this new ExpensiveItemList
widget with a Provider
widget and pass it the counter
variable. Doing so makes counter
available to widgets further down the tree.
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int counter = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Text('$counter', style: TextStyle(fontSize: 20)),
SizedBox(height: 16),
Provider<int>.value(
value: counter,
builder: (_, __) => const ExpensiveItemList())
],
),
),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
counter += 1;
});
},
),
);
}
}
class ExpensiveItemList extends StatelessWidget {
final items = 10;
const ExpensiveItemList();
@override
Widget build(BuildContext context) {
return Wrap(
children: Iterable.generate(items)
.map((e) => ExpensiveItem(
position: e + 1,
))
.toList(),
);
}
}
Optimize The List Item
The ExpensiveItem
Widget no longer receives the counter
variable in its constructor but it still needs access to it in order to know when to rebuild. We’ll use the select
extension on BuildContext
which provider
gives us to only listen to when the item switches from not being a factor to being a factor, and vice versa.
class ExpensiveItem extends StatelessWidget {
final int position;
const ExpensiveItem({Key key, this.position}) : super(key: key);
@override
Widget build(BuildContext context) {
final size = (MediaQuery.of(context).size.width / 5) - 32 / 5;
final isFactor = context
.select<int, bool>((value) => value > 0 && position % value == 0);
...
}
}
Results
After implementing the changes, re-run the app and you’ll notice that only the items that need to update their UI are being rebuilt!

Conclusion
We successfully used the provider package to optimize our Flutter app. Congratulations! 🥳
Note that, as is usually the case, this isn’t the only way we could have done this. There are other packages and state management solutions we could have used instead. What matters is that we chose a solution that got the job done in a reasonable amount of time.
The key takeaway here is this:
When you have a Widget that is particularly expensive to create, isolate!
Wrapping Up
Thanks for reading! If you enjoyed this article, please consider sharing it with others and becoming a sponsor on GitHub. ❤️