I will use simplified version than that of my project but feel free to ask for more code if necessary. This may be more of an architectural question.
I have a service (interface) FooService
in my app which exposes a stream fooStream
. I have this service as a member of the bloc class MyBloc
.
Now the state of the bloc MyBlocState
contains isProcessing
to indicate if the bloc is processing and also it contains fooStream
field which expose the service's version of fooStream
to the UI layer (so there are 3 layers: service => bloc => UI).
The problem now is that when I want to listen to this service stream in my UI, I am forced to use a StreamBuilder
which itself will be wrapped inside a BlocBuilder
. And this will become very messy in a large app. So whats the best way to represent the fooStream
inside the bloc's state? Can I just listen to the stream inside the bloc and add an event to the bloc whenever a fooStream
event arrives? Wouldn't this be a bad side effect where state will be updated in a messy way and changes in state can't be tracked anymore?
Sample code:
class FooService{
Stream fooStream;// the stream is exposed here
...
}
class MyBloc extends ... {
FooService myService = ...;//the service is used here as a member
...
}
class MyBlocState extends ... {
Stream? fooStream;//this value will hold the stream exposed from the service and it may be initialized in an init event in the bloc for example
...
}
and now in the UI I have to do this ugly thing (ugly because now I am listening to the state from 2 places, from the bloc and from the stream, which in my opinion defeats the purpose of the bloc as the sole entry point to change the state and maintain it in the UI/app):
Widget build(BuildContext context){
return BlocBuilder<MyBloc,MyBlocState>(
builder: (context, myBlocState) {
if(myBlocState is Loading){
return CircularProgressIndicator();
}
if(myBlocState is Error){
return ErrorWidget();
}
if(myBlocState is SomeState){
return StreamBuilder(//I need to wrap the StreamBuilder inside the BlocBuilder because the stream will be null when the app launches but then it will have a value which is fooStream when I call init in the bloc
stream: myBlocState.fooStream,
builder: (context, snapshot) {
if(snapshot.hasData){
return SomeWidget();
}
else{
return SomeOtherWidget();
}
},
);
}
},
)
}
CodePudding user response:
The UI should ideally just be responding to state changes in your bloc so I highly recommend subscribing to the stream within the bloc and emitting states in response to data from the stream. You can check out the timer example for a reference which manages the subscription in order to pause/resume. In simple cases, you can use emit.forEach
or emit.onEach
in bloc >=7.2.0 which means you don't need to manually maintain a StreamSubscription
.
You can see an example of emit.onEach
in the flutter bloc with stream example and emit.forEach
would look very similar:
class TickerBloc extends Bloc<TickerEvent, TickerState> {
TickerBloc(Ticker ticker) : super(TickerInitial()) {
on<TickerStarted>(
(event, emit) async {
await emit.forEach<int>(
ticker.tick(),
onData: (tick) => TickerTick(tick),
);
},
transformer: restartable(),
);
}
}