Cubits are a special kind of stream component that rebuild UI on state emitted by the stream.
Functions are not a part of a stream but are a pre-baked list of what the Cubit can do. The stream part of it is hidden.
Blocs on the otherhand do emit streams of state when it is triggered by events.
- What is the initial state?
- submitting, completed
- What are the possible interactions?
- Buttons a screen for example
- What are the methods in the Cubit?
- login(), logout()
It's okay to start off with Cubits, it is basically Bloc lite. If you need to extend to Bloc, the main change is to add event.dart.
- Create a Cubit state
- Define values
class TestState{ final int testInt; }
- Create a class extending the Cubit state
- Set initial values
- Define methods to emit
class TestCubit extends Cubit<TestState>{ TestCubit(): super(TestState(testInt: 0)); void increment(){ emit(TestCubit(state.testValue: +1)); } }
The pattern goes that we usually wrap the main widget, MaterialApp()
for example, in a BlocProvider. Since it can be kind of expensive for every widget to have access to the Cubit, so we then wrap the widgets that use the state in a
BlocBuilder
for building widgets based on state.BlocListener
for performing side effects such as showing dialog messages.BlocConsumer
for building widgets and performing side effects.
For multiple Bloc/Cubit, we would use MultiBlocProvider
- Wrap a widget in the BlocProvider
- add a
create:
method that takes in the context and return the Cubit.
home: BlockProvider( create: (context) => TestCubit(TestRepository()), child: TestPage() ),
- add a
- For the widget (
TestPage()
in this case) that was child of the BlocProvider, add aBlocConsumer<Bloc, BlocState>
to listen and build.- add a
builder: (context, state) {}
and return widgets to render based on conditions.
builder: (context, state){ if(state is BlocInitial){ return buildInitialInput(); }else{ return buildErrorInput('An error has occured'); } }
- in addition to having a builder:, we can also add a
listener: (context, state){}
method to perform side effects such as showing Snackbars or AlertDialogs.
listener: (context, state){ if(state is TestError ){ Scaffold.of(context).showSnackBar( SnackBar(context: Text(state.message)) ); } }
- add a
Calling methods on the Cubit requires creating a new method in the widget that takes in the context and simply call the method after grabbing it from the context.
There are two ways to get the Cubit.
final testCubit = BlocProvider.of(context)
or
final testCubit = context.bloc<TestCubit>()
Then we can call the method.
testCubit.increment();
The only difference between Cubit and Bloc is that Blocs have events while Cubits don't.
We can use the exact same state created by the Cubit onto Bloc.
On the Cubit, we had a method called "increment", and for Bloc, we will create an event also called TestIncrement
.
Events come into the Bloc, and states come out. On Cubits, there are no events, only methods. Cubits hide the stream controller behind the emit()
method while Blocs shows the stream.
// Cubit
class TestCubit extends Cubit<TestState>{
TestCubit(): super(TestState(testInt: 0));
void increment(){
emit(TestCubit(state.testValue: +1)); // emit
}
}
For Bloc, we would actually create a Stream<BlocState>
called mapEventToState(event, callback)
, it actually uses shows the stream. Instead of emitting, Blocs will yield
. We retrieve state from the event. event.testValue
.
// Bloc
class TestBloc extends Bloc<TestEvent, TestState>{
TestBloc(): super(TestState(testInt: 0));
@override
Stream<TestState> mapEventToState (TestEvent event, ) async* {
if(event is TestIncrement){
yield TestBloc(event.testValue: +1);
}
}
}
Grabbing the Bloc from the context is the same as grabbing Blocs. There is only one simple change, instead of testCubit.increment()
we calling it testBloc.add(TestIncrement())
- Dependency injection widget that will pass Bloc down the Widget Tree.
- New screens will NOT get the context or the provider.
- Need to do Route Access to provide Bloc to multiple screens.
- Pass in BlocProvider.value to pass the Bloc into newly created context.
- For global access for all widgets, including newly created widgets, wrap the Material App in BlocProvider
BlocProvider.value(
value: BlocProvider.of<Bloc>(context),
child: CurrentScreen();
)
BlocListener and BlocBuilder
The foundations of listening to streams. They emit the initial state as soon as they are created.
streamSubscription = testCubit.listen(
(testState){
if(testState is State1) increment();
}
);
Remember to cancel the stream manually by overriding the close method, since it was created manually.
@override
Future<void> close(){
streamSubscription.cancel();
return super.close();
}
If the app is going to communicate with the outer data layer such as asynchronous calls to a database or API from the internet, then this file helps organize the file structure. Most of the data here would be streams, sinks, and variables returned should be futures.
Streams can bring 0, or multiple values, instead of 1 value from futures.