How do I provide an existing provider from `main` to `app`

62 Views Asked by At

In Flutter I currently have the following basic app structure (simplified):

class MyProvider extends ChangeNotifier {
  String get value => 'Hello!';
}

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider.value(value: MyProvider()),
      ],
      builder: (context, _) => MaterialApp(
        home: Scaffold(
          body: Center(
            child: Text(context.read<MyProvider>().value),
          ),
        ),
      ),
    );
  }
}

Now, MyProvider needs an async init method. I thought I could initialize the provider in the main function and then pass it along to the app and eventually to the MultiProvider, like so:

class MyProvider extends ChangeNotifier {
  Future<void> init() async {
    // Something async
  }

  String get value => 'Hello!';
}

Future<void> main() async {
  final myProvider = MyProvider();
  await myProvider.init();

  final app = MyApp(
    providers: [
      myProvider,
    ],
  );
  runApp(app);
}

class MyApp extends StatelessWidget {
  const MyApp({super.key, this.providers = const []});

  final List<ChangeNotifier> providers;

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ...providers.map((p) => ChangeNotifierProvider.value(value: p)),
      ],
      builder: (context, _) => MaterialApp(
        home: Scaffold(
          body: Center(
            child: Text(context.read<MyProvider>().value),
          ),
        ),
      ),
    );
  }
}

However, this doesn't work as it throws a ProviderNotFoundException. I'm struggling to see why though. To me, the two versions seem almost identical, as the actual ChangeNotifierProvider is created in the same place. Only the value is created somewhere else.

Am I doing something obviously wrong?

As a side note, I know I could make my app work by wrapping the MaterialApp inside a FutureBuilder and initializing the provider there, but that just looks ugly and suboptimal.

3

There are 3 best solutions below

0
Temo Fey On

You can wrap MyApp widget into MultiProvider

main() async {
  final p = MyProvider();
  await p.init();

  runApp(
   MultiBlocProvider(
     providers:[
       ChangeNotifierProvider<MyProvider>.value(value: p),
     ],
     child: MyApp()
   )
  )
}
0
Pratik Lakhani On

Update your provider like this:

class MyProvider extends ChangeNotifier {
  MyProvider() {
    init();
  }
  Future<void> init() async {
    // Something async
  }
  String get value => 'Hello!';
}

When you initialize the provider, the init function is automatically called.

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<MyProvider>(
          create: (context) => MyProvider(),
        ),
      ],
      builder: (context, _) => MaterialApp(
        home: Scaffold(
          body: Center(
            child: Text(context.read<MyProvider>().value),
          ),
        ),
      ),
    );
  }
}
1
pcba-dev On

When creating the ChangeNotifierProvider's passed to the MultiProvider you are not indicating the type of those notifiers (the type of the provided model), consequently it cannot be found in the BuildContext the provider which has that type MyProvider.

You shall declarte as Provider<YourModel> instead of just Provider. Where YourModel in your case is MyProvider.

NOTE: You are using the suffix "Provider" but it is actually a ChangeNotifier.

You shall define the type of provider and then pass the actual Provider (not the ChangeNotifier) as an argument to your MyApp:

Future<void> main() async {
  final notifier = MyChangeNotifier();
  await notifier.init();
  final provider = ChangeNotifierProvider<MyChangeNotifier>.value(value: notifier);
  
  runApp(MyApp(
    providers: [provider],
  ));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key, this.providers = const []});

  final List<SingleChildWidget> providers;

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: providers,
  ...