Stream/Bloc/Repository/Firebase data flow Flutter
Asked Answered
H

1

7

I'm starting with flutter as I want to port my swift app to Flutter, but I'm getting stuck understanding the pattern Bloc/Repository/Firebase as I'm following the tutorial https://bloclibrary.dev/#/flutterfirestoretodostutorial dough I use the real time database, not Firestore. My swift app is basically a map where you can add Alerts at your actual coordinates. The Alert get sent to Firebase and the firebase observer on the map updates the map showing the just added alert. The above tutorial should help me porting my app. I'm just not sure I do understand the logic behind the code. My concerns are 2:

First. There is an Entity layer between the model object and the firebase object. It is explained that this will facilitate having different Data providers, but I don't really see it facilitating anything. In the Model class there is a toEntity() and a fromEntity() conversion method, and in the Entity class there is a fromSnapshot() and a toDocument() conversion method. I don't see what's the point here. Is it really necessary? What's wrong with doing the conversion directly in the Model class , having different methods for each Data provider?

Second. Inside the TodoBloc I can't follow the logic. The first event that is sent to the bloc at AppStart is LoadTodos.

BlocProvider<TodosBloc>(
          create: (context) {
            return TodosBloc(
              todosRepository: FirebaseTodosRepository(),
            )..add(LoadTodos());

In the mapEventToState() method of TodoBloc that event gets mapped to this Stream:

Stream<TodosState> _mapLoadTodosToState() async* {
    _todosSubscription?.cancel();
    _todosSubscription = _todosRepository.todos().listen(
          (todos) => add(TodosUpdated(todos)),
        );
  }

So far so good. As I understand this subscribes to the todos() Stream ()

@override
  Stream<List<Todo>> todos() {
    return todoCollection.snapshots().map((snapshot) {
      return snapshot.documents
          .map((doc) => Todo.fromEntity(TodoEntity.fromSnapshot(doc)))
          .toList();
    });
  }

and this should be the equivalent of the firebase observer in my swift app. It this part inside the listen closure I'm not sure to understand: (todos) => add(TodosUpdated(todos)) .

This sends to itself (TodoBloc) a TodosUpdated event on which the bloc will map this Stream:

Stream<TodosState> _mapTodosUpdatedToState(TodosUpdated event) async* {
    yield TodosLoaded(event.todos);
  }

which is this:

class TodosLoaded extends TodosState {
  final List<Todo> todos;

  const TodosLoaded([this.todos = const []]);

  @override
  List<Object> get props => [todos];

  @override
  String toString() => 'TodosLoaded { todos: $todos }';
}

Is this the actual list of Firebase objects? Does the todos() Stream return the entire node every time a new object is added in Firebase? In my swift app the observer returns only the .childAdded after the first download of the node. Should I use the firebase_database package that has a FirebaseList class(https://pub.dev/documentation/firebase_database/latest/ui_firebase_list/FirebaseList-class.html) that will just return a List on any change on the node as my observers do in my swift app? Sorry for this very long and messy question, but I'm quite lost here starting with bloc pattern. Thank you very much for your time and help.

Halve answered 23/2, 2020 at 16:20 Comment(0)
H
6

Ok, I I think I understood the logic behind it, but if you see that I didn't get it right please correct me as at this stage of getting into a new paradigm is very important not to carry any misconceptions.

  1. todos() is the Stream coming from Firebase and returns a List<Todo>.
  2. _mapLoadTodosToState() is the bloc method that attach a bloc listener to todos() and in the .listen(onData) callback, it sends to the bloc an TodosUpdated(todos) event containing the latest list.
  3. TodosUpdated(todos) gets mapped to _mapTodosUpdatedToState, which yields TodosLoaded(event.todos) , the new state that BlocProvider uses to build the UI.

Thank you and I hope this will help others struggling to master BloC pattern at a more complex level. Cheers

Halve answered 24/2, 2020 at 5:36 Comment(7)
Needed some time too to understand this pattern but I came to the same conclusion. Only thing that I don't understand quite yet is how to show a LoadingIndicator while my stream is loading the data. For example, what if I have a filter on my todos and I change the query to firestore with a .where() method or maybe have a slow internet connection?Dimeter
Once you get used to it it's a breeze.. quite a bit of code to write ..but there is a new easier package Cubit . .( haven't tried it yet ) it's a simpler version of bloc which can be integrated in bloc implementation..Halve
Thanks a lot. I was doing the exact same thing with riverpod and wasn't sure if it's a right approach. If you are familiar with Riverpod, I have some further problems with this approach for getting better performance. May I ask those questions if you comfortable with it?Barrelchested
Hi, I haven't looked into Riverpod yet but let's try, I might be helpful anywaysHalve
After getting the data from stream, I converted it to the object (let's say objA) which contains a list of change notifiers. So, whenever firestore stream gets new data, it will be converted into objA, added in the state (in my case, StateNotifier) and will be sent to the widget where we are using it. Now, If the object is same or most of the items in state's list are identical, I only want to update the changes in the modified change notifiers otherwise all of the objects will be rebuilt. (I know flutter will try not to rebuild object if they are identical, but we can't be 100% sure.)Barrelchested
mm I'm not comfortable with Change Notifier class and consumer etc etc.. that's why I chose flutter_ bloc so I can modularise the code and not get a mess.. I felt that when you need this kind of logics using Change Notifier leads to a spaghetti code quite easily with all the listeners you have to setup.. with flutter_bloc you set up your listeners and builders and have a quite deep control plus is very easy to grasp.. and after 3 years of it I'm not really keen to implement a different state management. Try to make a new question and you'll get help from the community in a breeze. CheersHalve
@Halve Thank you so much for your reply. I haven't done any project in flutter_bloc, so, it is new for me. I am planning on changing those Change Notifiers with StateNotifier if it better suit my needs. I think I will have to research the exact structure before starting to implement it to avoid spaghetti code. I actually posted a question regarding it but didn't get any response or suggestions. #70483270Barrelchested

© 2022 - 2024 — McMap. All rights reserved.