5

Im very new to flutter and riverpod so im very sorry if the question is not clear, let me know if this is the case

I have 2 providers, one listens to real-time changes of a FireStore collection, and the other one is a List Service to handle the actual list of items being received from FireStore. I want to add the list received to the itemList and then notify the widgets listening to the itemListProvider so that the listview can be rebuilt

Item List state notifier provider

final itemListProvider =
    StateNotifierProvider<ItemListService, List<Item>>(
        (ref) => ItemListService([]));

realtime listener

final firestoreListUpdateWatcher = Provider((ref) {
  //Listens to a stream which provide query snapshot, this works as intended
  final firestoreListWatcher = ref.watch(firestoreListProvider);
  
  //using `ref.watch(itemListProvider.notifier)` because i want to be able 
  //to change the state from this provider
  final itemListProvider = ref.watch(itemListProvider.notifier);
  
  firestoreLisUpdateWatcher.when(
      data: (value) {
          for (int i = 0; i < value.docChanges.length; i++) {
            QueryDocumentSnapshot currentQuery = value.docs[i];
            //current item in list
            ListItem item = fireStoreList.fromMap(currentQuery.data());

            //add current item to the ListService 
            itemListProvider.add(item);
          }
      },
      loading: () {},
      error: (err, stack) {});
});

Item List Service class

class ItemListService extends StateNotifier<List<Item>> {

  ItemListService(List<Item> itemList):super(itemListList ?? []);

  void add(Item it) {
    state = [...state, it];
  }

  //more methods modifying the list....
}

In my widget's build function i simply want to listen to the changes to the itemListProvider and recreate the list based on the current state

This is not working, my widget does not get rebuilt when a change to the list is made. I believe the reason it is not working is because when i create the "watcher" in my "firestoreListUpdateWatcher" using ref.watch(itemListProvider.notifier) im creating a new instance which my widget is not listening to and thus does not get notified when changes happen. Is this assumption correct?

Also, how can i achieve this behavior (modifying the list from another provider)

Is there a better way of designing my app to avoid this constraint?

Thanks in advance for your help

neonx97
  • 53
  • 1
  • 4

1 Answers1

4

Exactly as you said, ref.watch is recreating the provider, thats why you're not seeing your changes, something you could do with your current logic is to watch the stream instead of the provider and updates the state with a listener in the same itemListProvider without creating another Provider:

/// This is your StateNotifierProvider
final itemListProvider = StateNotifierProvider.autoDispose<ItemListService, List<Item>>((ref) {
  //Listen to the stream itself instead of the value hold by the provider
  final firestoreListStream = ref.watch(firestoreListProvider.stream);
  
  //Create the instance of your StateNotifier
  final itemListProvider = ItemListService([]);

  /// subscribe to the stream to change the state accordingly
  final subscription = firestoreListStream.listen((value) {
          for (int i = 0; i < value.docChanges.length; i++) {
            QueryDocumentSnapshot currentQuery = value.docs[i];
            //current item in list
            ListItem item = fireStoreList.fromMap(currentQuery.data());

            //add current item to the ListService 
            itemListProvider.add(item);
          }
      });
  /// When you stop using this porvider cancel the subscription to the stream, to avoid memory leaks
  ref.onDispose(subscription.cancel);
  return itemListProvider;
});

With this you won't need another Provider (firestoreListUpdateWatcher)

As a recomendation I note that you iterate through a for loop and add them individually, which will force the state to update multiple times, I would recommend get the list first and then update the state with an addAll method

final subscription = firestoreListStream.listen((value) {
   List<ListItem> items = [];
   for (int i = 0; i < value.docChanges.length; i++) {
       QueryDocumentSnapshot currentQuery = value.docs[i];
       //current item in list
       ListItem item = fireStoreList.fromMap(currentQuery.data());

       //add current item to the ListService 
       items.add(item);
   }
   ItemListService.addAll(items);
          
});

And in your ItemsService create addAll method

void addAll(Iterable<Item> it) {
  state = [...state, ...it];
}
EdwynZN
  • 4,895
  • 2
  • 12
  • 15
  • First of all thank you very much for your response! I tried to implement it the way you suggested and it worked beautifully i couldnt thank you enough. on a side note, is my approach a reasonable way of separating the business logic from the UI? thanks again! – neonx97 May 23 '21 at 13:32
  • I think it's ok, its decoupled from your UI and you could implement other functions inside it without affecting your view (filter by some attribute, etc), you can check MVVM architecture to create differents service for your views without changing too much your logic – EdwynZN May 23 '21 at 15:50