rrousselGit / riverpod

A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
https://riverpod.dev
MIT License
6.24k stars 955 forks source link

Documenting an approach for how to init some values before pushing a route #3765

Open AhmedLSayed9 opened 2 weeks ago

AhmedLSayed9 commented 2 weeks ago

We need to find some reliable approach for initializing some values before pushing a route and add it to documentation.

There was a related issue #1329 that was asking for a way to auto dispose after first listener, which is one of the approaches to solve this matter. but as Remi mentioned, it seems dangerous, as if the "first listener" is never added for some reason, the disposal will never happen.

Another proposed approach is to do something like:

 await ref.run(provider, (value) async {
    // provider will stay alive until the callback completes
    await Navigator.push(); // The await here will keep the provider alive until the route is poped
  });

but this won't work with the common go_router's method .go as it's synchronous. Note: There's an issue asking for it to be asynchronous, but it's there for a very long time.

rrousselGit commented 2 weeks ago

Currently you can do:

final sub = ref.listen(provider.notifier, (_) {});
sub.read().someMethod();

try {
  await Navigator.push();
} finally {
  sub.close();
}
AhmedLSayed9 commented 2 weeks ago

Currently you can do:

final sub = ref.listen(provider.notifier, (_) {});
sub.read().someMethod();

try {
  await Navigator.push();
} finally {
  sub.close();
}

This is good enough, but unfortunately this won't work with the common go_router's method .go as it's synchronous.

AhmedLSayed9 commented 2 weeks ago

Maybe for .go we can just do:

try {
  SomeRoute().go(context);
  await Future.delayed(someDuration);
} finally {
  sub.close();
}

Until #108764 is resolved

rich-j commented 1 week ago

Consider changing the implementation to pass the init parameter(s) in the route and initializing the edit provider in the target page. Initializing the edit provider before navigating is effectively using it as a global variable (imperative).

In our app, when a user selects an item to edit in another page/dialog, our route includes the ID of the selected item. The edit page uses the given route parameter to watch the edit provider that is a .autoDispose.family where the family value is the passed ID. We do have another provider that caches our objects that the edit provider reads the object data from. If your only data source is the list in the original page you can use the GoRouterState.extra parameter to pass an arbitrary data object.

Passing init parameters in the route makes the implementation more "declarative". This removes the timing issue where during async navigation processing RiverPod can dispose of unused states. Also on the web where the user can easily do back navigation the previous item edits are (can be) restored (updated objects need additional handling/caching).

AhmedLSayed9 commented 1 week ago

Consider changing the implementation to pass the init parameter(s) in the route and initializing the edit provider in the target page. Initializing the edit provider before navigating is effectively using it as a global variable (imperative).

In our app, when a user selects an item to edit in another page/dialog, our route includes the ID of the selected item. The edit page uses the given route parameter to watch the edit provider that is a .autoDispose.family where the family value is the passed ID. We do have another provider that caches our objects that the edit provider reads the object data from. If your only data source is the list in the original page you can use the GoRouterState.extra parameter to pass an arbitrary data object.

Passing init parameters in the route makes the implementation more "declarative". This removes the timing issue where during async navigation processing RiverPod can dispose of unused states. Also on the web where the user can easily do back navigation the previous item edits are (can be) restored (updated objects need additional handling/caching).

The use-case for this approach is to initialize and pass some data that's already there or can't be fetched through an init parameter (which we'd pass it as path/query parameter ofc).

Using GoRouterState.extra parameter should work but there're 2 obstacles here:

  1. It's broken and not safe to be used anyway. It stores only 1 global extra parameter at a time and can unintentionally break your app. check https://github.com/flutter/flutter/issues/106121.
  2. There're cases where it's needed in different widgets/sub-routes and user would prefer to inject it through a provider instead of passing it everywhere.
rrousselGit commented 1 week ago

Overall I think this is more of a GoRouter issue than a Riverpod issue.

Navigator has various ways to handle this, such as Navigator.push(route) ; which allows pushing a route with custom values.