Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
AhmedLSayed9 opened this issue Oct 7, 2024 · 6 comments
Assignees
Labels
documentation Improvements or additions to documentation needs triage

Comments

@AhmedLSayed9
Copy link
Contributor

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.

@AhmedLSayed9 AhmedLSayed9 added documentation Improvements or additions to documentation needs triage labels Oct 7, 2024
@rrousselGit
Copy link
Owner

Currently you can do:

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

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

@AhmedLSayed9
Copy link
Contributor Author

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
Copy link
Contributor Author

Maybe for .go we can just do:

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

Until #108764 is resolved

@rich-j
Copy link

rich-j commented Oct 10, 2024

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
Copy link
Contributor Author

AhmedLSayed9 commented Oct 10, 2024

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 [go_router_builder][go_router] Exception thrown when parent route has $extra param flutter/flutter#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
Copy link
Owner

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation needs triage
Projects
None yet
Development

No branches or pull requests

3 participants