DEV Community

loading...
Cover image for StateNotifier, an improved ChangeNotifier

StateNotifier, an improved ChangeNotifier

Marcos Sevilla
Flutter and Dart developer.
Updated on ・6 min read

Whether you are just starting out or have been using Flutter for a few years, there's one topic that always comes up and has a thousand perspectives revolving around it: state management.

State management is essential in Flutter. Many confuse it with architecture or believe that an architecture depends on a specific state handler. But the truth is that state management is a small part of the great architecture that a project can have.

On previous occasions I have already spoken enough on this subject, so I better leave you references to these talks that I have given:

  • Flutter's Six Paths of State (yes, it's a reference to Naruto): It will help you to contemplate different alternatives to manage state in Flutter and choose the one that makes your life easier.
  • Talking about State Management + Architecture: It will help you to see what role the state management of your app plays in the architecture of it and serves as a critique for Six Paths of State.

😓 The videos I'll quote or suggest you to watch are in Spanish because that's my mother tongue. So I apologize for that.

https://media.giphy.com/media/KcVny4iEXBr7NEsSv7/giphy.gif

First of all, Provider

You're probably already familiar with the Provider package, which isn't exactly a state manager, but a friendlier way to use InheritedWidget. If you don't know anything about InheritedWidget, here I have a video for you.

Simply put, Provider allows us to use InheritedWidgets to work with classes or more atomically, values, to handle our state. That makes Provider a tool for communication between widgets, not for state management as such.

If at this precise moment I'm destroying the concept you had of Provider, I'm accomplishing my mission. The plan is not to confuse you, it is for you to better understand why StateNotifier, which is the subject of this article. We'll get to that, calm down.

Provider itself can provide you with information, just like an InheritedWidget does. But the state managers are the classes that contain that data that Provider has. Got it?

☝️
BLoC → BLoC pattern
Bloc → Library to manage states implementing BLoC
flutter_bloc → package containing the widgets to integrate Bloc with Flutter

I call BLoCs or business logic components to those classes. They're in charge of managing this information interacting with external data sources (an API, for example) through data access classes (services or repositories). And basically the BLoC pattern is followed there, I also have a video talking about it at this link.

The classes used with Provider are usually ChangeNotifier, ValueNotifier or some custom class that extends from these. They're classes that notify -as their names say- to others (widgets included) that are consuming their properties (through Providers). That's the most typical and one of the simplest and most initial ways of handling state with Flutter.

I clarify, there are other packages that use Provider for communication of its logic components with its user interfaces.

Some of them are bloc and mobx.

The problem

The problem is not ChangeNotifier, the problem is that it depends on Flutter.

ChangeNotifier is a good alternative to create classes that serve as BLoC. But as you may know about me - if you read my other articles - I really enjoy modularizing my code and to extract my logic in other packages and making them as uncoupled as possible, that's when it's not enough for me. This mostly because of its dependency on Flutter.

I like to create my packages separately with the least amount of dependencies possible. If I can avoid the Flutter SDK, better.

I propose this case...

🤔 I'm creating my app logic in Flutter that I can reuse with other interfaces, so I make it a package. That same logic is useful to me for another app that allows me to call Dart code directly, let's remember that Dart can be compiled into binary and that opens it up to more use cases than just apps with Flutter. I already have the logic, but I use ChangeNotifier, which makes me unnecessarily dependent on Flutter.

https://i.redd.it/z26wpq3ddm121.jpg

Hello StateNotifier

StateNotifier is another type of class that serves as a logic component and is a "reimplementation" of ValueNotifier, with the difference that it doesn't depend on Flutter.

This package with the functionality of ValueNotifier abstracted, gives us the power to reuse our logic without depending on Flutter, as we wanted. Plus it has a better integration with Provider, because both were made by the same author.

After having learned a lot about flutter_bloc and its structure, I understood how handling states as classes was much more convenient and helped to modularize your widgets based on the current state in the Bloc.

StateNotifier has a lot of things similar to Bloc, including:

  • Initial state in the base constructor.
  • Changes to the state through methods (more similar to Cubit than to Bloc).

💡 One difference is that StateNotifier assigns the state to the protected variable state (equivalent to value in ValueNotifier) and Bloc uses the word yield to emit a state so it uses Streams for stream data.

How to use StateNotifier

  1. We install the dependencies.

    # * if you use Provider
    provider: ^4.3.3
    state_notifier: ^0.6.0
    flutter_state_notifier: ^0.6.1
    
    # * if you use Riverpod, already includes state_notifier
    flutter_riverpod: ^0.12.2
    
    # use the latest versions of these dependencies or 
    # the ones compatible with the rest of your project
    
  2. We create the states.

    abstract class UserState { }
    
    class UserNotLogged extends UserState { }
    
    class UserLogged extends UserState {
        UserLogged({@required this.user});
        final User user;
    }
    
  3. We create our StateNotifier.

    class UserStateNotifier extends StateNotifier<UserState> {
    // we create our initial state by passing it to super, 
    // just like Bloc
    UserStateNotifier() : super(UserNotLogged());
    
    // we change state assigning a new
    // one to the state property
    void logIn(User user) => state = UserLogged(user: user);
    }
    
  4. We consume our BLoC (StateNotifier) in the UI.

    /// * if you use Provider
    
    void main() => runApp(
        StateNotifierProvider<UserStateNotifier, UserState>(
            create: (_) => UserStateNotifier(),
                child: MyApp(),
            ),
    );
    
    ...
    
    class MyWidget extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
    
            final userState = context.watch<UserStateNotifier>();
    
        return Scaffold(
          body: Center(
            child: Text('${userState.runtimeType}'),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              context.read<UserStateNotifier>().logIn(User());
            },
          ),
        );
      }
    }
    

And that's a simple example of how to start using StateNotifier instead of ChangeNotifier or ValueNotifier.

You can see that another flutter_state_notifier package is used. As well as other packages like bloc and flutter_bloc, a package with the prefix flutter is included since this is the one that contains widgets that are useful to integrate the another package with your user interface. In this case it works for us to use the StateNotifierProvider class.

Creating better states

In step 2 of the previous part, you can see that we created an abstract state that can be used by any class through inheritance. This is a practice that I first saw in Bloc, where defined events alter the current state of a BLoC. It's very good because we define in the BLoC that we're going to send a state that inherits from an abstract state, as we saw in the example.

With StateNotifier, this is excellent when passing a specific type of data (our abstract state) and assigning it as we need.

The problem with this is that it gets a bit slow and we write boilerplate code when we have several possible states. For this reason, we are including another package called Freezed, which works amazingly with StateNotifier.

We can simplify the code with a Freezed union, it would look like this...

import 'package:freezed_annotation/freezed_annotation.dart';

part 'user_state.freezed.dart';

@freezed
abstract class UserState with _$UserState {
  const factory UserState(User user) = UserLogged;
  const factory UserState.notLogged() = UserNotLogged;
}
Enter fullscreen mode Exit fullscreen mode

If you would like to know more about Freezed to know how to autogenerate the above code and what it means, I suggest you read my article about this package here.

Goodbye ChangeNotifier / ValueNotifier?

Not really.

Rémi Rousselet, creator of Provider, StateNotifier and Freezed, has maintained and created pull requests to the official Flutter repository to improve ChangeNotifier.

So it doesn't mean that StateNotifier is a successor. As usual:

It's one more option that you can choose.

I personally use StateNotifier with Riverpod, the Provider rewrite, and I quite like the combination as Riverpod includes it by default. Handling states much like the way flutter_bloc does seems very convenient and makes code more predictable (which is very good). All this added to the fact that the state assignment syntax is simpler and facilitates its emission.

https://i.redd.it/jvm0gl943b121.jpg

As always...

You can share this article to help another developer to continue improving their productivity when writing applications with Flutter.

There's a Spanish version of this article on Medium. You're welcome. 🇪🇸

Also if you liked this content, you can find even more and keep in contact with me on my socials:

dev.to, GitHub, LinkedIn, Medium, Twitter, YouTube.

Discussion (2)

Collapse
robvirtuoso profile image
robvirtuoso

Too many options out there that are almost the same things, just called differently. I'm even tempted to just write my own, with an API that I like, and use that in all of my projects. But good to see efforts like this to simplify things. I honestly don't like the full BLoC format (event streams) of flutter_bloc, although Cubit is good and simple. I feel like explicit event streams is added convolution and produces code noise more than anything else.

Collapse
marcossevilla profile image
Marcos Sevilla Author

I agree, there are too many options and feel free to experiment for yourself to create the one you prefer. On the other hand, flutter_bloc is one of the best packages for state management, the good thing about it is that it abstracts the stream API (which you can also access if you want) so you technically don't work with streams directly, Cubits are mostly the same as StateNotifiers, just based on streams.