I've recently faced weird bug in production project: making some request to server triggers socket event in the same bloc. But returning state from request is not finished yet. Thus I faced situation when event from socket fires and I get new state in page, but state from request never reaches page.
I've managed to fix it by adding delay for events in mapEventToState().
if (event.withDelay) {
await Future.delayed(Duration(milliseconds: 50));
}
Adding this code allows to get two states even if they were triggered close to each one.
flutter_bloc version is 5.0.0, this behaviour can be reproduced in example app code attached lower. It simulates adding another event to bloc right after fake server request is done. App has two buttons: make fake request with delays for event or without them. Every event triggers fake socket event. Event from request returns double value in state and event from socket returns color value in state.
I can't understand if its bug or default behaviour for Bloc.
```import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State
double number;
Color color;
final bloc = MyBloc(InitState());
final states =
@override
void dispose() {
bloc.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: BlocBuilder
bloc: bloc,
builder: (BuildContext context, state) {
states.insert(0, state.runtimeType.toString());
print('Building state: ${state.runtimeType}');
if (state is DataFromRequestState) {
number = state.number;
}
if (state is DataFromSocketState) {
color = state.color;
}
return Container(
width: double.infinity,
height: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Number from request:',
),
Container(
color: color ?? Colors.transparent,
child: Text(
'$number',
style: Theme.of(context).textTheme.headline4,
),
),
Expanded(
child: states.isEmpty
? SizedBox()
: ListView.builder(
itemCount: states.length,
itemBuilder: (BuildContext context, int index) {
return Text('${states[index]}');
}),
),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
RaisedButton(
child: Text('With delay'),
onPressed: () {
bloc.add(NumberRequestEvent(true));
},
),
RaisedButton(
child: Text('Without delay'),
onPressed: () {
bloc.add(NumberRequestEvent(false));
},
),
],
),
],
),
);
},
),
);
}
}
class MyBloc extends Bloc
MyBloc(initialState) : super(initialState);
@override
Stream
if (event.withDelay) {
await Future.delayed(Duration(milliseconds: 50));
}
switch (event.runtimeType) {
case NumberRequestEvent:
yield await _fakeRestRequest(event);
break;
case SocketEvent:
yield DataFromSocketState(Color.fromRGBO(
Random().nextInt(256),
Random().nextInt(256),
Random().nextInt(256),
1.0,
));
break;
}
}
Future
Future.delayed(Duration(milliseconds: 300)).then((value) {
add(SocketEvent(event.withDelay));
return DataFromRequestState(Random().nextDouble());
});
}
abstract class BaseEvent {
final bool withDelay;
BaseEvent(this.withDelay);
}
class SocketEvent extends BaseEvent {
SocketEvent(bool withDelay) : super(withDelay);
}
class NumberRequestEvent extends BaseEvent {
NumberRequestEvent(bool withDelay) : super(withDelay);
}
abstract class BaseState {}
class InitState extends BaseState {}
class DataFromSocketState extends BaseState {
final Color color;
DataFromSocketState(this.color);
}
class DataFromRequestState extends BaseState {
final double number;
DataFromRequestState(this.number);
}
```
Hi @Mizhek 馃憢
Doesn't matter how close your events are when added, bloc will process all of them in the order they were added.
If a state is not emitted it's because the new state is value equal to the previous one, so you might wanna have a look at that.
Hi @Mizhek 馃憢
Doesn't matter how close your events are when added, bloc will process all of them in the order they were added.
If a state is not emitted it's because the new state is value equal to the previous one, so you might wanna have a look at that.
Unfortunately using equatable package doesn't help.
I've tried using it with state classes:
abstract class BaseState extends Equatable {
@override
bool get stringify => true;
}
class InitState extends BaseState {
@override
List<Object> get props => [];
}
class DataFromSocketState extends BaseState {
final Color color;
DataFromSocketState(this.color);
@override
List<Object> get props => [color];
}
class DataFromRequestState extends BaseState {
final double number;
DataFromRequestState(this.number);
@override
List<Object> get props => [number];
}
Can you please create a small github repo with a minimal reproduction of your issue and detail exactly what your intent is and what the expected result should be ? 馃憤
https://github.com/Mizhek/bloc_sample_app here is repo with reproduction of problem.
My intent is to receive all states when socket event fires during REST request. Unfortunately I can't recreate app with real socket io in bloc, but sample app completely recreates problem I've faced recently in production app.
@Mizhek this is most likely related to a currently existing issue that's being worked on.
We'll get back with details once that's solved.
Thanks for your patience 馃憤
@RollyPeres nice. I will be waiting for updates. Thanks for good support.
I apologize it took so long to get back to you @Mizhek .
I took the liberty to alter your example in a more realistic one which you can find in this PR. I assumed you have a socket giving you realtime data through a stream and you'd also want to be able to process a different event which gives you some asynchrounous data back.
This works without overriding transformEvents too, but it will treat events sequentially.
Let me know what you think and if this is around the lines of what you're trying to achieve. 馃憤
Thanks for answer. I'm not sure your approach will help to resolve problem. I used https://pub.dev/packages/socket_io_client package and it seems that there is no a stream with events. Instead you need to set event handler for every event separately.
Maybe problem is that I set these handlers inside page bloc and this causes strange behaviour (in your PR socket steam is top-level variable). Anyway fix was done on backend by not emitting socket event to user who triggered it.
I'll close this issue. Thanks for great package and good support!
I see, well you can either add bloc events in your handlers and transform events with flatMap or you can use a StreamController to add incoming data to it and then listen to it for actual processing.
I used top level socket stream for the sake of simplicity but it would normally come from a repository.
Glad you found a solution. 馃憤
Most helpful comment
I apologize it took so long to get back to you @Mizhek .
I took the liberty to alter your example in a more realistic one which you can find in this PR. I assumed you have a socket giving you realtime data through a stream and you'd also want to be able to process a different event which gives you some asynchrounous data back.
This works without overriding
transformEventstoo, but it will treat events sequentially.Let me know what you think and if this is around the lines of what you're trying to achieve. 馃憤