11

I want use provider (ChangeNotifierProvider) and ChangeNotifier for manage app state. But how I can access state from one model in another model?

Use case: In chat app, one model for store user information. Other model use user information (for example user id) to make call to database (Firestore) and get stream of chat data.

For example:

class Model1 extends ChangeNotifier {
  final List<Item> items = [];

class Model2 extends ChangeNotifier {
//Access items from Model1 here
items;

Is this possible? I not like have very big Model because is hard to maintain.



Thanks!

FlutterFirebase
  • 2,163
  • 6
  • 28
  • 60

5 Answers5

12

Using provider, one model doesn't access another model.

Instead, you should use ProxyProvider, to combine a model from others.

Your models would look like:

class Foo with ChangeNotifier {
  int count = 0;

  void increment() {
    count++;
    notifyListeners();
  }
}

class Bar with ChangeNotifier {
  int _count;
  int get count => _count;
  set count(int value) {
    if (value != count) {
      _count = value;
      notifyListeners();
    }
  }
}

And then you can use ChangeNotifierProxyProvider this way (assuming there's a `ChangeNotifierProvider higher in your widget tree):

ChangeNotifierProxyProvider<Foo, Bar>(
  initialBuilder: (_) => Bar(),
  builder: (_, foo, bar) => bar
    ..count = foo.count, // Don't pass `Foo` directly but `foo.count` instead
)
Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • 1
    Thanks for reply! I am try understand how this work for make API call. Are you say we cannot access another model from within a model, so we use `ChangeNotifierProxyProvider` to pass state from `Model1` to `Model2`? – FlutterFirebase Jun 27 '19 at 20:49
  • 1
    Hello Rémi, I have a question. What would happen if in your example `Bar` needs to call some business logic function defined in `Foo`? Can `Bar` get the `Foo` `ChangeNotifier` instance or should it always be avoided? In my use case I have a screen with tabs with a "Parent ChangeNotifier" and different ChangeNotifiers for each tab. Sometimes a tab needs to trigger a change in the "parent". Are there other better alternatives? Thanks for your great work! – Martyns Jul 17 '19 at 07:29
  • @Rémi Rousselet what is the reason you put ".." before calling count – Janaka Aug 03 '20 at 09:57
  • @Rémi Rousselet, I have three Provider, and inone of them, I would like to have access of the other two. I tried: ChangeNotifierProxyProvider( create: (BuildContext context) => Einkauf(null), update: (context, provider1, provider2, myNotifier) => myNotifier..update(provider1, provider2))... But it is only allowed for one argument. Except this challenge, your package is awesome! – betzebube1000 Aug 21 '20 at 17:55
  • In my experience using `ChangeNotifierProxyProvider` becomes nightmare when things become more complex over time. I don't use this approach. Instead, I use Services as per Clean Architecture. Services don't know anything about business logic and they are shared across app via service locator, like `get_it` package for example. – Kirill Karmazin Sep 06 '20 at 14:42
  • @Janaka `..count` is the use of [cascade notation](https://dart.dev/guides/language/language-tour#cascade-notation). That way, you can assign `bar.count = foo.count` but by using cascade notation `..` your builder function is actually returning `bar`, not `bar.count`. It's just a shorthand for `builder: (_, foo, bar) { bar.count = foo.count; return bar; }` – maganap May 11 '21 at 14:44
5

From v4 the ChangeNotifierProxyProvider the provider can be constructed like so:

ChangeNotifierProxyProvider<Foo, Bar>(
  create: (_) => Bar(),
  update: (_, foo, bar) => bar
    ..count = foo.count,
)

Notice that initialBuilder: has changed to create: and builder: to update:

Iain Smith
  • 9,230
  • 4
  • 50
  • 61
  • Hi I have dumb question why do you do `bar..count = foo.count` instead of `Bar(foo.count)`? You reuse the old instance if you do the .. right? – user1354934 Sep 15 '20 at 17:46
  • @user1354934 it's not dumb specially for newcommers! If we were to use `update: (_, foo, bar) => Bar(foo.count)`we end up passing in as a prop and won't require a setter. Because of `ProxyProvider` every provider will update when a dependency change; it is what we want. But the main difference is that every value stored in `Bar` provider will be lost if not passed and consumed by the last param (`bar`) which is the previousState. – Underscore Jan 14 '21 at 12:07
1

I have tried to implement this simply.

Provider version ^4.3.3

Here i given a simple example

main.dart

ChangeNotifierProxyProvider<AuthProvider, ProductsProvider>(
       create: (context) => ProductsProvider(),
       update: (context, auth, previousProducts) => previousProducts
            ..update(auth.token, previousProducts.items == null ? [] : previousProducts.items),
),

authProvider.dart

class AuthProvider with ChangeNotifier {
  String _token;
  // _token is a response token.
  String get token {
    return _token;
}

productsProvider.dart

Product is a class of single product

class ProductsProvider with ChangeNotifier {
  List<Product> _items = [];
  String _authToken;
  void update(authToken, items) {
    _items = items;
    _authToken = authToken;
    notifyListeners();
  }
}

Product.dart

class Product with ChangeNotifier {
  final String id;
  final String title;
  final String description;
  final double price;
  final String imageUrl;
  bool isFavorite;

  Product({
    @required this.id,
    @required this.title,
    @required this.description,
    @required this.price,
    @required this.imageUrl,
    this.isFavorite = false,
  });
}
Mijanur Rahman
  • 1,094
  • 1
  • 11
  • 20
  • This code, I think we are using same Udemy course. I am using provider: ^5.0.0. When I click logout button, ProductsOverviewScreen is re-rendering again and call fetchAndSetProducts function again. Then I got error because we already clear userId and token. Do you know how to solve this issue. – Alex Aung Jun 27 '21 at 15:27
0

I'm trying to implement the same scenario, here is my code: Main file:

        ChangeNotifierProvider<SharedPreferencesData>(
      create: (context) => SharedPreferencesData(),
    ),
    ChangeNotifierProxyProvider<SharedPreferencesData, DatabaseService>(
      create: (_) => DatabaseService(),
      update: (_, sharedPreferencesData, databaseService) {
        print('ChangeNotifierProxyProvider RAN');
        databaseService..setSpCompanyId = sharedPreferencesData.sPCompanyId;
        return null;
      },
    ),

SharedPreferencesData File:

    class SharedPreferencesData with ChangeNotifier {
    String sPCompanyId = '';
    void setCompanyId(String cpID) {
     sPCompanyId = cpID;
     print('setCompanyId sPCompanyId:$sPCompanyId');
     notifyListeners();
    }
  }

DataBaseService file:

class DatabaseService with ChangeNotifier {
  String sPCompanyId = 'empty';
  String get getSpCompanyId => sPCompanyId;

  set setSpCompanyId(String value) {
    print('value in setter database before if: $value');
    if (value != getSpCompanyId) {
      print('value in setter database: $value');
      sPCompanyId = value;
      notifyListeners();
    }
  }

The DatabaseService Class doesn't update. I've added the prints to figure out what is running and what is not. When I run my code, the prints in Shared_Preferences / setCompanyId method RUNS correctly. Yet the print function in main.dart file print('ChangeNotifierProxyProvider RAN'); AND in DataBaseService file print('value in setter database: $value'); does not run.

What am I missing? Why the notify sPCompanyId setter notifyListeners() in SharedPreferencesData is not updating DatabaseService String sPCompanyId through the ChangeNotifierProxyProvider? Thanks!

YaGeorge
  • 47
  • 10
0

I did Something like this. We are passing "items".

>class Model1 extends ChangeNotifier {
  final List<Item> items = [];
}

>class Model2 extends ChangeNotifier {
//Access items from Model1 here
 Model1 items;
Model2(this.items);
// Now I can access items in the entire class. 
}

>ChangeNotifierProxyProvider<Model1, Model2>(
//create the class to pass the value, which is M2. 
  create: (_) => Model2(Provider.of<Model1>(context)),
  update: (_, model1, model2) => model2..items
)



>Model2 model2 = Provider.of<Model2>(context, listen: false);
>>Text(model2.items),
Ilidio Nzage
  • 321
  • 3
  • 3