Flutter - How to use Riverpod's StateNotifierProvider

After seeing the other types of Providers of the Riverpod package, such as the "Provider" and the "StateProvider" now it's time to take a look at something that will be really useful especially in cases where we have a more complex state and we want to deal with it;

The "StateNotifierProvider" is what we need, it is no complex as it sounds, indeed in this written tutorial I am going to make examples so simple that it is impossible that you don't understand, also if you prefer the video version, check it out.

We need the StateNotifier

As the name may suggest the "StateNotifierProvider" it's a type of provider that is tied to the StateNotifier.

For those who don't know the StateNotifier is a package developed by the same author of Riverpod, Remi Rousselet.

To make things easy StateNotifier is like if you are using a ChangeNotifier but with Immutability, also you don't need to call manually updateLiseners cause when you change the state it will update automatically.

The StateNotifier package is already present inside Riverpod so we don't need to install anything.

Time to create our Notifier

Let's begin by defining a simple StateNotifier as example

dart
class myNotifier extends StateNotifier<List<String>> {
  myNotifier() : super([]);
}

We can define a StateNotifier by simply creating a class and extending the StateNotifier, also we define the type of content that we use inside our state, a List of Strings with <List<String>>

In the next line when we repeat the class Name followed by super, we simply are setting the initial state of our Notifier, we do so by passing it to the super keyword.

We are saying that we want this list initially to be an empty list by passing empty square brackets;

Obviously, if we want to initialize it with something we simply pass the string of values we want to show like "super([ 'something', 'here' ])".

Now we want to add a method so we can update our state, or it will be useless.

dart
class myNotifier extends StateNotifier<List<String>> {
  myNotifier() : super([]);

  void addString(String stringToAdd) {
    state = [...state, stringToAdd];
  }
}

The addString method will allow us to add new Strings inside our List; We do so firstly by passing as argument our string with the name of stringToAdd, this is the new string that we want to append to our list.

Inside the body of our functions the update the state with another state, if this is your first time dealing with Immutability and functional programming, you have to know this.

If you follow the Functional Programming Paradigm you should not change the state but you should return a new state, hence state.add(stringToAdd) it is not allowed, and also, most probably if you do so Riverpod will not be able to detect the change and it will not update;

Instead, you should change the state with a new state.

Since we need a new state this is the reason why we create a new Array with the brackets [] then inside we use the spread operator ... to spread all the values inside the previous state, that at the moment are present inside state, after we simply add our stringToAdd to the array as the last item.
 
If you find it difficult to conceptualize please check out the video, it may help a little bit, I know it's difficult to exampling things in this way, and no I'm not trying to advertise my Youtube channel! or maybe yes ;)

Finally StateNotifierProvider yes!

Now we are ready to define the Provider that will be responsible to provide us the notifier that we have just created.

This time we are going to use the StateNotifierProvider, as said previously it is tied to the StateNotifier, but how we can define it? Really simple! (As always)

dart
final providerOfMyNotifier = StateNotifierProvider((ref) => myNotifier());

By calling our class with myNotifier inside the StateNotifierProvider callback we initialize it to [] and now we are ready to call the provider to get the value.

Reading StateNotifierProvider

In order to read the StateNotifierProvider, we should do the same steps that we would do with other providers, get the ref and watch for our value.

In our case now we want to get the ref by defining a ConsumerWidget, that will return the needed ref as a second argument of the build method just after the context, if you recall, for sure you recall right?

dart
class MyApp extends ConsumerWidget {
  Random random = new Random();

  
  Widget build(BuildContext context, WidgetRef ref) {
    final listOfString = ref.watch(providerOfMyNotifier) as List; <---- HERE

    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Column(
            children: [
              ...listOfString.map(
                (string) => Text(string),
              ),
            ],
          ),
        ),
        appBar: AppBar(
          actions: [
            IconButton(
              onPressed: () {
                ref
                    .read(myProvider.notifier)
                    .addString('string ${random.nextInt(100)}');
              },
              icon: const Icon(Icons.add),
            ),
          ],
        ),
      ),
    );
  }
}

To finish things up we not only call our Provider and watch for changes with ref.watch but we also create a simple button inside the AppBar so we could add new values inside our list, and since we are watching for this list as soon as we will add new values the widgets will update immediately showing us the new values added.

In the upper part of the scaffold we simply show a Column and as children the list our string that is present inside our notifier, yes cause, when we call ref.watch with our provider, it returns the state, and the state contains the list as defined previously; At the beginning will contain an empty List but then by using the add button will add the new values to the list.

Adding new values

As you can see to add new values we use the addString method, to access it pretty straightforward cause we have already done so! Inside the onpressed we use the read, instead of the watch cause it's a callback, we get the controller simply by calling the .notifier property then we can access the addString method.

As a new value I'm adding a new string with a random value from 0 to 100, just to see something different on the string.

If you have followed everything correctly you will see that as soon as you press that button the view update and shows your list with a new value in the column.

As said previously if you have any troubles you can check out the video so you see how things work out.

tutorial statenotifierprovider how to use statenotifier how to use statenotifierprovider with riverpod easy tutorial on how to use statenotifierprovider with riverpod
Expand your knowledge about this topic