Riverpod's StreamProvider yields StreamValue only once | Flutter & Hive

1,140

but the watch() method is not triggered anymore after it got triggered once

Thats because after every CRUD you're closing the box, so the stream (which uses that box) stop emitting values. It won't matter if you're calling it from somewhere outside riverpod (await Hive.openBox('users')) its calling the same reference. You should close the box only when you stop using it, I would recommend using autodispose with riverpod to close it when is no longer used and maybe put those CRUD methods in a class controlled by riverpod, so you have full control of the lifecycle of that box

final localUsersBoxFutureProvider = FutureProvider.autoDispose<Box>((ref) async {
  final usersBox = await Hive.openBox('users');

  ref.onDispose(() async => await usersBox?.close()); //this will close the box automatically when the provider is no longer used
  return usersBox;
});

final localUserStreamProvider = StreamProvider.autoDispose<User>((ref) async* {
  final usersBox = await ref.watch(localUsersBoxFutureProvider.future);

  yield* Stream.value(usersBox.get(0, defaultValue: User()) as User);
  yield* usersBox.watch(key: 0).map((usersBoxEvent) {
    return usersBoxEvent.value == null ? User() : usersBoxEvent.value as User;
  });
});

And in your methods use the same instance box from the localUsersBoxFutureProvider and don't close the box after each one, when you stop listening to the stream or localUsersBoxFutureProvider it will close itself

Share:
1,140
Florian Leeser
Author by

Florian Leeser

Updated on December 27, 2022

Comments

  • Florian Leeser
    Florian Leeser over 1 year

    I wrote a StreamProvider that I listen to right after startup to get all the information about a potentially logged in user. If there is no user, so the outcome would be null, the listener stays in loading state, so I decided to send back a default value of an empty user to let me know that the loading is done. I had to do this, because Hive's watch() method is only triggered when data changes, which it does not at startup. So after that, I want the watch() method to do its job, but the problem with that, are the following scenarios:

    1. At startup: No user - Inserting a user -> watch method is triggered -> I get the inserted users data -> Deleting the logged in user -> watch method is not triggered.

    2. At startup: Full user - Deleting the user -> watch method is triggered -> I get an empty user -> Inserting a user -> watch method is not triggered.

    After some time I found out that I can make use of all CRUD operations as often as I want to and the Hive's box does what it should do, but the watch() method is not triggered anymore after it got triggered once.

    The Streamprovider(s):

    final localUsersBoxFutureProvider = FutureProvider<Box>((ref) async {
      final usersBox = await Hive.openBox('users');
      return usersBox;
    });
    
    final localUserStreamProvider = StreamProvider<User>((ref) async* {
      final usersBox = await ref.watch(localUsersBoxFutureProvider.future);
    
      yield* Stream.value(usersBox.get(0, defaultValue: User()));
      yield* usersBox.watch(key: 0).map((usersBoxEvent) {
        return usersBoxEvent.value == null ? User() : usersBoxEvent.value as User;
      });
    });
    

    The Listener:

    return localUserStream.when(
      data: (data) {
        if (data.name == null) {
          print('Emitted data is an empty user');
        } else {
          print('Emitted data is a full user');
        }
    
        return Container(color: Colors.blue, child: Center(child: Row(children: [
          RawMaterialButton(
            onPressed: () async {
              final globalResponse = await globalDatabaseService.signup({
                'email' : '[email protected]',
                'password' : 'password',
                'name' : 'My Name'
              });
    
              Map<String, dynamic> jsonString = jsonDecode(globalResponse.bodyString);
              await localDatabaseService.insertUser(User.fromJSON(jsonString));
            },
            child: Text('Insert'),
          ),
          RawMaterialButton(
            onPressed: () async {
              await localDatabaseService.removeUser();
            },
            child: Text('Delete'),
          )
        ])));
      },
      loading: () {
        return Container(color: Colors.yellow);
      },
      error: (e, s) {
        return Container(color: Colors.red);
      }
    );
    

    The CRUD methods:

    Future<void> insertUser(User user) async {
        Box usersBox = await Hive.openBox('users');
        await usersBox.put(0, user);
        await usersBox.close();
      }
    
      Future<User> readUser() async {
        Box usersBox = await Hive.openBox('users');
        User user = usersBox.get(0) as User;
        await usersBox.close();
        return user;
      }
    
      Future<void> removeUser() async {
        Box usersBox = await Hive.openBox('users');
        await usersBox.delete(0);
        await usersBox.close();
      }
    

    Any idea how I can tell the StreamProvider that the watch() method should be kept alive, even if one value already got emitted?

    • DolDurma
      DolDurma almost 3 years
      could you tell me please what's your localDatabaseService implementation? i try to implement Hive initials with Riverpod
    • Florian Leeser
      Florian Leeser almost 3 years
      Every method I implemented, can be found in my question under "the CRUD methods".
    • DolDurma
      DolDurma almost 3 years
      i read again, localDatabaseService and localUserStream implementations doesn't exist in your post
    • Florian Leeser
      Florian Leeser over 2 years
      @DolDurma I really don't get what you are searching for. "localDatabaseService" is the variable used for the instance of the "LocalDatabaseService" class whose CRUD operations you should be able to see in my question. Also "localUserStream" is the variable for what I get from the localUserStreamProvider.
  • Florian Leeser
    Florian Leeser over 3 years
    I'll include your name in my prayer!