riverpod icon indicating copy to clipboard operation
riverpod copied to clipboard

Documentation doesn't mention eager initialisation of Providers

Open DreadBoy opened this issue 5 years ago • 17 comments
trafficstars

Describe what scenario you think is uncovered by the existing examples/articles It seems, providers are initialised lazily only when needed, possibly never. This isn't mentioned in documentation, so I assumed they are created eagerly, specially since they are defined globally. I might be alone in this assumption but I suspect I'm not. At glance it looks like init() method should be called as soon as this file is loaded by Dart VM but it's not:

final notificationProvider = Provider<NotificationProvider>(
  (ref) {
    return NotificationProvider()..init();
  },
);

Describe why existing examples/articles do not cover this case I read through entire documentation (Concepts and Guides, really) and didn't find it mentioned. Also using integrated search to look up "eager" or "lazy" words yielded nothing. I don't think I missed that information.

DreadBoy avatar Oct 30 '20 11:10 DreadBoy

Global variables are always lazy-loaded. This is a specificity of how Dart behaves.

rrousselGit avatar Oct 30 '20 12:10 rrousselGit

As a comeback to this, because of #225 – If you want to initialize/read providers inside your main, you can do:

final greeting = Provider<String>((ref) => "hello world");

void main() {
  final container = ProviderContainer();

  print(container.read(greeting)); // "hello world"

  runApp(
    UncontrolledProviderScope(
      container: container,
      child: MyApp(),
    ),
  );
}

rrousselGit avatar Nov 21 '20 14:11 rrousselGit

can you provide more info on the behavior of "UncontrolledProviderScope"

I'm guessing this tells Riverpod to keep state, not in the widget tree, but in some other area of global memory??

dgaedcke avatar Dec 14 '20 19:12 dgaedcke

I'm guessing this tells Riverpod to keep state, not in the widget tree, but in some other area of global memory??

No, this stores the state in ProviderContainer

This class is always where the state of your providers is stored.

Using ProviderScope is equivalent to a StatefulWidget that instantiate a ProviderContainer and inserts it in the widget tree with UncontrolledProviderScope

rrousselGit avatar Dec 14 '20 23:12 rrousselGit

@rrousselGit Can ProviderScope be added anywhere in the tree? instead of as the root widget?

I'm creating an internal package for my team and I'm planning to use riverpod, but not all the apps use riverpod.

JoseGeorges8 avatar Mar 12 '21 13:03 JoseGeorges8

Not having ProviderScope as the root of the app is not ideal

if the ProviderScope is somehow destroyed, that'll destroy your app state.

rrousselGit avatar Mar 12 '21 13:03 rrousselGit

so I'll create a widget that exposes ProviderScope then and have them add it to the root. Would you agree?

JoseGeorges8 avatar Mar 12 '21 13:03 JoseGeorges8

Yes

rrousselGit avatar Mar 12 '21 13:03 rrousselGit

Is this possible to make this into a feature request?

If you wrap something like Firebase/Firestore/Any cloud DB; you sometimes want eager init, to hide server latency.

What I'm asking for is an official way, similar to lazy: false in provider.

larssn avatar Nov 17 '21 16:11 larssn

It's not doable, because global/static variables in Dart are lazy-loaded.

Providers aren't created until you read them for the first time, so Riverpod can't initialize them even if it wanted to

rrousselGit avatar Nov 17 '21 17:11 rrousselGit

Maybe have something like:

ProviderScope(
 child: myApp(),
 eager: [...listOfProviders],
)

All I'm really asking for is some official mechanism to achieve what you demonstrated in https://github.com/rrousselGit/river_pod/issues/202#issuecomment-731585273

larssn avatar Nov 17 '21 17:11 larssn

The official mechanism to achieve is the official mechanism.

It's not a workaround. That's the actual solution

rrousselGit avatar Nov 27 '21 13:11 rrousselGit

 final greeting = Provider<String>((ref) => "hello world");

void main() {
  final container = ProviderContainer();

  print(container.read(greeting)); // "hello world"

  runApp(
    UncontrolledProviderScope(
      container: container,
      child: MyApp(),
    ),
  );
}

With respect to this^ @rrousselGit, what if we wanted to override values of Providers with this UncontrolledProviderScope as well? ProviderScope allowed overrideWithValue but this does not. What is the workaround in that case?

PoojaB26 avatar Mar 04 '22 06:03 PoojaB26

To give more context, this is related to initialising SharedPreferences in main, where earlier I had var sharedPreferencesServiceProvider = Provider<SharedPreferenceService>((_) => throw UnimplementedError());

And after getting the instance of SP, I could override the value this way

final sharedPreferences = await SharedPreferences.getInstance();

runApp(
      ProviderScope(
         overrides: [
            sharedPreferencesServiceProvider.overrideWithValue(SharedPreferenceService(sharedPreferences)),
         ],
        child: MyApp(),
      ),
    ),

But now if I use UncontrolledProviderScope to solve another problem, and to override, if I try

container.updateOverrides([
    sharedPreferencesServiceProvider.overrideWithValue(SharedPreferenceService(sharedPreferences)),
  ]);

This ^ does not allow me to override null with ValueProvider<Object?, SharedPreferenceService>. Actual error: Replaced the override of type Null with an override of type ValueProvider<Object?, SharedPreferenceService>, which is different.

Didn't have this error during ProviderScope overriding. I'm on version 0.14.0+3

PoojaB26 avatar Mar 04 '22 07:03 PoojaB26

With respect to this^ @rrousselGit, what if we wanted to override values of Providers with this UncontrolledProviderScope as well? ProviderScope allowed overrideWithValue but this does not. What is the workaround in that case?

The constructor of ProviderContainer has the observers and overrides parameter that you find on ProviderScope. So you can use those

rrousselGit avatar Mar 04 '22 10:03 rrousselGit

Maybe have something like:

ProviderScope(
 child: myApp(),
 eager: [...listOfProviders],
)

All I'm really asking for is some official mechanism to achieve what you demonstrated in #202 (comment)

This approach would have the benefit of being easier to discover and document, it was also where I first looked for this functionality before I had to go through GitHub issues.

timcreatedit avatar Mar 08 '22 16:03 timcreatedit

That won't work with family, which is an equally important case.

rrousselGit avatar Mar 08 '22 17:03 rrousselGit

Surprised me, too. Would be really nice if you can give this a mention.

Global variables are always lazy-loaded. This is a specificity of how Dart behaves.

Yes, but no one here ever even used global variables in Dart before Riverpod 😀

textzenith avatar Jun 08 '23 13:06 textzenith

This topic should be addressed in the documentation rework. Maybe inside the "notable Cookbooks examples" section. such guide in particular should be cross-referenced in the "migration from Provider" quickstart.

lucavenir avatar Jul 13 '23 22:07 lucavenir

It does now https://docs-v2.riverpod.dev/docs/essentials/eager_initialization

rrousselGit avatar Oct 10 '23 17:10 rrousselGit

As a comeback to this, because of #225 – If you want to initialize/read providers inside your main, you can do:

final greeting = Provider<String>((ref) => "hello world");

void main() {
  final container = ProviderContainer();

  print(container.read(greeting)); // "hello world"

  runApp(
    UncontrolledProviderScope(
      container: container,
      child: MyApp(),
    ),
  );
}

Hey @rrousselGit, so when using this for a StateNotifier and also changing the state with something like container.read(greeting).changeGreeting('hello riverpod') directly in the main before running the app, then when using this provider later on in some widget tree and watching its value with something like Text(ref.watch(greeting)) it will still show the old greeting 'hello world'. But if I print the new greeting value after setting it to a new one in main, it does give me the updated value. Only the UI seems to miss it. If I update the state again from a button press, it does work like intended and the text updates to the new value. Can you explain why this is and if this is intended or how we can fix the UI to be in sync with the provider state from the beginning?

tobiasbentin avatar Oct 12 '23 10:10 tobiasbentin

@tobiasbentin I know you didn't ask me, but please read the new documentation, it basically answers your question, but to put it shortly AVOID doing shenanigans with container before ProviderScope. Initialize your data directly into a dedicated pre-app loading page as shown.

lucavenir avatar Oct 12 '23 16:10 lucavenir

@rrousselGit one thing that imo is missing from the page is mentioning how using ref.keepAlive for some one-time-asynchronous singletons is key to avoiding StateErrors when using .requireValue on eagerly initialized providers.

lucavenir avatar Oct 12 '23 17:10 lucavenir

@lucavenir No. That's not an issue with how the page works.

rrousselGit avatar Oct 12 '23 17:10 rrousselGit

@lucavenir thanks for your answer. of course for this simple case I would do it differently but basically what I wanted to achieve is what is shown in this example but for a StateNotifierProvider instead of a normal Provider. Is there a good way to do that? Currently I am making use of the requireValue getter but I thought it would be nice to have it overwritten so it can be sync. And I would like to know if using the method described above is fine for initialising stuff like firebase, deeplinks etc.? Just as shown at the end here

tobiasbentin avatar Oct 17 '23 09:10 tobiasbentin