5

New to riverpod here. (Using Flutter and hooks_riverpod, btw).

Using Hive to store related lists of items. I need to call Hive.initFlutter and wait for Hive to be initialized, and do the same to open hive boxes, once when my app loads. After that, calls to Hive are synchronous.

My original idea (though I'm open to better ideas) was to create a StateNotifier that holds both the lists. This notifier could have a setUp function that's asynchronous. Here's what that looked like, simplified:

class ItemsNotifier extends StateNotifier<ItemsState> {
  ItemsNotifier() : super(ItemsState([], []));

  setUp() async {
    await Hive.initFlutter();
    // What is ItemDao? It's a data accessor object singleton used to house Hive logic.
    await ItemDao().openBoxes();
    // Putting a breakpoint here, I can see by calling `ItemDao().list1` etc that the lists have loaded with items as expected, but setting state here does not trigger a rebuild of the consumer widget.
    state.list1 = ItemDao().list1;
    state.list2 = ItemDao().list2;
  }

  ...getters and setters and other functions omitted...
}

final itemsProvider = StateNotifierProvider<ItemsNotifier, ItemsState>((ref) {
  final notifier = ItemsNotifier();
  notifier.setUp(); // I've never seen anything to suggest that calling an async setUp method here is supported, it's just something I tried.
  return notifier;
});

class ItemsState {
  List<Item> list1;
  List<Item> list2;
  ItemsState(this.list1, this.list2);
}

As mentioned in the comments, I call an async setUp method while constructing itemsProvider. I put a breakpoint inside the setup method and inside my consumer widget. First the breakpoint inside the widget catches, and we see that list1 is empty, as expected. Next the breakpoint inside the setup method catches. We see that ItemDao().list1 is full of items, so loading from Hive succeeded. So I'd expect calling state.list1 = would cause the consumer to reload as it usually does. But it doesn't. The breakpoint in the widget doesn't catch again, the widget remains empty. Probably because Riverpod isn't expecting async methods to change state from inside the StateNotifierProvider constructor.

So a simple solution to this question might just be an answer to where in the app should I call setUp()? It would need to be somewhere that only runs once when the app starts. In an initState in a stateful widget somewhere? That doesn't quite feel right... as I said, I'm new to using riverpod.

Or if you have an alternate (better) way to architect this out, that would also be helpful.

Solutions considered:

I'll note that I also tried using riverpod's FutureProvider. It works for loading the list. But, as stated in the docs, FutureProvider can't be extended as StateNotifier can, so I'm not sure where I'd put custom setters and getters. And if I wrote one FutureProvider per each list, that wouldn't handle the fact that Hive.initFlutter should only be called once. I could see a ways around this, but it's a bit clunky, and thought I would be better off if someone with more experience advised me. StreamProvider seems basically the same as FutureProvider. Maybe there's a way to compose a FutureProvider inside a StateNotifierProvider? Really not sure what that would look like.

Benjamin Lee
  • 1,014
  • 10
  • 24

1 Answers1

0

I solved a related problem using this question and the comment on it. Since OP seemed to solve his problem 2 years ago, I am posting this in the hope that it will help somebody else.

In my app I let the user choose lighting mode using toggle_switch enter image description here

I use flutter_riverpod to manage mode state and hive to persist mode value, so that if the user has chosen dark mode, the next time the app will start with dark mode as well.

mode_provider.dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hive_flutter/hive_flutter.dart';

class ModeStateNotifier extends StateNotifier<String> {
  ModeStateNotifier() : super('light');

  void setState(String val) {
    state = val;
    Hive.box('box123').put('mode', val);
  }

  setUp()  {
    String? defaultMode =  Hive.box('box123').get('mode');
    setState(defaultMode ?? 'light');
  }
}

bool needSetUp = true;
final modeProvider = StateNotifierProvider<ModeStateNotifier, String>((ref) {
  final modeStateNotifier = ModeStateNotifier();
  if (needSetUp) {
    modeStateNotifier.setUp();
    needSetUp = false;
  }
  return modeStateNotifier;
});

Hive is initialized in main:

main.dart

void main() async {
    await Hive.initFlutter();
    await Hive.openBox('box123');
    runApp(
    const ProviderScope(child: MainApp()),
  );
}

MainApp extends ConsumerWidget:

Widget build(BuildContext context, WidgetRef ref) {
String mode = ref.watch(modeProvider);
return MaterialApp.router(
  routerConfig: rout.Router.getRouter(),
  themeMode: ThemeMode.values.byName(mode);
  ...

When switch tapped:

onToggle: (index) {
                String value = 'light';
                if (index == 1) value = 'system';
                if (index == 2) value = 'dark';
                ref.read(modeProvider.notifier).setState(value);
              },
Yuriy N.
  • 4,936
  • 2
  • 38
  • 31