felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.79k stars 3.39k forks source link

A Question About the Implementation of the State Stream #1189

Closed psygo closed 4 years ago

psygo commented 4 years ago

I'm not that good at reactive programming, so this question might be a bit basic. But maybe it can help improving the docs if others share the same doubts.

As far as I understand, the mapEventToState method returns a Stream which transforms an event internally to emit a state. However, if a new event enters the BLoC, due to this functional stream creation pattern, a new Stream would be created/returned and, I guess, a new subscription to this new Stream would need to be created. Am I right in this interpretation so far?

Anyway, my real question is: why can't this be accomplished via a combination of StreamController(s) and the .add() method? The mapEventToState would then not return a Stream of states but only states, and then be called internally in the add method for the StreamController or the Sink of the BLoC states itself. If this is possible, I think it would greatly simplify the developer's life since you wouldn't need to worry about yield* and whatever asynchronous complications may arise.

I know I'm probably 99% wrong, but I would bet only 1% of new developers to this library won't have these questions ever, so maybe this could be included in the architecture or streams sections of the documentation.

felangel commented 4 years ago

Hi @psygo ๐Ÿ‘‹ Thanks for opening an issue!

A bloc is essentially a special type of StreamController which allows you to define a transformation function (mapEventToState). With a StreamController the input type has to match the output type.

I highly encourage you to try to accomplish the same thing with StreamControllers (it's a good learning exercise) and you should see that you'll need multiple controllers to accomplish the same thing.

If you have questions about yield* I'm happy to try to answer them and improve the docs to make it easier for you as well as the rest of the community.

Let me know if that helps ๐Ÿ‘

psygo commented 4 years ago

A bloc is essentially a special type of StreamController which allows you to define a transformation function (mapEventToState).

Knowing that is already really neat. That info could be placed below the Streams Section of the docs for example, I think it would be useful to bring BLoC into "familiar" reactive terms, but that's only my opinion...

I can see from the source code that the BLoC abstract class is extending and implementing the two important reactive components in Dart also, Stream and Sink.

https://github.com/felangel/bloc/blob/c2b4a8ecdff01b5e3193c0f88ccc9576bdc2e81e/packages/bloc/lib/src/bloc.dart#L39


I think it would also be very useful to have some sort of basic sketched UML diagram of the library in the docs, wouldn't it? It would also increase the chances of contributions drastically — is there a package to do this programmatically? Have I skipped this somewhere in the documentation?


With a StreamController the input type has to match the output type.

True, and I admit I hadn't paid that much attention to this point. But couldn't you use a StreamTransformer to not only deal with mapEventToState but also change the output type? Maybe something like this:

void main() {
  final Stream<int> intStream = streamOfInts(10); // would print 0, 1, 2, ..., 10

  final StreamTransformer<int, String> streamTransformer = 
      StreamTransformer<int, String>
          .fromHandlers(handleData: (int event, EventSink<String> output) 
              => output.add('number: ' + event.toString()));

  final Stream<String> stringStream = intStream.transform<String>(streamTransformer);

  stringStream.listen(print); // prints number: 0, number: 1, number: 2, ..., number: 10
}

Stream<int> streamOfInts(int intTotal) async* {
  for (int i = 0; i <= intTotal; i++) {
    yield i;
  }
}

It would be even easier with a .map() actually:

final Stream<String> stringStream = 
    intStream.map<String>((int i) => 'number: ${i.toString()}');
narcodico commented 4 years ago

Hi @psygo ๐Ÿ‘‹

If you take a closer look at the bloc's implementation you'll notice it uses two different StreamControllers, one for the incoming events and one for the states being outputted. That allows for advanced usage like applying operators on those streams to change how the events/states are being handled(debouncing, throttling, cancelling, etc).

felangel commented 4 years ago

Another thing to add to ๐Ÿ‘† is it is designed to be able to easily emit multiple states for a single event.

if (event is WeatherRequested) {
  yield WeatherLoadInProgress();
  final weather = await _getWeather();
  yield WeatherLoadSuccess(weather);
}

I'm not sure what the desired outcome of this issue is. If you feel the documentation is lacking/unclear I welcome all pull requests ๐Ÿ˜„

psygo commented 4 years ago

I'm not sure what the desired outcome of this issue is.

I intended for this issue to be a clarifying discussion about how the BLoC package works in the background. Maybe that would result in some later improvements to the docs as a side effect. At any rate, if anyone has the same questions in the future, they will hopefully find this issue.

Now it is clearer to me why you chose it to be the way it is. It seems like it would not have been possible to achieve more complicated features with only, say, 2 StreamControllers and a Sink. Being able to emit more than one state per event might be all that was necessary to counterargue my original thought of having the mapEventToState return a state and not a Stream of states — though I bet there's more to it.

I still think that some of what we discussed here could improve the docs — like having UML-like diagrams and mentioning the benefits of having mapEventToState be a Stream — and I'll see what I can do to create a pull request if you're interested.

I don't think I have much more to add, so, if you wish, we can close this issue.

felangel commented 4 years ago

Will keep this open to track the documentation request ๐Ÿ‘