State Management in Flutter with Provider Package
After reading this article full, you’ll have clear view of why we use state management solutions and how helpful they can be as complex the app gets. You’ll also learn about Provider Package in Depth, from installation to implementing in code, everything. Make sure to read the full blog for this! Let’s dive right into it.
What is State and State Management?
State is defined as the data which affects the user interface that can change over time. Thus, User Interface here can be described as function of your data.
There are two main kinds states:
- App Wide State: This type of state affects the entire app, example, if we want to check if the user is authenticated. If the user is not authenticated, then we have to show the login/signup screen or the products screen for an online shopping app.
- Local State: This type of state affects only widgets, example, if we want to add loading spinner until the products are shown on the online store.
State Management is defined as the management of the state of one or more user interface controls. For example, I click a button and that button changes the theme of the app. This is done through State Management. The main question now arises, why do we need State Management? Let’s know about it!
Why do we need State Management?
- As the app gets bigger and complex, passing data via constructors can be cumbersome and difficult. For example, I have a login screen and I am in need of the name of the user on the Settings Page. For that, I’ll have to pass the name of the user through constructor to Home Page which will then send the name through constructor again to Settings Page which gets tiring as the app gets bigger and we start to get more screens.
- There are unnecessary rebuilds of the entire app or major parts
of the app and even memory leaks which are to be taken care of.
Now, we know why we need state management but which one do we need in Flutter? There are mant state management solutions. Let’s have an overview of them.
State Management Solutions In Flutter:
- Provider
- Inherited Widget
- Redux
- BLoC
- MobX
- GetIt
- RiverPod
I personally prefer Provider over all of these and we will go in depth of Provider Package in this blog.
Provider uses Inherited Widget indirectly to establish direct communication channel between the widget and data provider(container) that is attached to different widget and below is the diagrammatic representation of how Inherited Widget Works.
Provider Package
Provider works as if you have a global central data provider(container) which you have to attach to your widget. After you have attached provider to widget, all child widgets can listen to that provider, not using the constructor but
using of(context). Provider needs to be defined with the help of class. New Provider is built based on that class definition which is called as model. The advantage of using Provider is that it makes only the build() method run when state changes i.e whenever data in the data provider(container) changes, only then the UI will also update.
Models that are used in Provider are basically objects with a mix-in. Mix-in is added using *with* keyword followed by the name of mix in. Mix in is similar to *extending* keyword(Inheritance). The main difference is that you merge some properties or add some methods into the existing class but you don’t return your class into an instance of that class.
Installation
Go to Pub Dev website of Provider Package and go to the installing tab and add the code to dependencies.
Code With Provider
class Products with ChangeNotifier {}
This is how Models using Provider Package are created. ChangeNotifier is the name of the mix-in which is related to inherited widget which is used by provider package behind the scenes. You don’t necessarily have to use ChangeNotifierProvider in the root of the widget tree, you just have to add it at the highest level of the widgets that need the data in the Widget Tree. (More about this in detail below)
Note: The entire app will not rebuild if you provide it at the highest level. Only the widgets that are listening to the model will get rebuilt.
Types Of Providers
- ProxyProvider
- ChangeNotifierProvider
- Provider
- ListenableProvider
- StreamProvider
- MultiProvider
and more..
We will go with ChangeNotifierProvider..
ChangeNotifier
class Products with ChangeNotifier {
List<Product> _items = {// add some data..};
List<Product> get items {
return [..._items];
}void findById(String newId) {
_items.firstWhere((prod) => prod.id == newId); // This is how we get loaded Products
}void addProduct(Product newProduct) {
_items.add(newProduct);
notifyListeners();
}
}
This is a model named Products. To remind you, ChangeNotifier is mix-in provided by Provider package which gives us in built functions like notifyListeners. To explain the code, we return a copy of the items using the spread operator so that we cant directly edit our items from anywhere else in the app as if we do that we can’t call notifyListeners() because we can only do that from inside of our class and the widget rebuild would not be done
correctly as the widgets would not know about the change.
ChangeNotifierProvider
return ChangeNotifierProvider(
create: (ctx) => Products(),
//If you use provider version 3.0.0 then function you have to call is builder. For 4.0.0 and above function is create.
child: ...
);
You have to wrap ChangeNotifierProvider around the parent widget of the widget(s) you want to listen info from. It’s not a compulsion to wrap around the root widget.
Provider.of
For the widget interested, you can listen to the main class to get the data using the code below.
Provider.of<T>(context);
Provider allows us to set connection between one of the provided classes.
T here refers to type of data you want(Generic Data Type)
For our case, we can use:
Provider.of<Products>(context);
This is how you listen to the class from the widget. Provider now goes into the widget's parent class and checks if it is wrapped by any of the Providers(ChangeNotifierProvider in our case).
Logic Related Matter & Conventions
It is recommended to have all your logic related to Provider in the model in which our Provider is defined so that we get leaner and easy-to-read code in the widgets.
To find a list of loaded products, we will run the following code in the screen where we want the products to show up:
Provider.of<Products>(context).findById("AnyIdDuh");
The problem with the above code is that we don’t have to update the UI continuously, we just have to get the data once i.e. when screen
is built. To do this, we can use the following code:
Provider.of<Products>(context, listen:false).findById("AnyIdDuh");
Alternative To Provider.of
Consumer is the alternative to Provider.of which is also provided by the Provider Package. It is wrapped specifically around the child widget that needs to be built in the widget. Example, I have a heart icon on a grid tile, that has to change it’s color with every press on it and nothing else has to change thus nothing has to listen to the provider(container). Instead of rebuilding the entire widget which has name and price of the product as well, I can wrap Consumer around the widget, in my case, IconButton. Check the code below for clarity.
return Consumer<T>(
builder: (ctx, instanceOfT, child) => IconButton(...),
child: Text("This text will never change."),
);
If you would have used Provider.of in this case, it would have triggered the whole build method again causing minor performance issues.
Here, T refers to the Generic Type which can be any data type but makes much more sense when we use the Data Type/ Models we define not already present like String.
So, this was the Provider Package- it’s explanation, installation and code! I hope you have enough knowledge to implement in your code and work with them well enough!
For any doubts, feel free to comment below or contact with me:
Instagram: https://instagram.com/optimalcoding
GitHub to Project Using Provider Package: https://github.com/rivaanranawat/astra