3

I'm trying to learn Riverpod with clean architecture.

I have following set/chain of providers:

final databaseFutureProvider = FutureProvider<Database>((ref) async {
  final db = await DatabaseHelper().createDatabase(); // this is async because 'openDatabase' of sqflite is async
  return db;
});

final toDoDatasourceProvider = Provider<ToDoDatasource>((ref) {
  final db = ref.watch(databaseFutureProvider); // problem is here!!
  return ToDoDatasourceImpl(db: db);
});

final toDoRepositoryProvider = Provider<ToDoRepository>((ref) {
  final ds = ref.watch(toDoDatasourceProvider);

  return ToDoRepositoryImpl(ds);
});

I am probably missing some small things or doing it completely wrong. How to properly provide DB (that is async in its nature)?

Maciej Pszczolinski
  • 1,623
  • 1
  • 19
  • 38

2 Answers2

3

You don't need multiple providers, in your case since you would need ToDoRepository always you can just Initialized before running the app and use it later without worrying about the database connection state

Future<void> main(List<String> args) async {
  // Initialization the db
  final db = await DatabaseHelper().createDatabase();

  ProviderScope(
    overrides: [
      // pass the db
      toDoRepositoryProvider.overrideWithValue(db),
    ],
    child: RootApp(),
  );
}

final toDoRepositoryProvider = Provider<ToDoRepository>((ref) {
  throw UnimplementedError();
});
Mohammed Alfateh
  • 3,186
  • 1
  • 10
  • 24
  • thanks @Mohammed-Alfateh. so in more abstract words - the technique is to create `exception throwing provider` and then `override` it in `ProviderScope`? Is it official, recommended way? – Maciej Pszczolinski Oct 24 '22 at 05:59
  • Yes it's the normal way to do it in Riverpod, you declare this provider should be implemented / overridden before using others ways the complier will throw `UnimplementedError` reminding you to implemented it. You would do the same when using `ScopeProvider` to pass arguments to widgets through a provider instead of the widget constructer. – Mohammed Alfateh Oct 24 '22 at 11:08
  • Do you know how we can pass ref into the override value. That's the problem I am having right now. I'm doing this very thing with initializing the database then passing it into the overrideWithValue, but I am wanting to use ref in the class as well. – Trey Thomas Feb 08 '23 at 16:07
  • @TreyThomas you can override with `Ref` by using `overrideWith` instead of `overrideWithValue ` so you can access the provider ref, one way could be by making a class that take the initialized database and the ref `toDoRepositoryProvider.overrideWith((ref) => DatabaseProvider(ref, db))` – Mohammed Alfateh Feb 08 '23 at 17:30
0

I totally agree with Mohammed Alfateh's decision. In addition, you can use ProviderContainer()..read(toDoDatasourceProvider) and UncontrolledProviderScope, to asynchronously assign values in main method. And in ToDoDatasourceImpl call the async method init() to assign a value in the field late final db.

Ruble
  • 2,589
  • 3
  • 6
  • 29