DEV Community

Cover image for BRIC: Create your own state management solution using just Flutter
Guilherme Rocha
Guilherme Rocha

Posted on

BRIC: Create your own state management solution using just Flutter

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, 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

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

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 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> in which T has to be a Listenable, and lucky for us ValueListenable is one.

Here is the solution for the InheritedNotifier

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!;
  }
}
Enter fullscreen mode Exit fullscreen mode

And this is the BricitProvider implementation

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,
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

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),
    );
  }
Enter fullscreen mode Exit fullscreen mode

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:

extension BuildContextExtensions on BuildContext {
  TBricit of<TBricit extends Bricit<TValue>, TValue>() {
    return dependOnInheritedWidgetOfExactType<
            InheritedBricit<TBricit, TValue>>()!
        .notifier!;
  }
}
Enter fullscreen mode Exit fullscreen mode

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

Sample

First, define your Bricit

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

  void increment() => emit(value + 1);
  void decrement() => emit(value - 1);
}
Enter fullscreen mode Exit fullscreen mode

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

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),
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)