I am using BLoC in flutter.
As soon as BLoC instance is created I want to make to API calls. To achieve that, I have added the following code inside the constructor.
class MyBloc extends Bloc<MyBlocEvent, MyBlocState> {
MyBloc() {
_repository = MyAccountRepository();
_myAccountList = List();
add(API1CallEevent());
add(API2CallEevent());
}
...
and the event handling part
...
#override
Stream<MyBlocState> mapEventToState(MyBlocEvent event) async* {
if (event is API1CallEevent) {
var ap1 =
await _repository.getAPI1();
----
----
}else if (event is API2CallEevent) {
var api2 =
await _repository.getAPI2();
----
---
}
}
The problem I am facing is that the API calls are not executed parallel, which means after API1CallEvent is completed then API2CallEvent get executed...
is there any way to do that in parallel?
In my opinion, doing two API calls in parallel and expecting result at the same time is not much related to BLoC.
It is better if each bloc-event triggers a specific set of actions, and events are decoupled from each other.
Additionally;
Instead of raising an event inside init block, it is better to do that when you init Bloc inside a provider. See example;
BlocProvider<AuthBloc>(
lazy: false,
create: (context) => AuthBloc(
userRepository: _userRepository,
)..add(AppStartedEvent()),
),
This generates an event right after Bloc is initialized.
A bloc basically is a state machine. It does not do parallelism, that's not what it's built for. It's sequentially going from one state into another. In doing that, it can do things in parallel internally, but it cannot (or should not) take input in parallel.
If you want one event to execute multiple awaitable actions in parallel, you can do that:
#override
Stream<MyBlocState> mapEventToState(MyBlocEvent event) async* {
if (event is CallTheAPIsEvent) {
final results = await Future.wait([
_repository.getAPI1(),
_repository.getAPI2()
]);
// do something with the results
yield ApisHaveBeenCalledState();
}
// more event handling
}
Related
my application is growing in complexity and I can't figure out how to handle the await of an Event, when I need to execute code after that Event. Now I'm just putting the code that I need after an event inside the Bloc and I now that this is not the way to do it, making my app a mess. This is how I am managing my app:
For example, if I need to add a user in my backend and after that, execute an action I do this in my view/screen:
BlocProvider.of<UserBloc>(context).add(AddUserEvent())
As events are async, I can't put the code after that line so Inside the UserBloc I am making:
on<HomeNavigationEvent>((event, emit) {
#Call backend api to create user
#Do my needed action
});
And some times this is even worst because I need to call another Bloc, so I have to pass the context to that Event, like this:
BlocProvider.of<UserBloc>(context).add(AddUserEvent(context))
on<HomeNavigationEvent>((event, emit) {
#Call backend api to create user
BlocProvider.of<OtherBloc>(event.context).add(MyNeededActionEvent())
});
So I think the answer is related with Bloc listener, but I don't know how to check for an event instead of a state I mean I can't do this because I am receiving the state but not the Event:
return BlocListener<UserBloc, UserState>(
listener: (context, state) { #I would like to have (context, state, event))
if (event is AddUserEvent) {
#DO my needed action
}
})
[EDITED]
Real case of my app:
VehiculoBloc() : super(const VehiculoState(vehicle: null)) {
on<GetCurrentVehicle>((event, emit) async {
vehicle = await api.getCurrentVehicle();
final bool showVehicleButton = vehicle != null;
BlocProvider.of<HomeBloc>(event.context).add(ShowVehicleButtonEvent(showVehicleButton ));
emit(state.copyWith(vehicle: vehicle));
});
}
The main purpose of using BLoC is to separate the UI from the state. And have a global, and immutable, state that you can access from wherever you wish. The purpose of the UI is the fire events and let the BLoC generate the Appropriante state. If you try to listen to event, why would you even use BLoC?
In situations like yours, you want to create a dedicated state for the AddUserEvent. For example AddUserStateSuccess Whenever the BLoC receives that event it will emit the corresponding state. All you have to do is listen for that state ;)
In the listener of that state you can fire other events from other BLoC as well.
I'm newbie to the BLoC design pattern, and I have a question about it:
Does state requires (green) thread-safety?
I give a short psuedocode below:
class Bloc {
State state;
StreamSubscription subscription;
void subscribe(Stream src) {
this.subscription = src.listen(this.businessLogic);
}
async void businessLogic(Object data) {
State oldState = this.state;
State newState = await someAsyncFunction(oldState); // <-- here
this.state = newState;
}
}
In this case, I guess a race condition may happen if two data successively arrive at src:
If the second data arrives while the first data is awaiting someAsyncFunction, the oldState will be the initial state for both of the first and second call of businessLogic.
Is there any good insight about this problem? Thanks!
Dart by default runs on the only 1 thread, so there's no concept of threads in dart. The equivalent term is Isolates and Event Loops which manages Isolates which runs your code on separate thread. So, to answer your question "No", if you don't use Isolates BLOC don't require thread safety.
If you are using Future then It'll also use same Event loop and make your Future function wait till response arrives from the execution of that Future function, so if you want to wait till the response arrives and after that assign another value to newState then you need to use Async await in following fashion.
Future<void> businessLogic(Object data) async{
State oldState = this.state;
State newState = await someAsyncFunction(oldState); // <-- here
this.state = newState;
}
More info on Isolates and Event loops here.
Haven't seen a lot of info about it online...
What are the possible use cases in which I'd want to use, either Future.doWhile, Future.microtask or Future.sync?
Future.sync
A lot of times after a button press for example, I'd want a Future to happen immediately,
but I wouldn't want it to block the UI, is that a good use case for Future.sync or is that better to use Future and let dart handle when thing will get executed?
I'd want a Future to happen immediately...
You can't make Future to happen immediately because it needs some time to be executed. Instead you can block UI thread while future is executing. The pseudo code looks like that:
T runSync<T>(Future<T> future) {
while (future.running) sleep(10);
return future.result;
}
This will block your ui thread. That's why we are using Futures. Futures used for specific tasks that's not resolved immediately (usually I/O tasks, eg: network requests, file read/write) to get notified when future resolves without blocking UI thread.
Here's how I'm handling futures without blocking UI thread:
class MyState extends State<..> {
bool _running = false;
Future<String> doTask() async {
// some long running IO tasks
return 'Hello world';
}
Future handlePress() async {
setState(() { _running = true; });
try {
await doTask();
} finally {
if (mounted) {
setState(() { _running = false; });
}
}
}
Widget build(BuildContext context) {
return FlatButton(
child: Text('Execute'),
// Disable button if task is currently running to block parallel calls (for example sending same http request twice)
onPressed: _running ? null : handlePress,
);
}
}
In this code when user presses FlatButton I'm setting _running to true to disable FlatButton until Future is running.
I have two BLoCs.
EstateBloc
EstateTryBloc
My Application basically gets estates from an API and displays them in a similar fashion
Now I wanted to add a sort functionality, but I could only access the List of Estates via a specific state.
if(currentState is PostLoadedState){{
print(currentState.estates);
}
I wanted to make the List of estates available for whichever bloc, that needed that list.
What I did was, I created the EstateTryBloc, which basically contains the List of estates as a state.
class EstateTryBloc extends Bloc<EstateTryEvent, List<Estate>> {
#override
List<Estate> get initialState => [];
#override
Stream<List<Estate>> mapEventToState(
EstateTryEvent event,
) async* {
final currentState = state;
if(event is AddToEstateList){
final estates = await FetchFromEitherSource(currentState.length, 20)
.getDataFromEitherSource();
yield currentState + estates;
}
}
}
As I print the state inside the bloc I get the List of estates but I dont know how I would use that List in a different bloc.
print(EstateTryBloc().state);
simply shows the initialState.
I am open for every kind of answer, feel free to tell me if a different approach would be better.
When you do print(EstateTryBloc().state); you are creating a new instance of EstateTryBloc() that's why you always see the initialState instead of the current state.
For that to work, you must access the reference for the instance that you want to get the states of. Something like:
final EstateTryBloc bloc = EstateTryBloc();
// Use the bloc wherever you want
print(bloc.state);
Right now the recommended way to share data between blocs is to inject one bloc into another and listen for state changes. So in your case it would be something like this:
class EstateTryBloc extends Bloc<EstateTryEvent, List<Estate>> {
final StreamSubscription _subscription;
EstateTryBloc(EstateBloc estateBloc) {
_subscription = estateBloc.listen((PostState state) {
if (state is PostLoadedState) {
print(state.estates);
}
});
}
#override
Future<Function> close() {
_subscription.cancel();
return super.close();
}
}
To be honest I overcomplicated things a little bit and did not recognize the real problem.
It was that I accidently created a new instance of EstateBloc() whenever I pressed on the sort button.
Anyways, thanks for your contribution guys!
Is there any elegant way to map incoming streams from a private api directly inside mapEventToState() without having to create redundant private events in the bloc?
I came with this solution. It's ok with one single stream, but with multiple streams it starts to get a mess. Thanks in advance.
// (don't mind the imports, this is the bloc file)
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
final MyPrivateApi api = MyPrivateApi.instance; // singleton
ExampleBloc() {
// api has a stream of booleans
api.myStream.listen((b) {
// if it's true fire this event
if (b) this.add(_MyPrivateEvent());
}
#override
ExampleState get initialState => InitialExampleState();
#override
Stream<ExampleState> mapEventToState(
ExampleEvent event,
) async* {
if (event is _MyPrivateEvent) {
yield SomeState;
}
}
// private Event
class _MyPrivateEvent extends ExampleEvent {
}
As I can see, you can subscribe on event updates in your screen, and push event from screen to Bloc if need some calculations. Code will be more clean.
Your way seems to be the only way works and seems to be used - see this bloc issue: https://github.com/felangel/bloc/issues/112 and this example project: https://github.com/algirdasmac/streams_and_blocs
Just make sure to dispose the subscription that gets returned by api.myStream.listen.
Previous answer
The following DOES NOT work for infinite streams because the generator function will await until the stream finishes. This can only be used for stream the complete fast, like maybe an upload/download progress.
See accepted answers here Dart yield stream events from another stream listener and here Dart/Flutter - "yield" inside a callback function
ExampleBloc() {
_MyInitEvent();
}
#override
Stream<ExampleState> mapEventToState(
ExampleEvent event,
) async* {
if (event is _MyInitEvent) {
await for (bool b in api.myStream) {
if (b) yield SomeState;
}
}
}
Build another block that encapsulate your stream of bytes.
You can make two events (ByteRead and ByteConsume) and two states (ByteWaiting and ByteAvailable).
Byteread and ByteAvailable should have a _byte field for storing data. Your bytebloc has a subscriber listening the stream and every time it reads a byte it fires a ByteRead event.
You should also add to the bloc a consume() method that gives the last byte readed and fires the ByteConsume event.
The two states and events are mutual:
you start in bytewaiting and every time you have a ByteRead you change to ByteAvailable and
every time you have a ByteConsume you change back to ByteWaiting.