How to avoid boilerplate code in Flutter when changing routes and keeping the providers?

I have already solved this problem, but I want to clean the code so it's easier to maintain.

The problem was: When using data that comes from Provider<T>, how can I make the providers and their data available in the next view when Navigator.of(context).push?

I solved it with this code:

await Navigator.of(context).push(
  MaterialPageRoute(
    builder: (_) {
      return MultiProvider(
        providers: [
          Provider<User>.value(value: context.read<User>()),
          Provider<Computer>.value(value: context.read<Computer>()),
        ],
        builder: (_, __) => const TheNextScreen(),
      );
    },
    fullscreenDialog: true,
  ),
);

This works properly, but I'm concerned about a few points:

  • Is this a good design pattern? Or does it look like an anti-pattern? Is there a better way to do it?
  • If I type context instead of _, I'd end up overwriting the original context variable and use a context that does not have the providers, therefore increasing the risk of errors in the future (e.g. by calling .read on the incorrect context).
  • I saw some other questions and some people used ChangeNotifierProvider instead (Flutter Provider nested navigation). This seems like an OK solution and I know how to use ChangeNotifierProvider, but as far as I know it doesn't allow a clean way to pass an array of providers, so MultiProvider was my choice.

If there's any way to improve this code design (for example, related to the above concerns), that'd be a helpful answer.


Author of Provider here

This problem arises only when you're trying to scope providers (aka having a provider only available for one screen).

A much simpler solution is to not scope the provider at all. Place your providers above MaterialApp:

MultiProvider(
  providers: [...],
  child: MaterialApp(...),
)

This way, you won't need to add those providers again when using Navigator.push, since the new route would already have access to them.