Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal?] 'Stream<State> get state' is confusing #558

Closed
bernaferrari opened this issue Oct 9, 2019 · 43 comments
Closed

[Proposal?] 'Stream<State> get state' is confusing #558

bernaferrari opened this issue Oct 9, 2019 · 43 comments
Assignees
Labels
deprecation A public API is being deprecated enhancement New feature or request
Projects

Comments

@bernaferrari
Copy link
Contributor

bernaferrari commented Oct 9, 2019

I've been developing a relatively large app using Bloc and I think my top 1 issue with Bloc is with state. No, not what you are thinking. I mean, the state variable/getter method.
image

It has happened to me once, then again, then again, then today. I always cast it, like (currentState as ContrastLoadedState) and I keep trying state before realising my mistake. Just check the screenshot above. It makes no sense for me that currentState returns a State while state returns a Stream<State>.

In BlocBuilder you get a state:

In your own sample you use state = currentState:

I think you could change state to stateStream and currentState to state or maybe just rename state to stateStream, that way there wouldn't be a state and IntelliJ/VSCode would be smart enough to just suggest currentState.

I'm not sure if this has been a source of bugs/confusion in the past, but it has been a great frustration for me.

@felangel felangel self-assigned this Oct 9, 2019
@felangel felangel added enhancement candidate Candidate for enhancement but additional research is needed feedback wanted Looking for feedback from the community labels Oct 9, 2019
@felangel
Copy link
Owner

felangel commented Oct 9, 2019

Hi @bernaferrari 👋
Thanks for opening an issue!

I'm glad you brought this up and I agree the naming should be improved. What are your thoughts on renaming state to states and leaving currentState as is?

@felangel felangel added the waiting for response Waiting for follow up label Oct 9, 2019
@felangel felangel added this to In progress in bloc Oct 9, 2019
@bernaferrari
Copy link
Contributor Author

I don't know, I guess it would also be a good change? I personally would probably change it to stateStream or something like this, to keep in track with _stateSubject which already exists, but stateStream is not a perfect name, since it's not obvious what it should be doing from its name, so I guess states would also work fine.

@felangel
Copy link
Owner

felangel commented Oct 9, 2019

@bernaferrari I've discussed this with the team and we're currently split between:

  • bloc.states.listen
  • bloc.stream.listen
  • bloc.stateStream.listen

What are your thoughts?

@felangel felangel mentioned this issue Oct 10, 2019
3 tasks
@felangel felangel added deprecation A public API is being deprecated in progress labels Oct 10, 2019
@basketball-ico
Copy link

@felangel
I prefer bloc.states

Bloc: Takes a [Stream] of [Event]s as input and transforms them into a [Stream] of [State]s as output.

@felangel
Copy link
Owner

@basketball-ico can you elaborate on why you prefer it over stream? Thanks for the input 🙏

@felangel felangel removed the enhancement candidate Candidate for enhancement but additional research is needed label Oct 10, 2019
@bigworld12
Copy link

stateStream is more intuitive in my opinion, since the name infers the type

@bernaferrari
Copy link
Contributor Author

bernaferrari commented Oct 10, 2019

I would vote either for states or stateStream. I like the bonus of having the type in the name, but you kind of loose the functionality. It's not that easy to tell what it does. I searched for other usages of the stream<> api and I found someone else also using a variable name in plural when referring to streams. So either way, either of the both are better than state.

Edit: my biggest issue, which made me open this issue, was writability. If tomorrow I need the stateStream (or the states), will it be trivial? Should I know it returns a stream? Is code easy to read? I think I've never used the stateStream method. You are more qualified than me to answer this, read the changes in the PR and think if the naming change makes sense.

@felangel
Copy link
Owner

felangel commented Oct 10, 2019

@bernaferrari thanks for the additional input. I agree that stateStream is the most descriptive but it's also the most clunky/verbose of the options. In most cases, you would only use the bloc state stream directly if you're subscribing to the stream manually or if you're unit testing the bloc.

Directly Subscribing

// Current
bloc.state.listen((state) { ... });

// Option 1
bloc.states.listen((state) { ... });

// Option 2
bloc.stateStream.listen((state) { ... });

// Option 3
bloc.stream.listen((state) { ... });

Testing

// Current
expectLater(bloc.state, emitsInOrder([ ... ]));

// Option 1
expectLater(bloc.states, emitsInOrder([ ... ]));

// Option 2
expectLater(bloc.stateStream, emitsInOrder([ ... ]));

// Option 3
expectLater(bloc.stream, emitsInOrder([ ... ]));

Personally I think when subscribing it reads a bit weird to have bloc.states.listen and when testing I think it reads weird to have bloc.states, emitsInOrder(...).

The only reason I am advocating for stream is because there should be no other public stream other than the stream of states (by definition of the bloc) so it's redundant to have stateStream.

With all that said, naming is hard and I can be convinced either way haha. Thoughts? @bigworld12 @basketball-ico @bernaferrari

@felangel felangel pinned this issue Oct 10, 2019
@tenhobi
Copy link
Collaborator

tenhobi commented Oct 10, 2019

I like the bloc.stateStream and bloc.stream. bloc.states might be a bit misleading. I would vote for bloc.stream, because it is short and obvious what it does. 👍

@basketball-ico
Copy link

@felangel
I like bloc.states because it is a bloc, you know the output is a Stream, so it's redundant to have bloc.stateStream. (for bloc class)

Sometimes what you're returning is different representations of the same thing, or different kinds of things, or whatever. In that case there does seem to be a convention to choose a plural noun that represents the type of things being returned in the stream.
https://stackoverflow.com/a/28805669

and for me is more easy read

bloc.states.listen((state){...});

than this

bloc.stream.listen((state){...});

thank you :)

@ThinkDigitalSoftware
Copy link

ThinkDigitalSoftware commented Oct 10, 2019

I like stream. Because if I were naming variables without types, state would be of type BlocState, states would be of type List<BlocState> and stream would be a Stream out whatever the bloc outputs, which would be Stream<BlocState>. stream also aligns with the StreamController API as a stream of the bloc's output type.
Also, you're not providing multiples states with the stream, you're providing a Stream of type BlocState

@audkar
Copy link

audkar commented Oct 11, 2019

Other example which matches this case:

class House {
  final Provider<Window> _window = <...>;
  //0
  Provider<Window> get window => _window;
  //1
  Provider<Window> get windows => _window;
  //2
  Provider<Window> get windowProvider => _window;
  //3
  Provider<Window> get provider => _window;

  final Provider<Door> _door = <...>;
  //0
  Provider<Door> get door => _door;
  //1
  Provider<Door> get doors => _door;
  //2
  Provider<Door> get doorProvider => _door;
  //3
  Provider<Door> get provider2 => _door;
}

OOP uses type of entities for method name which it returns. Not container type in which these entities are wrapped. What will be when class have more methods which return values in same type container? Name them stream2 and stream3?

IMHO 3rd (//3) suggested variant is worst in all cases.

2nd variant (plural form) also sounds strange form me. Stream is a stream. It emits one item at a time.

I personally do not have any problem with current variant (//0) or 3rd one (//3).

@felangel
Copy link
Owner

@audkar can you clarify? You said you think the 3rd variant is worst in all cases but in your last sentence you say you don’t have a problem with the current variant or the 3rd one.

@audkar
Copy link

audkar commented Oct 11, 2019

You said you think the 3rd variant is worst in all cases

brainfarted, sorry. There was proposed three new variants. And 3rd one doesn't looks ok to me.

@felangel
Copy link
Owner

You said you think the 3rd variant is worst in all cases

brainfarted, sorry. There was proposed three new variants. And 3rd one doesn't looks ok to me.

So you prefer to leave it as is and don’t like bloc.stream?

@audkar
Copy link

audkar commented Oct 11, 2019

So you prefer to leave it as is and don’t like bloc.stream

Yes. I would have no idea what this final stream = bloc.stream returns without looking at implementation details or IDE item type.

@rrousselGit
Copy link

rrousselGit commented Oct 12, 2019

I by far prefer the current names to all the proposed names.

  • stream: that doesn't mean anything. Dart is a typed language. Naming a variable "stream" doesn't bring any extra useful information that the type system doesn't tell you already.

    It's like naming a String "string" or "str". Sure, that's correct, but not helpful at all.

  • renaming currentState to state: we're losing meaningful information -> it can change over time.

  • state -> states: I don't like it either. It's not a list of state, but a state that changes over time. The plural doesn't mean anything here.
    And having both state and states in the same object makes it difficult to read.


Instead, what about removing state entirely?

Instead of:

abstract class Bloc<S> {
  Stream<S> get stream;
  S get currentState;
}

we can have Bloc implement Stream.

abstract class Bloc<S> implements Stream<S> {
  StreamSubscription<S> listen(void listener(S));

  S get currentState;
}

Alternatively, it’d be easier to just expose a “listens” method without implementing Stream and to expose a asStream method

@felangel
Copy link
Owner

Thanks for the input @rrousselGit.

So you’d prefer:
bloc.listen((state) { ... })

And in tests:
expectLater(bloc, emitsInOrder([ ... ]));

We discussed this briefly within my team and I personally like it but there were concerns about not knowing what you’re listening to. I actually like the way it reads a lot better than the others 👍

@ThinkDigitalSoftware
Copy link

ThinkDigitalSoftware commented Oct 12, 2019

I support @rrousselGit's ideas, but I feel that a getter named stream and listening to the bloc provides the same amount of information. At least stream indicates a type while bloc doesn't indicate that its listenable. However, we all got over a small learning curve when learning this library, so we can expect people not to know what to do with something until they learn it. asStream is probably my favorite suggestion. And in the docs, /// returns the output of this bloc as a stream of states.
Easy peasy

@brianegan
Copy link

tl;dr: I'd go with state instead of currentState and make the class implement Stream<State>. If ya wanna get crazy, also implement Sink<Event>.

Longer answer:

state vs currentState: I actually made the same mistake as the original author when trying out this library, and don't quite share the same concerns as Remi. I think it'd be a nice change.

When it comes to the name of the stream: In the past, I've created a class very similar to the bloc class, and I actually went with this particular interface (I called mine a Presenter or ReactivePresenter):

class Presenter<State, Action> extends StreamView<State> implements Sink<Action> {

In terms of the Bloc class, it would look like this:

class Bloc<State, Event> extends StreamView<State> implements Sink<Event> {

From one perspective, you could think of the Bloc class in terms of core dart async primitives: It is a class that exposes a Stream<State> and allows you to input values through a Sink<Event>. You also need to dispose of Blocs, and the Sink interface enforces this through the close method and the dart analyzer can warn you if you forget to close Sinks!

In practice, this means you'd work with the bloc class like so:

myBloc.add(IncrementEvent()); // Add events to the Sink instead of dispatching events
myBloc.listen(print); // Print state changes
myBloc.state; // Gets current state
myBloc.close(); // Cleans up the bloc, new name for `dispose`

Dunno if that helps out at all, haha, but I really think it makes sense to think of these classes in terms of those core primitives. Maybe I'm crazy tho :P

@felangel
Copy link
Owner

@ThinkDigitalSoftware yup 👍

@brianegan
Copy link

As a heads up, changing currentState -> state would need to be a breaking change, but I think implementing the Sink<Event> interface could be done gradually. You could start by implementing the Sink interface methods add and close while keeping and @deprecate dispatch and dispose so folks have time to adjust their own code.

@audkar
Copy link

audkar commented Oct 13, 2019

At what state Bloc object would be if client call bloc.sink.close()?

@brianegan
Copy link

@audkar It would be the equivalent to calling dispose

@tenhobi
Copy link
Collaborator

tenhobi commented Oct 13, 2019

Also, it would be nice with this change (and something else maybe) make finally a stable release v1.0.0.

@felangel
Copy link
Owner

@rrousselGit @jorgecoca @bernaferrari thoughts on #572?

@ThinkDigitalSoftware
Copy link

What will be done with the stream.map function? Since we have mapEventToState already

@felangel
Copy link
Owner

@ThinkDigitalSoftware what do you mean? With #572 you'd be able to do bloc.map(...) but as you pointed out in majority of cases you'd probably rely on mapEventToState exclusively.

With the current implementation you can still map on the state stream like bloc.state.map(...).

@ThinkDigitalSoftware
Copy link

So we'll have docs on when those will run if it's overridden?

@ThinkDigitalSoftware
Copy link

Shouldn't this just implement StreamController? To abstract away the sink?

@felangel
Copy link
Owner

@ThinkDigitalSoftware we can add that to the docs but it's not specific to the proposed implementation. We were going to implement Sink<Event> in a separate PR to expose add and close in place of dispatch and dispose.

@brianegan
Copy link

brianegan commented Oct 13, 2019

Implementing StreamController is also an interesting idea, but one reason I didn't suggest that route: it means the Bloc class would need to implement EventSink<Event> instead of Sink<Event>. In practice, that would require the Bloc class implement the addError method in addition to add and close.

IMO, it doesn't make much sense for blocs to have an addError method on them.

@felangel
Copy link
Owner

Also, if we implemented StreamController<T> wouldn't we be limited by T in terms of what we can add and emit?

@rrousselGit
Copy link

Yup. And the bloc instead won't be a stream anymore. We'd need bloc.stream.

@ThinkDigitalSoftware
Copy link

Makes sense, however I do believe we already limit what we can add and emit by the current generics on the Bloc class.

@felangel
Copy link
Owner

felangel commented Oct 13, 2019

@ThinkDigitalSoftware but if you implement StreamController<T> you would only be able to add type T and emit type T which would force your events and states to inherit from some common base class.

@ThinkDigitalSoftware
Copy link

ThinkDigitalSoftware commented Oct 13, 2019 via email

@felangel felangel added enhancement New feature or request and removed waiting for response Waiting for follow up in progress feedback wanted Looking for feedback from the community labels Oct 14, 2019
@felangel felangel moved this from In progress to Done in bloc Oct 15, 2019
@felangel
Copy link
Owner

felangel commented Oct 15, 2019

Implemented by #572, #575 and published in bloc v0.16.0 🎉

Edit: Now included in flutter_bloc v0.22.0 and angular_bloc v0.11.0

@bernaferrari
Copy link
Contributor Author

Awesome!! Thanks!! Never expected that small issue I had to get such discussion and at the end a very nice solution. Now please send us some BMW stickers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
deprecation A public API is being deprecated enhancement New feature or request
Projects
bloc
  
Done
Development

No branches or pull requests

9 participants