Bloc: Please add an example of reacting to a Stream from a repository in a Bloc

Created on 29 Mar 2019  路  11Comments  路  Source: felangel/bloc

In speaking with @jorgecoca, he discussed an aspect of the repository pattern given in the examples that I would like to see expanded upon. He mentioned a strategy wherein a repository can produce a Stream that can be observed within a Bloc. This seems like an interesting pattern that would enable several interesting behaviors.

One aspect of Redux that I have utilized in the past was a way for multiple reducers to observe a single Action and update state respectively. Combining this with middleware and multiple parts of my state graph can react to side effects and keep data in sync in various parts of my Store. This is a powerful way to coordinate functionality without having explicit wiring between concrete components.

I think that the same sort of ends may be able to be achieved with this repository pattern and I'd like to see this explored a bit more.

Additional context
Let's suppose I've got a UserBloc which maintains my user state. It's got some expected capabilities for updating and accessing user data.

Let's suppose I've got another Bloc called TimeClockBloc which depends on UserBloc. Let's also suppose that there is an operation within the domain of TimeClockBloc which has a side effect that updates something about the User.

I would want to be sure that UserBloc gets updated with the latest User state _as well as_ whatever TimeClock state that got updated by said operation.

Perhaps the strategy would be to have a shared UserRepository and then have some Stream that UserBloc observes push a new UserState onto the stream?

Also, suppose I've got a live subscription to a backend service (Firebase, or GraphQL, WebSocket, etc) and I want a way to interface with this. Would the repository pattern be a good fit for this interface?


I realize this request for example is a bit vague. I've been happily building with Redux in our app for the past year and have had some good success. Based on a tweet from @jorgecoca I decided to experiment with this package because I think it does a good job at consolidating Redux concepts into places that are easier to reason about (fewer functions, fewer files, more concise data flows). So mostly I'd like to see an expanded example of how the repository pattern might be used when a Bloc is reacting to a stream coming from a repository.

Thank you!

example

Most helpful comment

Yep, much nicer :+1:
Thanks.

P.S.: "Future

All 11 comments

Hey @dustin-graham 馃憢

Great question! I think this is totally do-able and @jorgecoca said he'd put together an example over the next few days 馃憤

Sorry, do you talk about how to solve this or am I misunderstanding something?

I added this example project for reference.

After doing this example, we believe we can simplify the API for consuming streams; we will try to come up with a draft in the near future.

Closing this for now.

@jpablogn let us know if you have any questions/suggestions for how this can be improved 馃憤

Sorry for my ingnoranze, but:

flutter_bloc_stream

Am I right?

@jpablogn I'm not sure I understand your question. Can you please elaborate? I believe the example you've illustrated should work without having to take out await firestore().

@felangel sorry, may be my example is not clear.

I mean:

  • EVENTs must go from UI to BLOC
  • STATEs must go from BLOC to UI.
    If you have to send EVENTs from BLOC to BLOC (as it is done here -I extract the snippet below- ) to solve the problem you are "corrupting" the pattern.
@override
Stream<TickerState> mapEventToState(TickerEvent event) async* {

   if (event is StartTicker) {
       subscription?.cancel();
       subscription = ticker.tick().listen((tick) => dispatch(Tick(tick))); // ** Event from BLOC to BLOC **
   }

   if (event is Tick) {
       yield Update(event.tickCount);
  }

}

I have used the same aproach to solve my problem with firestore while using flutter_bloc library, and it works but I do not like at all.

I write a simplify example below (it is not real code, I get out some pieces to simplify):

class QuedadaBloc extends Bloc<QuedadaEvent, QuedadaState> {

    List<Quedada> quedadas = List<Quedada>();  
    Stream<QuerySnapshot> query;

    QuedadaBloc(){

      query = Firestore.instance.collection('quedadas').snapshots();
      query.listen((datos){  
        quedadas.clear();
        quedadas = datos.documents.map((dato){
          return Quedada.fromSnapShot(dato);
        }).toList();

        // ** (1) Event from BLOC to BLOC dispatched on remote db change **
        dispatch(OnReloadEvent(quedadas: quedadas));   
      });
    }

    @override
    QuedadaState get initialState => QuedadaUninitialized();

    @override
    Stream<QuedadaState> mapEventToState(QuedadaState estado, QuedadaEvent evento) async* {

      if(evento is Fetch){                                        // ** Normal case **

        if(estado is QuedadaUninitialized){
           quedadas = await _fetchQuedadas(0, 20);
           yield QuedadaLoaded(quedadas: quedadas);
        }

      }

      if(evento is OnReloadEvent){                         // ** (2) Handled event coming from (1) **
        yield(QuedadaLoaded(quedadas: evento.quedadas));
      }

      if(evento is OnCreateQuedadaButtonEvent){    // ** Strange case **
          yield QuedadaLoading();
          bool correcto = await _createQuedada(evento.quedada);  
          if(!correcto){
            yield QuedadaError();  
          }
          // ** "else" is not needed because if it is created successfully it is handled in (2) **
      }

    }

}

Thanks and sorry, may be it is just a miss understanding at my side.

Hey @jpablogn

Thanks for the additional details. I don't think events must come from the UI. Blocs can notify other blocs and can also internally dispatch their own events.

The main principle is that events go in (via dispatch) and states come out (from mapEventToState).

Can you describe what the implementation for await _createQuedada(evento.quedada); would look like in the above example?

Hello @felangel,

Many thanks for the explanation.

Yes, of course, here is the code for _createQuedada(evento.quedada):

  Future<bool> _crearQuedada(Quedada quedada) async{
    return Firestore.instance.collection('quedadas').add(quedada.toJSON()).then((document){
      print(document.documentID);
      return true;
    }).catchError((onError){
      print(onError.toString());
      return false;
    });
  }

Regards.
J. Pablo.

Hey @jpablogn sorry for the delayed response and thanks for the code snippet!

I would recommend you update _creatQuedada to just look like:

Future<bool> _crearQuedada(Quedada quedada) async{
  return Firestore.instance.collection('quedadas').add(quedada.toJSON())
}

That way in your mapEventToState you can handle OnCreateQuedadaButtonEvent like:

if(evento is OnCreateQuedadaButtonEvent){
  yield QuedadaLoading();
  try {
    await _createQuedada(evento.quedada);  
  } catch (error) {
    yield QuedadaError();
  }
}

I don't think this is corrupting the pattern because you're still handling an event as input and returning a state as output. The only difference is there is a bit more indirection here. The state is output as a result of another dispatch.

You could also remove the subscription to firebase if you wanted to and only pull from firebase once upon initialization and then create/delete locally and then sync with firebase.

I would recommend looking at the todos bloc as a reference. _saveTodos is called after the todos are modified locally in order to keep the local todos in sync with the persisted todos.

Hope that helps 馃憤

Yep, much nicer :+1:
Thanks.

P.S.: "Future

Was this page helpful?
0 / 5 - 0 ratings

Related issues

krusek picture krusek  路  3Comments

clicksocial picture clicksocial  路  3Comments

1AlexFix1 picture 1AlexFix1  路  3Comments

ricktotec picture ricktotec  路  3Comments

nerder picture nerder  路  3Comments