State Management in Flutter using Provider

One of the challenges that I faced when I first started developing with flutter was understanding how to manage different states in the app. As you probably know in Flutter everything is a widget, Flutter rebuild these widgets each time the state changed and because Flutter is declarative you write only one code path per state which means managing these states is very important

The problem with managing all states in the app main widget is that you will have to pass it as an argument to all widgets in the tree in order to get to the one that needs or uses it which is bad for your app performance because Flutter will rebuild all these widgets.

State is any data that affects the UI and any change of that data results in a change of the UI. For that, we can identify two types of state Local and App-wide state

Local state which only affects one widget this is what we use in Stateful widgets.

App-wide state which affects the whole app or many parts of the app

The state here only affects the widget itself and has no effect on any other part of the app this typically useful for widgets that interact with users but has no effect on the data (show or hide something or progress bars). setState() tells flutter that the state has changed so it rebuilds the widget with the state.

When it comes to app-wide state I’m sure you heard about a lot of solutions that can be used but the one that is mostly recommended by Flutter is using Provider.

First to use Provider in your application you need to add it to dependencies in your pubspec.yaml file and run pub get

dev_dependencies:
flutter_test:
sdk: flutter
provider: ^5.0.0

Now you are ready to start implementing and using Provider in your app. in this article we are going to use a book shop app to show how you can benefit from managing your app-wide state with Provider.

ChangeNotifier

Your data class needs to extend this class in order to be able to notify listeners of changes to its data, each time you make changes to this data you need to call notifyListeners(). this will result in a rebuild of all widgets that are listening to this data. In our example, we have a Books class that contains a list of books and you can add and remove books from the list.

class Products with ChangeNotifier{
List<Book> _books = [];
void addProduct(String title,double price) {
_books.add(Book(title:title,price:price));
notifyListeners();
}
}

Change Notifier Provider

The official documentation says that ChangeNotifierProvider is the widget that provides an instance of a ChangeNotifier to its descendants. It comes from the provider package.

That means the ChangeNotifierProvider widget is the one holding and providing data to its child widgets. which takes us to an important part that is ChangeNotifierProvider needs to be the highest widget in the tree of all widgets that uses that data, also, it shouldn’t be higher than necessary.

Taking the same example we want to put a favorite button on each item so the user can add that book to favorites, to do that we need to provide a book for each item. this time the widgets that need that data are Item and Details that means ChangeNotifierProvider needs to be in BooksList widget, also, we have to make Book class extend ChangeNotifier

class Book extends ChangeNotifier{void toglleIsFavirote() {
isFavorite = !isFavorite;
notifyListeners();
}
}

In the Books ListView widget builder, we should wrap our Items with ChangeNotifierProvider and provide the data to it from the books list provided from MyApp to BookList as we explained before.

ChangeNotifierProvider.value(
value: book[index],/* this is the value of the data you want to provide*/
child: BookItem(),
),

You need to use value constructor when dealing with list or grid views because Flutter reuses old widgets and recycles them this will cause a problem in keeping track of listeners.

It is important to dispose providers when navigating and replacing screens to avoid memory leaks, fortunately, ChangeNotifierProvider does that for you.

In the BookItem widget we can get Book instance by calling Provider.of<Book> this will look for the closes Provider with the same Type in our case Book and it return it now you can access data and make changes.

final Book book = Provider.of<Book>(context, listen: false)

Consumer

when your widget needs data but does not get affected by changes you should use listen: false. Now for favorite button we need to be changing its Icon when the user adds the book to favorites or takes it out, since only the button is affected by that we can use Consumer<Book> this allows you to wrap a widget child and provide data to it. This helps Flutter as it rebuilds only Icon Button and not the entire Item widget.

Consumer<Product>(
builder: (context, product, child) => IconButton(
icon: Icon(product.isFavorite
? Icons.favorite
: Icons.favorite_border),
onPressed: () {
product.toglleIsFavirote();
},
color: Theme.of(context).accentColor,
),
child: ,/* some widget that is not affacted by changes you can use
it in the builder*/
),

the builder takes a Function that returns a widget and you get a context, data you need to be provided, and child which is a static widget that doesn’t get affected by changes to the data.

Now when you want to access your favorite books anywhere in your app, you only need to add a filter that returns only the books with isFavorite equal to true.

List<Book> get favOnly {
return books.where((book) {
return book.isFavorite;
}).toList();
}

This article was inspired by Maximilian Schwarzmüller Flutter and Dart course on udemy.

Thank you for reading, If you find this helpful hit that ♥ button and share this with your friend.

A young Mobile Developer with a passion for learning