Bloc: Is possible to get previous state

Created on 17 Jun 2020  路  7Comments  路  Source: felangel/bloc

Hi,
I think not all state field are needed to expose constantly, and pass the same value for this kind of field is little bit waste.
Is that possible to get or filter previous state (from initial state to the current state) inside mapEventToState?

e.g. Like lastWhere() method function to filter the stream event, but not need to wait the stream finish.

question

Most helpful comment

@pqsky7 that looks like a good approach for your use case. One optimization I'd personally do is to only save the latest state of each type you're interested in, since it's pointless to store all those states when you'll only need the latest one. 馃憤

All 7 comments

Hi @pqsky7 馃憢

If you're interested in listening to a bloc's state and you're only interested in certain state you could filter it with .ofType<YourStateType>().

Other thing to consider here is that if you feel you have a scenario where you're only interested in certain state and you want to ignore the rest, then you might be in a situation where you could be better off extracting that one into a separate bloc.

If I misunderstood your issue feel free to give additional context 馃憤

Hi @RollyPeres

Thanks for you quickly response!

I am new to bloc, I have heard bloc not only can manage current state but also mange all previous state, so it can easily revert the change or fetch the previous state, am I correct?

In my case, I am trying to do the email link sign-in via bloc, so I need save the emailAddress in state after the link send successfully, and when the system detect the link, it will try to sign-in via the link with the saved emailAddress.

It means, emailAddress only saved after the link is successfully sent and then it need expose once to notify user to check link in that emailAddress. and then, the emailAddress will not be used until the app detect the link is come. The emailAddress field have to pass with the same value for every added event.

Thus, I am thinking, if bloc can find previously last EmailSentState, I don't need to pass the emailAddress to every state. I can just find the emailAddress in a specific previous state.

Hi @pqsky7 bloc does not keep a history of transitions but you can easily implement your own bloc that does this if you'd like. In my opinion, it's probably cleaner to keep the email as part of the state in this case because it does reflect the state of the feature at that particular point in time. Hope that helps 馃憤

You don't have to pass your email address to each event, you only need to pass it to the event which saves it on the bloc state. You can then access it from there. In case you want to use separate state classes you need to make sure you're exposing it to all states that cares about it. You can use BlocListener to react to different states or values present in the state and trigger your sign in or whatever logic you might need to run.

Thanks for clarifying that @RollyPeres 馃檹
@pqsky7 I highly recommend checking out the form validation example for inspiration 馃憤

Closing for now but feel free to comment with additional questions and I'm happy to continue the conversation 馃槃

Thanks for your reply, it really helps me to solve the problem. @felangel @RollyPeres .

I have checked and learned examples and tutorials on bloclibrary.dev, these materials are helpful!

I desire to understand the stream and Bloc more deeply, so I am trying to apply the bloc based on my understanding and thinking.

What I am trying to do is making each state only contains the fields that are needed/used in UI since not all of the fields are constantly needed, and they only be used in some specific cases/states.

For example, the savedEmailAddress only needs when the email is successfully sent or the link is detected. In a tutorial manner, many state classes need the savedEmailAddress field, and it may involve passing the savedEmailAddress with the same value over the state to state.

So, I am trying to decouple different state class and make they are simple and easier to build since they only care the fields they need and they do not need to figure out the relations between data flow (they do not need to input/output/pass unused data).

Please have a look at following full bloc code, there are many different state class and each class is really simple. As the fetch needed data from the previous state manner applied, other state class does not need to input savedEmailAddress to keep consistency.

I hope this is a proper manner to use bloc, if anything wrong, please let me know.

Key code

class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
  ...
  final _previousStates = <ExampleState>[];

  @override
  void onTransition(Transition<ExampleEvent, ExampleState> transition) {
    //ignore InitialExampleState only
    _previousStates.add(transition.nextState);
    super.onTransition(transition);
  }

  @override
  ExampleState get initialState => InitialExampleState();

  @override
  Stream<ExampleState> mapEventToState(ExampleEvent event) async* {
    if (event is EmailAddressChanged) {
      ...
    } else if (event is FormSubmit) {
      ...
    } else if (event is LinkDetected) {
      yield ExampleInProcess();
      // fetch emailAddress in previous state
      final emailAddress = _previousStates.whereType<ExampleEmailSentSuccess>().last.emailAddress;
      final result = _repository.signInWithLink(emailAddress: emailAddress, link: event.link);
      ...
    }
  }
}

Full Bloc Code

class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
  ExampleBloc(this._repository);
  final ExampleRepository _repository;
  final _previousStates = <ExampleState>[];

  @override
  void onTransition(Transition<ExampleEvent, ExampleState> transition) {
    //ignore InitialExampleState only
    _previousStates.add(transition.nextState);
    super.onTransition(transition);
  }

  @override
  ExampleState get initialState => InitialExampleState();

  @override
  Stream<ExampleState> mapEventToState(ExampleEvent event) async* {
    if (event is EmailAddressChanged) {
      if(state is ExampleValidationOn){
        yield ExampleValidationOn(event.emailAddress);
      }else{
        yield ExampleValidationOff();
      }
    } else if (event is FormSubmit) {
      if(event.emailAddress.isValid()){
        final result = await _repository.sendLink(event.emailAddress);
        if (result != null){
          yield ExampleEmailSentFail();
        } else {
          yield ExampleEmailSentSuccess(event.emailAddress);
        }
      }else{
        yield ExampleValidationOn(event.emailAddress);
      }
    } else if (event is LinkDetected) {
      yield ExampleInProcess();
      final emailAddress = _previousStates.whereType<ExampleEmailSentSuccess>().last.emailAddress;
      final result = _repository.signInWithLink(emailAddress: emailAddress, link: event.link);
      if(result != null){
        yield ExampleSignInFail();
      }else{
        yield ExampleSignInSuccess();
      }
    }
  }
}

@pqsky7 that looks like a good approach for your use case. One optimization I'd personally do is to only save the latest state of each type you're interested in, since it's pointless to store all those states when you'll only need the latest one. 馃憤

Was this page helpful?
0 / 5 - 0 ratings