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.
Related
I was going though GSkinner's flutter_vignattes codebase, in one of the functions there was an empty await for a Future
Future<void> _reset() async {
// Wait until next event loop to advance animation and call setState or flutter will yell at you
await Future<void>.value();
_controller.forward(from: 1.0 - _percentage * 0.83);
if (_isLoading) {
setState(() {
_model = BasketballGameModel.randomize();
});
}
_isLoading = false;
}
I understand how promises are sent to micro-task queue in JS (assuming same happens in Dart), but not quite able to understand the reason provided in the comment here i.e.,
// Wait until next event loop to advance animation and call setState or flutter will yell at you
Really appreciate if someone can provide a deeper insight into this. This is the particular line in codebase i am referring to.
https://github.com/gskinnerTeam/flutter_vignettes/blob/0ccc72c5b87b5ab6ba2dee9eff76f48ce2fadec8/vignettes/basketball_ptr/lib/demo.dart#L149
Future<void> function() {}
Defines an asynchronous function that ultimately returns nothing but can notify callers when it eventually completes. Also see: What's the difference between returning void vs returning Future?
Or You can learn from this https://github.com/dart-lang/sdk/issues/33415
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 found it a bit confused with the concept of Future and async programming.
By definition, Future is a type that async function will return in Future.
The purpose is that we want the program to keep running while it is still waiting for the result of the async function.
What I dont understand is that, every often/always, I saw people using async with await which stop proceeding the program until it gets the result from async function called.
Arent we come to full circle? At first, async comes in for the situation that we dont want to wait for program taking time. But now, we use async with await in which we wait until the result is there
It is not always necessary to use await with a future. await can be used if you want to do further processing with the data.
Example:
Future<int> _getInt()async{
Future.delay(Duration(seconds:3)); //simulating network delay
return 7;
}
void _add() async{
int res = await _getInt() + 10; //need to await because we are going to use a future variable
_putInt(res); //not nesscary to await if you don't want to handle the response
/* Ex: var result = await _putInt(); // if you want to handel the response
if (result.statusCode==200){
// handle success
}else{
// handle error
}*/
}
Future _putInt(int number)async{
var res = await http.post('url',body:{'data':number});
return res;
}
Well, your institution is right, we use Future and async as it is nonblocking and follows an event loop approach, meaning there is a callback to the Future when it is ready to execute.
Coming to your point, I have done this myself a lot of time. And neither approach is wrong.
But when it comes to Flutter, it is in your best interest that you don't do anything else when running an async function because dart is single-threaded.
This can be blocking sometimes depending on the work of the function.
This can also be simply for UX as some data would be critical to your application and you shouldn't allow the user to do anything else until it is loaded.
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
}
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.