StateNotifier, ValueNotifier outside Flutter

StateNotifier, ValueNotifier outside Flutter

Another way to manage state

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 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 watch are in Spanish because that's my main language. So I apologize for that.

https://cdn.hashnode.com/res/hashnode/image/upload/v1643931809914/xzkvDd_Ak.gif

First of all, Provider

You're probably already familiar with package:provider, which isn't a state manager, but a friendlier way to use InheritedWidget. If you don't know anything about InheritedWidget, here you go.

Simply put, provider allows us to use InheritedWidgets to work with classes or more atomically, values, to handle our state. That makes it a tool for communication between widgets, or dependency injection, 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 use state_notifier, 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 the 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 here.

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 package:bloc in its sibling package:flutter_bloc, and package: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 to extract my logic in other packages and making them as uncoupled as possible, that's when it's not enough for me. This is 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://cdn.hashnode.com/res/hashnode/image/upload/v1643931812122/IQawO34wi.jpeg

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 better integration with provider via package:flutter_state_notifier 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 were much more convenient and helped to modularize your widgets based on the current state of the Bloc.

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

  • Initial state in the base constructor.
  • Changes to the state through methods.

Actually, the last one is related to Cubit not Bloc.

💡 One difference is that StateNotifier assigns the state to the protected variable state (equivalent to value in ValueNotifier) and Bloc uses the Emitter class to emit a state, which uses Stream underneath.

How to use StateNotifier

  1. We install the dependencies.
dependencies:
    # * if you use Provider
    provider:
    state_notifier:
    flutter_state_notifier:

    # * if you use `riverpod`, already includes `state_notifier`
    flutter_riverpod:

    # use the latest versions of these dependencies or 
    # the ones compatible with the rest of your project
  1. We create the states.
abstract class UserState {}

class UserNotLogged extends UserState {}

class UserLogged extends UserState {
  UserLogged({required this.user});
  final User user;
}
  1. 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);
}
  1. We inject and then consume our StateNotifier.
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 another package with your user interface. In this case, it works for us to use the StateNotifierProvider class.

Creating better states

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 package:freezed, which works amazingly with StateNotifier.

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

import 'package:freezed_annotation/freezed_annotation.dart';

part 'user_state.freezed.dart';

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

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 and ValueNotifier?

Not really.

Rémi Rousselet, the creator of provider, state_notifier, 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 it includes it by default. Handling states much like the way flutter_bloc does seem 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://cdn.hashnode.com/res/hashnode/image/upload/v1643931813602/1rRCshdhq.jpeg