Hello, fellow developers! Welcome to another blog post. Today, I'd like to share my experience with state management in Flutter. I've seen many developers debate the best approach, and I'd like to offer a simple and effective solution.
The abundance of state management libraries in Flutter is a double-edged sword. On the one hand, it's great that the framework doesn't lock us into a single, official solution. On the other hand, this variety can be overwhelming for junior developers just starting their Flutter journey. A common question they face is, "Which library should I learn?"
If you're new to Flutter, I believe ValueNotifier is an excellent starting point. It requires no external dependencies, and using it will help you better understand how Flutter works under the hood. Plus, if you're coming from an Android background, you'll find that you can apply your existing ViewModel knowledge, making the transition much smoother.
ValueNotifier provides a straightforward way to manage state without relying on setState or more complex libraries that can sometimes clutter your code. It gives you the power to update only the specific widgets that need to change, leaving others untouched.
ValueNotifier works in conjunction with ValueListenableBuilder to update widgets. While this might seem like extra work at first, I'll show you how to enhance this process to make it even more efficient.
First, let's define a ViewModel class. This can be a regular Dart class, or you can extend it from ChangeNotifier if you prefer.
dart// lib/my_view_model.dart import 'package:flutter/foundation.dart'; class MyViewModel { // Name of the person final name = ValueNotifier(""); // Update name to a new value void changeName(String newName) { name.value = newName; } }
Now you have a MyViewModel with a single name field, which is a ValueNotifier of type String. You can instantiate this ViewModel using a service locator like GetIt or any other dependency injection. For this example, we'll keep it simple and create a global instance.
dart// lib/main.dart final viewModel = MyViewModel();
With the ViewModel instantiated, you can now use it within your widgets as shown below.
dart// lib/main.dart import 'package:flutter/material.dart'; class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Column( children: [ ValueListenableBuilder( valueListenable: viewModel.name, builder: (context, value, child) { return Text(value); }, ), ElevatedButton( onPressed: () { viewModel.changeName("Sophoun"); }, child: Text("Change Name"), ), ], ), ); } }
The code above demonstrates a basic use of ValueNotifier. But what if you need to listen to multiple values at once? But before that you need to clean up our code first.
To make your code cleaner, you can create an extension for ValueNotifier.
dart// lib/app_extensions.dart import 'package:flutter/material.dart'; extension ValueNotifierBuilderon ValueNotifier { Widget builder({required Widget Function(T value) build}) { return ValueListenableBuilder ( valueListenable: this, builder: (context, value, child) { return build(value); }, ); } }
Now, you can refactor your widget code to be more concise.
dart// lib/main.dart class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Column( children: [ viewModel.name.builder( build: (value) { return Text(value); }, ), ElevatedButton( onPressed: () { viewModel.changeName("Sophoun"); }, child: Text("Change Name"), ), ], ), ); } }
When you click the button, the name will update just as before, but the code is now much cleaner.
Now that you've seen how to use a single ValueNotifier, let's explore how to handle multiple ValueNotifiers. First, let's update our ViewModel to include more than one value.
dart// lib/my_view_model.dart import 'package:flutter/foundation.dart'; class MyViewModel { // Name of the person final name = ValueNotifier(""); final age = ValueNotifier (0); void changeName(String newName) { name.value = newName; } void changeAge(int newAge) { age.value = newAge; } }
To listen to multiple ValueNotifiers, you can create a helper function that combines them. This approach, similar to a Tuple, has worked well in my projects.
dart// lib/app_extensions.dart import 'package:flutter/material.dart'; Widget combine2Notifier( ValueNotifier notifier1, ValueNotifier notifier2, Widget Function(T1 value1, T2 value2) builder, ) { return ValueListenableBuilder ( valueListenable: notifier1, builder: (context, value1, child) { return ValueListenableBuilder ( valueListenable: notifier2, builder: (context, value2, child) { return builder(value1, value2); }, ); }, ); }
Now, you can use the combine2Notifier function to listen to multiple values as shown below.
dart// lib/main.dart class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Column( children: [ combine2Notifier( notifier1: viewModel.name, notifier2: viewModel.age, builder: (name, age) { return Column( children: [ Text("Name: $name"), Text("Age: $age"), ], ); }, ), ElevatedButton( onPressed: () { viewModel.changeName("Sophoun"); }, child: Text("Change Name"), ), ElevatedButton( onPressed: () { viewModel.changeAge(20); }, child: Text("Change Age"), ), ], ), ); } }
As you can see, ValueNotifier is a powerful yet simple tool for state management in Flutter. It's built into the framework, requires no external dependencies, and helps you write clean, efficient code. By using ValueNotifier, you can avoid the complexities of larger state management libraries while still building robust and scalable applications.
For developers who are new to Flutter or those who prefer a more lightweight solution, ValueNotifier is an excellent choice. It encourages a deeper understanding of Flutter's reactive nature and provides a solid foundation for building well-structured apps. Give it a try in your next project—you might be surprised by how much you can achieve with this simple yet effective tool.
ValueNotifier is a simple and efficient way to manage state in Flutter.ValueNotifier allows you to update only the specific widgets that need to change.