updatestage
updatestage

Reputation: 831

How to use a provider inside of another provider in Flutter

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)?

Upvotes: 45

Views: 29200

Answers (7)

suraj singh
suraj singh

Reputation: 29

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'),
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}

Upvotes: -1

user8604852
user8604852

Reputation: 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...
    }
  }

Upvotes: -1

ltk
ltk

Reputation: 1292

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;
  }
}

Upvotes: 16

updatestage
updatestage

Reputation: 831

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();
        }
    }

    ...
}

Upvotes: 37

Randal Schwartz
Randal Schwartz

Reputation: 44056

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.

Upvotes: -1

AnandShanbhag
AnandShanbhag

Reputation: 7100

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(
    ...
  ),
);

Upvotes: 33

Gazihan Alankus
Gazihan Alankus

Reputation: 11984

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();
    ///...
  }
}

Upvotes: 3

Related Questions