In this blog, we will unpack a critical concept within the Flutter framework – the Flutter ChangeNotifier. As you may know, state management is at the heart of any Flutter application, dictating how components of an app interact and change over time.
Understanding how to manipulate and utilize the ChangeNotifier Flutter for state management is a powerful tool that every Flutter developer should wield comfortably. Come along as we take a deep, easy exploration into the workings of the Flutter ChangeNotifier and ChangeNotifierProvider.
When it comes to state management in the Flutter SDK, one of the classes that can't be ignored is the ChangeNotifier class, a simple class that exposes a crucial part of the change notification API. The Flutter ChangeNotifier is a part of the Flutter Provider package, which allows for efficient management of the state of an application.
1class MyApp extends StatelessWidget { 2 @override 3 Widget build(BuildContext context) { 4 return ChangeNotifierProvider( 5 create: (context) => SomeClass(), 6 child: MaterialApp( 7 home: Scaffold( 8 appBar: AppBar(), 9 body: SomeWidget(), 10 ), 11 ), 12 ); 13 } 14}
The above code depicts a sample Flutter app inside which a ChangeNotifierProvider is created.
A ChangeNotifier is a way to manage and abstract your business logic from the user interface (UI). It's similar to scattering your app's state into different entities. Each entity manages its private state and exposes methods for manipulating this state while keeping the state's consistency.
In essence, ChangeNotifier is a class from the Flutter framework that provides a change notification API, using Void add and removing listeners to dispatch notifications, triggering a rebuild of large portions of your widget tree.
In the Dart language, the class ChangeNotifier has a simple set of methods and APIs as part of the Flutter package. This class encapsulates the process of adding listeners and removing them, as well as dispatching notifications to them. It's important to note that the state in our ChangeNotifier class is what we are concerned with; thus, we only notify listeners when there is a change.
1class MyNotifier extends ChangeNotifier { 2 int _value = 0; 3 int get value => _value; 4 5 void increment() { 6 _value++; 7 notifyListeners(); 8 } 9}
In this example, the _value represents the current state of the object, and whenever this value is incremented using the increment method, the notifyListeners() function is called, notifying all the listeners of the new state, causing them to rebuild.
The ChangeNotifier class in Flutter is a simple yet powerful tool for managing the state of an application. For example, let's examine a class that extends ChangeNotifier:
1class Counter extends ChangeNotifier { 2 int _count = 0; 3 int get count => _count; 4 5 void increment() { 6 _count++; 7 notifyListeners(); 8 } 9}
In this simple class, a count variable is used to manage a private state. This state can only be manipulated by the increment method, which increments the count and then calls notifyListeners(). This method allows for the dispatching of notifications whenever the state changes.
For a widget to listen to a class extending ChangeNotifier, it must be wrapped in a Consumer widget or use the context to listen to the ChangeNotifier directly.
1class CountDisplay extends StatelessWidget { 2 @override 3 Widget build(BuildContext context) { 4 var count = context.watch<Counter>().count; 5 return Text('Count: $count'); 6 } 7}
In CountDisplay, the widget listens for any changes in Counter and rebuilds automatically when Counter changes due to the watch method.
The Consumer widget can also be used as follows to handle change notifications:
1Consumer<Counter>( 2 builder: (context, counter, child) => Text('Count: ${counter.count}'), 3)
The ChangeNotifierProvider is one of the classes from the provider package that links the lifecycle of a ChangeNotifier to widgets on the widget tree. It is responsible for creating and supplying an instance of a ChangeNotifier directly to the UI.
1class MyApp extends StatelessWidget { 2 @override 3 Widget build(BuildContext context) { 4 return ChangeNotifierProvider( 5 create: (context) => TaskData(), 6 child: MaterialApp( 7 home: TasksScreen(), 8 ), 9 ); 10 } 11}
In MyApp, it creates an instance of the TaskData class and then offers it to all consumer widgets that are its children in the widget tree. It handles the lifecycle of the ChangeNotifier for us. The 'create' method is in charge of creating a new instance of TaskData.
1class TaskData extends ChangeNotifier { 2 List<Task> _tasks = [ 3 Task(name: 'Buy milk'), 4 ]; 5 6 UnmodifiableListView<Task> get tasks { 7 return UnmodifiableListView(_tasks); 8 } 9 10 void addTask(String newTaskTitle) { 11 final task = Task(name: newTaskTitle); 12 _tasks.add(task); 13 notifyListeners(); 14 } 15}
In TaskData, which extends ChangeNotifier, we maintain a private list of tasks and expose it through a getter. We also provide an addTask function to manipulate the state.
Having understood what Flutter ChangeNotifier and ChangeNotifierProvider are, let's see how to implement them in a Flutter application. We'll create a simple counter application.
1class Counter with ChangeNotifier { 2 int _value = 0; 3 4 int get value => _value; 5 6 void increment() { 7 _value++; 8 notifyListeners(); 9 } 10}
In this Counter class, the value is exposed through a getter for read-only access. To change the value, we have increment(). notifyListerners() is crucial here as it informs listening widgets of the changes.
1void main() => runApp( 2 ChangeNotifierProvider( 3 create: (context) => Counter(), 4 child: MyApp(), 5 ), 6);
This code creates an instance of Counter and provides it to our widget tree.
1class CounterText extends StatelessWidget { 2 @override 3 Widget build(BuildContext context) { 4 return Text( 5 'Value: ${context.watch<Counter>().value}', 6 style: Theme.of(context).textTheme.headline4, 7 ); 8 } 9}
In the CounterText widget, we subscribe to changes using context.watch(). This will cause our widget to rebuild whenever notifyListeners() is called in our ChangeNotifier.
Consider an app whose primary function is to display a list of notes. The notes' information is dynamic and can be updated at any time, hence requiring state management. Here's how we can implement this using Flutter ChangeNotifier:
First, we define a simple Note class and our NoteProvider, which extends ChangeNotifier.
1class Note { 2 String content; 3 4 Note(this.content); 5} 6 7class NoteProvider extends ChangeNotifier { 8 List<Note> _notes = []; 9 10 void addNote(String content) { 11 _notes.add(Note(content)); 12 notifyListeners(); 13 } 14}
Our ChangeNotifierProvider would then be:
1void main() { 2 runApp( 3 ChangeNotifierProvider( 4 create: (context) => NoteProvider(), 5 child: MyApp(), 6 ), 7 ); 8}
We can then access our notes for display through a consumer in our UI:
1class NoteList extends StatelessWidget { 2 @override 3 Widget build(BuildContext context) { 4 final notes = context.watch<NoteProvider>().notes; 5 6 return ListView.builder( 7 itemCount: notes.length, 8 itemBuilder: (context, index) => Text(notes[index].content), 9 ); 10 } 11}
Here are some common mistakes Flutter developers might make when working with Flutter ChangeNotifier and ChangeNotifierProvider, along with solutions:
context.read
in build function. Solution: It's best practice to avoid using context.read<ChangeNotifier>()
inside the build function, as it can lead to unnecessary rebuilds of your widget. Instead, use context.watch<ChangeNotifier>()
or Provider.of<ChangeNotifier>(context)
.notifyListeners()
after updating state. Solution: Always ensure notifyListeners()
is called after any function that modifies the data within the ChangeNotifier. This triggers the change notification and causes the listeners to rebuild, thus reflecting changes in the UI.While ChangeNotifier in Flutter provides a straightforward way to manage state, alternative approaches are available depending upon the complexity and requirements of your applications.
Choosing the appropriate state management solution depends largely on the specific requirements of your project and the complexity of the state to manage.
In this blog post, we have delved into the ins and outs of Flutter ChangeNotifier and ChangeNotifierProvider, from their basic functioning and implementation details to alternative approaches.
Understanding how to utilize Flutter ChangeNotifier for state management effectively is essential for crafting applications that are efficient, scalable, and easy to debug. Although we also highlighted alternative state management options, the decision of which approach to adopt hinges on the complexity and requirements of your application.
I hope this blog post has provided valuable insights for you as you continue your journey in Flutter development. Keep exploring, implementing, and creating!
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.