Save the expensive rebuild of apps using ValueNotifier in flutter
What is setState()
?
In Flutter, setState()
is like a magical incantation that triggers a UI update. When you call setState()
, you’re telling Flutter: “Hey, something has changed! Rebuild the affected part of the UI.”
Specifically, setState()
does two things:
- It marks the widget as needing to be rebuilt.
- It schedules a rebuild of the widget tree during the next frame.
How Does setState()
Work?
Imagine you have a stateful widget (a widget that can change dynamically). Inside that widget, you define some mutable variables (state). When you want to update the UI based on changes in those variables, you call setState()
.
Flutter then does its magic:
- It rebuilds the widget (and its subtree) associated with that state.
- The new state values are reflected in the updated UI.
When to Use setState()
?
- Carefully! While
setState()
is powerful, it’s also a double-edged sword. Here’s how to wield it wisely: - Localize the
setState()
Call: Only usesetState()
where necessary. If a small part of your UI needs updating, callsetState()
only for that subtree. Don’t trigger unnecessary rebuilds elsewhere. - Avoid High-Up Calls: If a change affects only a specific widget or a small portion of your widget tree, don’t call
setState()
high up in the widget hierarchy. Keep it localized. - Think Performance: Frequent
setState()
calls can impact performance. Flutter is efficient, but excessive rebuilds can slow things down. Optimize where possible. - Use State Management Libraries: For complex apps, explore state management solutions like Provider, Riverpod, or BLoC. They help manage state more efficiently.
Where to Avoid setState()
?
- Avoid It When Not Needed: If a variable doesn’t affect the UI, skip
setState()
. No need to rebuild if it won’t be visible. - Network Calls and Heavy Computations: Don’t put network requests or heavy computations directly inside
setState()
. Fetch data elsewhere and then update the state. - Animations: For smooth animations, consider using AnimationController or other animation libraries instead of frequent
setState()
calls.
Let’s explore how ValueNotifier
and the ValueListenableBuilder
can be used as an alternative to setState()
for efficient state management and UI updates.
ValueNotifier
Basics:
What is ValueNotifier
?
- A
ValueNotifier
is a lightweight class provided by Flutter that extendsChangeNotifier
. It’s designed to hold a single value and notify listeners when that value changes. - Unlike
setState()
, which triggers a full widget rebuild,ValueNotifier
allows more granular updates. It’s like having a private story on Instagram—only your closest friends (the listeners) know about your updates.
When to Use ValueNotifier
:
- Use
ValueNotifier
when you have a simple piece of mutable state that needs to be tracked and updated reactively. - It’s particularly useful for scenarios where you want to avoid rebuilding the entire widget tree when only a specific part of it needs updating.
ValueListenableBuilder
:
This widget is the perfect companion for ValueNotifier
. It listens to changes from a ValueNotifier
and rebuilds its child widget accordingly. Think of it as a specialized widget that knows how to dance to the rhythm of your ValueNotifier
.
Here’s how it works:
- Wrap your widget subtree with a
ValueListenableBuilder
. - Pass in the
ValueNotifier
you want to listen to. - Inside the builder callback, define what should be rebuilt based on the updated value.
- Voilà! Efficient updates without unnecessary rebuilds.
Example: Using ValueNotifier
and ValueListenableBuilder
:
Let’s say we have a simple counter app. Instead of using setState()
, we’ll use ValueNotifier
:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
final ValueNotifier<int> _counter = ValueNotifier<int>(0);
void _incrementCounter() {
_counter.value++; // Update the value
}
@override
Widget build(BuildContext context) {
print('HomePage built');
return Scaffold(
appBar: AppBar(title: const Text('ValueNotifier Counter')),
body: Center(
child: ValueListenableBuilder<int>(
valueListenable: _counter,
builder: (context, value, child) {
return Text('Count: $value');
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
child: const Icon(Icons.add),
),
);
}
}
In this example:
- We create a
ValueNotifier<int>
called_counter
. - The UI updates only the
Text
widget inside theValueListenableBuilder
when the counter value changes. - No unnecessary rebuilds of the entire widget tree!
Remember the Key Points:
- Use
ValueNotifier
for simple state management needs. - Pair it with
ValueListenableBuilder
to update specific parts of your UI efficiently.