How to use a provider inside of another provider in Flutter
Asked Answered
D

7

42

I want to create an app that has an authentication service with different permissions and functions (e.g. messages) depending on the user role.

So I created one Provider for the user and login management and another one for the messages the user can see.

Now, I want to fetch the messages (once) when the user logs in. In Widgets, I can access the Provider via Provider.of<T>(context) and I guess that's a kind of Singleton. But how can I access it from another class (in this case another Provider)?

Dehydrogenase answered 3/9, 2019 at 6:19 Comment(0)
D
36

Thanks for your answer. In the meanwhile, I solved it with another solution:

In the main.dart file I now use ChangeNotifierProxyProvider instead of ChangeNotifierProvider for the depending provider:

// main.dart
return MultiProvider(
      providers: [
        ChangeNotifierProvider(builder: (_) => Auth()),
        ChangeNotifierProxyProvider<Auth, Messages>(
          builder: (context, auth, previousMessages) => Messages(auth),
          initialBuilder: (BuildContext context) => Messages(null),
        ),
      ],
      child: MaterialApp(
        ...
      ),
    );

Now the Messages provider will be rebuilt when the login state changes and gets passed the Auth Provider:

class Messages extends ChangeNotifier {
    final Auth _authProvider;

    List<Message> _messages = [];
    List<Message> get messages => _messages;

    Messages(this._authProvider) {
        if (this._authProvider != null) {
            if (_authProvider.loggedIn) fetchMessages();
        }
    }

    ...
}
Dehydrogenase answered 3/9, 2019 at 9:0 Comment(3)
Adding to that, here is an article that explains all Providers - medium.com/flutter-community/…Doubleton
Hi, how would you solve if you have to perform some async task in update. Like naybe setting user properties by fetching from server after getting user id.Howdah
This will reset the state in Messages. Take a look at another answer here on how to keep the Messages state using an builder: (...) => Messages..update(previousMessages) https://mcmap.net/q/383412/-how-to-use-a-provider-inside-of-another-provider-in-flutterMorehead
B
31

From version >=4.0.0, we need to do this a little differently from what @updatestage has answered.

return MultiProvider(
  providers: [
    ChangeNotifierProvider(builder: (_) => Auth()),
    ChangeNotifierProxyProvider<Auth, Messages>(
      update: (context, auth, previousMessages) => Messages(auth),
      create: (BuildContext context) => Messages(null),
    ),
  ],
  child: MaterialApp(
    ...
  ),
);
Burget answered 19/4, 2020 at 9:25 Comment(4)
The create is actually optional, so you don't have to specify it.Deshabille
@user1032613 it is not optional anymore for ChangeNotifierProxyProvider, but for ProxyProvider.Auspicious
@Deshabille No, it's required as you can see from required Create<R> create,. This is also true in its other forms such as ChangeNotifierProxyProvider2(), ChangeNotifierProxyProvider3, etc.Cartilaginous
@Cartilaginous You might be necro-posting to an old comment... software evolves over time, and we are no longer at version 4.Deshabille
T
15

Passing another provider in the constructor of the ChangeNotifierProxyProvider may cause you losing the state, in that case you should try the following.

ChangeNotifierProxyProvider<MyModel, MyChangeNotifier>(
  create: (_) => MyChangeNotifier(),
  update: (_, myModel, myNotifier) => myNotifier
    ..update(myModel),
);
class MyChangeNotifier with ChangeNotifier {
  MyModel _myModel;

  void update(MyModel myModel) {
    _myModel = myModel;
  }
}
Turbinate answered 14/5, 2021 at 13:59 Comment(1)
I should have read your comment before implementing the above solutions. I wasted like 4 hrs trying to figure out why state was being lostVerticillate
W
3

It's simple: the first Provider provides an instance of a class, for example: LoginManager. The other Provides MessageFetcher. In MessageFetcher, whatever method you have, just add the Context parameter to it and call it by providing a fresh context.

Perhaps your code could look something like this:

MessageFetcher messageFetcher = Provider.of<ValueNotifier<MessageFetcher>>(context).value;
String message = await messageFetcher.fetchMessage(context);

And in MessageFetcher you can have:

class MessageFetcher {
  Future<String> fetchMessage(BuildContext context) {
    LoginManager loginManager = Provider.of<ValueNotifier<LoginManager>>(context).value;
    loginManager.ensureLoggedIn();
    ///...
  }
}
Widgeon answered 3/9, 2019 at 6:43 Comment(2)
So instead of DI through the constructor, you do it through Provider.of?Hammad
Yes it's one of the ways to access the provider. It is never done through the constructor AFAIK. context.watch or context.read are new ways to do the same thing. Another option is the Consumer widget.Widgeon
F
-1

Seems like this would be a lot easier with Riverpod, especially the idea of passing a parameter into a .family builder to use the provider class as a cookie cutter for many different versions.

Flamen answered 24/11, 2020 at 23:50 Comment(0)
P
-1

Why not just pass the context to the constructor of the provider, and use that context inside the provider as shown below???

///in your main provider config...
 MultiProvider(
  providers: [
    ChangeNotifierProvider(
      create: (context) => MyProvider(context),
    ),
    ChangeNotifierProvider(
      create: (_) => MyProviderB(),
    ),
  ],

///in your provider...
  class MyProvider with ChangeNotifier {
    BuildContext context;
    
    MyProvider(this.context);

    myFunction(){
      var data = this.context.read<MyProviderB>().data;
///some more code here...
    }
  }
Pallium answered 20/9, 2022 at 13:45 Comment(0)
K
-1
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'first_provider.dart';
import 'second_provider.dart';

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => FirstProvider()),
        ChangeNotifierProxyProvider<FirstProvider, SecondProvider>(
          create: (_) => SecondProvider(Provider.of<FirstProvider>(_, listen: false)),
          update: (_, firstProvider, secondProvider) => secondProvider..firstProvider = firstProvider,
        ),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Provider Example')),
        body: Consumer2<FirstProvider, SecondProvider>(
          builder: (context, firstProvider, secondProvider, child) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(firstProvider.data),
                Text(secondProvider.combinedData),
                ElevatedButton(
                  onPressed: () {
                    firstProvider.updateData('Updated Data');
                  },
                  child: Text('Update FirstProvider Data'),
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}
Kablesh answered 5/6 at 9:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.