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();
}
}
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!;
}
}
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,
);
}
}
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),
);
}
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!;
}
}
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);
}
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),
),
),
);
}
}
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)