BRIC: Create your own state management solution using just Flutter
Tags: flutter, dart, mobile, programming
Published: 2023-01-09 22:41:15

State Management is a opinionated topic when it comes to Flutter Development, there are a lot of discussions regarding the comparison between Riverpod to Provider, or any other package like GetX, flutter_bloc and all the packages listed in the [official documentation](https://docs.flutter.dev/development/data-and-backend/state-mgmt/options), not only in matter of complexity and performance but also if it is trully a state management solution.

With that in mind I want to show how you could just use plain flutter to create your own state management solution using what it already provides you, creating your own abstraction.

### Why?

Flutter is already powerful enought and gives you solutions for state management, it's just a matter of knowing the components and widgets of the framework, It's always good to know what are your requirements instead of just using a package that is going to end up doing more than you actually need.

My focus on this post is to show what you can do in dart/flutter and show how you could abstract it and use it.

### Let's do it

To do it I was inspired to do something like what flutter_cubit does, so instead of Bloc I called my library Bric (It's very original as you can see).

Cubits are composed of three main components: `Cubit<T>`, `CubitProvider` and `CubitBuilder`. Also, I can get my cubit using `context.cubit<T>`. So let's start from `Cubit<T>`

Instead of Streams I decided to use `ValueListenable<T>` and `ChangeNotifier`, this is what `ValueNotifier` actually uses but instead of changing the value directly I wanted to use the emit method to mutate the value to be similar to cubit

```dart
class Bricit<TValue> extends ChangeNotifier implements ValueListenable<TValue> {
  TValue _state;

  Bricit(this._state);

  @override
  TValue get value => _state;

  void emit(TValue newState) {
    if (_state == newState) {
      return;
    }

    _state = newState;
    notifyListeners();
  }
}
```

As you can see it does not have to be complicated, just having a method to set the state and another one to get the state, not being able to mutate it directly.

Ok, but what about CubitProvider? how can you inject it anywhere in the widget tree and get it anywhere you want? Flutter already have [InheritedWidget](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html) which is a `Base class for widgets that efficiently propagate information down the tree.`. But what about notifying the widget tree via with ChangeNotifier? It also have a solution for that too called [InheritedNotifier\<T\>](https://api.flutter.dev/flutter/widgets/InheritedNotifier-class.html) in which T has to be a Listenable, and lucky for us `ValueListenable` is one.

Here is the solution for the InheritedNotifier
```dart
class InheritedBricit<TBricit extends Bricit<TValue>, TValue>
    extends InheritedNotifier<TBricit> {
  const InheritedBricit({
    required Widget child,
    required TBricit bricitNotifier,
    Key? key,
  }) : super(key: key, child: child, notifier: bricitNotifier);

  static TBricit of<TBricit extends Bricit<TValue>, TValue>(
      BuildContext context) {
    return context
        .dependOnInheritedWidgetOfExactType<InheritedBricit<TBricit, TValue>>()!
        .notifier!;
  }
}
```

And this is the BricitProvider implementation 
```dart
class BricitProvider<TBricit extends Bricit<TValue>, TValue>
    extends StatefulWidget {
  final TBricit Function(BuildContext context) create;
  final Widget child;

  const BricitProvider({
    Key? key,
    required this.create,
    required this.child,
  }) : super(key: key);

  @override
  State<BricitProvider> createState() =>
      _BricitProviderState<TBricit, TValue>();
}

class _BricitProviderState<TBricit extends Bricit<TValue>, TValue>
    extends State<BricitProvider<TBricit, TValue>> {
  @override
  Widget build(BuildContext context) {
    return InheritedBricit<TBricit, TValue>(
      bricitNotifier: widget.create(context),
      child: widget.child,
    );
  }
}
```

As you can see, it's all Flutter, there is no need to complicate your state management solution or downloading a third party package that does more than you actually need.

Let's see how the builder is implemented:

```dart
class BricitBuilder<TBricit extends Bricit<TValue>, TValue>
    extends StatefulWidget {
  final Widget Function(BuildContext context, TValue state) builder;

  const BricitBuilder({
    Key? key,
    required this.builder,
  }) : super(key: key);

  @override
  State<BricitBuilder> createState() => _BricitBuilderState<TBricit, TValue>();
}

class _BricitBuilderState<TBricit extends Bricit<TValue>, TValue>
    extends State<BricitBuilder<TBricit, TValue>> {
  @override
  Widget build(BuildContext context) {
    var bricitState = InheritedBricit.of<TBricit, TValue>(context);

    return ValueListenableBuilder<TValue>(
      valueListenable: bricitState,
      builder: (context, state, _) => widget.builder(context, state),
    );
  }
```

It just uses the `ValueListenableBuilder` that does all the heavy work for us, and to get the state it uses a static method to get the bricit from the InheritedNotifier. But you can change it and use a extension from dart get it via the BuildContext:

```dart
extension BuildContextExtensions on BuildContext {
  TBricit of<TBricit extends Bricit<TValue>, TValue>() {
    return dependOnInheritedWidgetOfExactType<
            InheritedBricit<TBricit, TValue>>()!
        .notifier!;
  }
}
```

There you have it, your own state management solution using pure Flutter.

### Sample

First, define your Bricit

```dart
class CounterBric extends Bricit<int> {
  CounterBric() : super(0);

  void increment() => emit(value + 1);
  void decrement() => emit(value - 1);
}
```

than you can simply use it just like this using the counter example

```dart
void main() => runApp(const CounterContainer());

class CounterContainer extends StatelessWidget {
  const CounterContainer({super.key});

  @override
  Widget build(BuildContext context) {
    return BricitProvider<CounterBric, int>(
      create: (_) => CounterBric(),
      child: const CounterWidget(),
    );
  }
}

class CounterWidget extends StatelessWidget {
  const CounterWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text("Bricit Counter Test"),
        ),
        body: BricitBuilder<CounterBric, int>(
          builder: (context, state) {
            if (state == 0) {
              return const Center(
                child: Text(
                  "There is no value",
                  style: TextStyle(
                    fontSize: 20,
                  ),
                ),
              );
            }

            return Center(
              child: Text(
                "The current value is: $state",
                style: const TextStyle(
                  fontSize: 20,
                ),
              ),
            );
          },
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: context.of<CounterBric, int>().increment,
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}
```


### Thank You!

I hope you enjoyed this post, tell me what you think in the comments below and leave a heart if you enjoyed it.
To see the full solution, just check my Github: https://github.com/guilhermerochas/bric