Florian Leeser
Florian Leeser

Reputation: 790

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

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?

Upvotes: 4

Views: 2023

Answers (1)

EdwynZN
EdwynZN

Reputation: 5611

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

Upvotes: 2

Related Questions