Riverpod's StreamProvider stuck in loading when reading Hive's box | Flutter

1,078

Hey I think I have some solution for you, as far as I understand the watch method of a Box will be empty the first time it runs, it doesn't matter if the box has something because watch only fires when there is a change since the moment it starts listening so it will be in loading state until you change the key 0 value somewhere in your app.

I'm not really a fan of this behavior and it would be better if the watch method returns the initial data the first time

final localUserStream = watch(localUserStreamProvider);

return localUserStream.when(
  data: (data) => data == null ? Container(color: Colors.blue) : data.isEmailVerified ? Container(color: Colors.green) : Container(color: Colors.purple), 
  loading: () => TextButton(
    onPressed: () async {
       final box = await watch(localUserBoxFutureProvider.future);
       await box.put(0, User()) // this is just an example that when you tap the button the stream actually change to data
    },
    child: Text('Update me'),
  ), 
  error: (e, s) => Container(color: Colors.red)
);

UPDATE

This can be a bit tricky (and I haven't tested it) but you could stream an initial value in your StreamProvider

final localUserStreamProvider = StreamProvider<User>((ref) async* {
  final usersBox = await ref.watch(localUserBoxFutureProvider.future);
  yield* Stream.value(userBox.get(0, defaultValue: User())); //or getAt(0)
  yield* usersBox.watch(key: 0).map((boxEvent) => boxEvent as User);
});

This way it will show the value saved in your box at the beggining of your app, and afterwards the change of events related with that key

Share:
1,078
Florian Leeser
Author by

Florian Leeser

Updated on December 26, 2022

Comments

  • Florian Leeser
    Florian Leeser over 1 year

    I am trying to stream the users data that I saved into a box called 'users' with Hive. This is for showing a screen based on the information provided from the user. For now, the box contains no data, so I expect the following code to show a blue screen. Otherwise it should be green or purple. It is mandatory for me to know when reading the value finished, so that I know wether the returned value null means the data did not load yet or the users box is empty.

    I am using Riverpod for state management and this approach.

    I implemented the following two providers

    final localUserBoxFutureProvider = FutureProvider<Box>((ref) async {
      final usersBox = await Hive.openBox('users');
      return usersBox;
    });
    
    final localUserStreamProvider = StreamProvider<User>((ref) async* {
      final usersBox = await ref.watch(localUserBoxFutureProvider.future);
      yield* usersBox.watch(key: 0).map((boxEvent) => boxEvent as User);
    });
    

    and would like to use them like something like this:

    final localUserStream = watch(localUserStreamProvider);
    
    return localUserStream.when(
      data: (data) => data == null ? Container(color: Colors.blue) : data.isEmailVerified ? Container(color: Colors.green) : Container(color: Colors.purple), 
      loading: () => Container(color: Colors.yellow), 
      error: (e, s) => Container(color: Colors.red)
    );
    

    The problem with this implementation is that it always shows a yellow screen, meaning its stuck in loading. Any ideas?

    • Rémi Rousselet
      Rémi Rousselet over 3 years
      Is the "yield" reached? And if so, is the "map" called? It is possible that nothing is pushed on your stream
    • Florian Leeser
      Florian Leeser over 3 years
      @RémiRousselet I removed the "map" and then only had the check data==null ?. Same behaviour. How can I check your "yield" part?
  • Florian Leeser
    Florian Leeser over 3 years
    Thank you! This gives me a good explanation on why it does not work, but not how to implement a solution.
  • EdwynZN
    EdwynZN over 3 years
    try my update and tell me if it helps you
  • Florian Leeser
    Florian Leeser over 3 years
    The problem with your updated implementation is the following: If there is no user inside the database, the stream would yield a null value all the time, which means: Loading cycle. I will solve this by yielding an empty User object if the stream value is null. With this approach, null should only be emitted when the ProviderReader is really "loading", I guess.
  • EdwynZN
    EdwynZN over 3 years
    userBox.get(0, defaultValue: User()); or some factory constructor of User (User.empty() for example)
  • Florian Leeser
    Florian Leeser over 3 years
    You were right! It works! This is a very clean workaround for the "initial-null-problem". Nevertheless it needs some kind of different data type (e.g. empty User object).
  • EdwynZN
    EdwynZN over 3 years
    that will always depends on what you need to show first when there is no value saved in the box, if this helped you solve the problem you can now accept the answer