Flutter Cubit & Freezed a great combination for state management.
We all know that flutter is great for ui . but is it it’s main strength ? well , personally i think the most powerful thing about flutter is it’s Team and developers support either its’ fascinating updates each time or its’ packages that make your life easier.
Today we’ll Try to build a Simple Counter App and we’ll use:
Cubit ( a simpler version of Bloc ) : https://pub.dev/packages/bloc
Freezed 0.12.7 : https://pub.dev/packages/freezed
we’ll also use
build_runner 1.11.1 : https://pub.dev/packages/build_runner for code generation.
First let’s talk about cubit :
cubit is a new and simple solution for state management in flutter , it takes orders from the UI and returns a new state . believe me , it’s as simple as that . Why should we use Cubit ? well , if you want your code to be clean , readable, and maintainable , you’ll probably need to separate logic from UI , so ui is just for UI , anything except widgets should be outside the ui files.
What about freezed :
Freezed is a combination of many useful things , it helps you make your state immutable , but also provides you with unions ( which dart does not support for now ).. we’ll see what unions mean in a second and you’ll love it because it makes you never forget to implement one of your cases. Don’t worry , it’s easy .
build_runner is just a code generator all we need is this command , try to copy this command and paste it elsewhere because you’ll need it . the only command we’ll use :
flutter pub run build_runner — delete-conflicting-output
Now , let’s start coding : first of all we’ll create a new project ( i suppose you all know how to do that ) .
This is our dependencies list :
After installing dependencies , we’ll now create our cubit , to make your life easier , just install Bloc extension ( if you’re using vscode) and just make a cubit folder and click right mouse and chose new cubit and enter the name of your cubit , it will detect that you’ll use freezed and build a cubit with freezed for you . for other editors maybe you’ll find a similar extension. Otherwise , just follow this folder structure and you’ll find the code in the github link provided below.
counter_cubit.freezed.dart is generated after we run the command , so don’t create it yourself.
We’ll first create our state : we’ll have 4 states , initial , loading , error ,and success.
After running the magical command , we’ll have something like this .
For our cubit
So as i said , cubit just take orders and return state , events are just functions , in our case ( increment and decrement and reset )
to return state we just call emit(“state”);
let’s focus on the increment method , we’ll first call emit(CounterState.loading()) ,, then we’ll wait for the increment function from the FakeData class to do it’s work , then
we’ll return either a Success state with the new value if everything is ok => emit(CounterState.success(newValue))
otherwise we’ll return error state with the message => emit(CounterState.error(e.message))
so far , i think every thing is clear , we’ll take a look at our fakeData class , i think you’re wondering what’s going on right there .
A added some Future.delayed to show you the loading state , otherwise we’ll not have the chance to see what’s going on.
So that’s all about Cubit . For the freezed part we’ll see it’s magic . our state is know immutable , but what about unions . we’ll talk about it when it comes to UI.
NOTE : after running the magical command , you’ll find a file called file_name.freezed.dart , it’s just a file for the generated code , don’t worry if you didn’t understand what’s going on there , you don’t have to.
UI part
This is our main widget . so we’ll wrap our Scaffold with a BlocProvider.
What’s a BlocProvider ? bloc provider is just a (provider) that provides a specific bloc to the widgets below . in our case we’ll need our CounterBloc to use it in the Counter Widget.
This is our Counter widget . For each bloc provider , we’ll need a bloc(builder-consumer-listener) on the other way . In our case i used BlocConsumer , it provides both a builder and a listener.
builder runs with every change in the state , so if you run setState() in a stateful widget builder will run either your counter state changed or not and it also runs when counter state changes
listener runs only when counter state changes , so if it goes from loading to success it will run only once. If any local change happens inside state , listener won’t run because our state is still ( counter state success is our case ). we can use listener to show a snackbar or to navigate to another screen.
Freezed unions :
In computer science, a tagged union, also called a variant, variant record, choice type, discriminated union, disjoint union, sum type or coproduct, is a data structure used to hold a value that could take on several different , but fixed , types ( wikipedia) .
And this is exactly what we need , several different state types . ( 4 in our case ) so depending on each type , we’ll return a specific UI . we’ll implement it in a second.
The magic of MAYBE and MAYBEWHEN
let’s know show the ui corresponding to a specific state.
Let’s first talk about maybeWhen. with unions provided by freezed , you’ll be able to use maybeWhen , it’s used when you want to do something depending on some different types but not all types . So here we just need the error state type to show our snackBar .. orElse is called if our state is not of type error. ( orElse is required but if you don’t want to use it just return an empty function)
when is used when you need to use all of your types , it shows an error when you forget to use one of them.
So when you type state.when it will automatically show you all of your different types ( that’s why i said it makes you never forget to implement or to use one of your cases)
Building the UI
inside the builder , we’ll return a different ui depending on the our state
Making orders
We’ll use 3 floatingactionbuttons to increment ,decrement and reset our counter .
i used state.maybeWhen to make order depending on the current state , you can just declare a local variable to track you countingnumber , and avoid this boilerplate but i just wanted to show you that we can use state.maybeWhen whenever we want .
To call a cubit function; builder as well as listener provides us with both our state and a context , to call the cubit provided by the BlocProvider you just need to call context.read<CounterBloc>().(increment-decrement-reset)().
you can also use BlocProvider.of<CounterBloc>() outside the builder and listener function , if you need to.
Final Application
so that’s our app , first is for the INITIAL state , second is for the LOADING state , third is for the ERROR state.
And of course you can increment and decrement.
PROJECT LINK
https://github.com/hedi-ghodhbane/cubitfreezed
myportfolio and finally my linkedin account for any contact