Lee
Lee

Reputation: 421

Flutter Provider. How to have multiple instances of the same provider type?

So I’m a little confused about how to port an application using setstate to provider.

Let’s say I have a person model which extends provider. Now I also want to have multiple person reports. Each report has it’s own properties such, title, created date etc plus a list of people.

The trouble I’m having understanding is how would I create a provider per person and per person report? All the examples I’ve seen of using provider appear to have just one instance of the provider.

Effectively I would want to be able to edit person ‘A’s values without it effecting person ‘B’s

EDIT:

Each instance of my object will need a separate state. How can provider manage states of multiple objects all of the same type?.

EDIT 2:

I’ll try to clarify further with a different example. Imagine the counter widget that is the default example when creating a flutter app. What if I require a list of dynamically created counters (maybe 10, maybe 100 of them). Would each counter would have it’s own provider controlling it’s state and if so, how would we create that?

Upvotes: 5

Views: 8335

Answers (5)

Michael Wang
Michael Wang

Reputation: 438

I keeps bumping into this issue when using provider package, so I think it's time to come up with a more 'generic' solution.

Let's define the problem: I need to provide values of same class using provider package, and a consumer to get rebuild when corresponding value is changed. And to keep it simple, ProxyProvider is not supported here.

By creating a notifier:

class MultiValueNotifier<E extends Enum, T> extends ChangeNotifier {
  // Map of name to value.
  final values = <E, T>{};

  add(E name, T value) {
    values[name] = value;
    notifyListeners();
  }

  remove(E name) {
    if (values.remove(name) != null) {
      notifyListeners();
    }
  }

  get(E name) => values[name];
}

And a consumer:

class ValueConsumer<E extends Enum, T>
    extends Selector<MultiValueNotifier<E, T>, T> {
  ValueConsumer({
    Key? key,
    required E name,
    required ValueWidgetBuilder<T> builder,
    ShouldRebuild<T>? shouldRebuild,
    Widget? child,
  }) : super(
          key: key,
          shouldRebuild: shouldRebuild,
          builder: builder,
          selector: (_, notifier) => notifier.get(name),
          child: child,
        );
}

I can provide multi values of same class like this:

enum Values {
  one,
  two;
}

// This is the class we want to have multiple instances and provide them in Providers.
class Foo {}

class MyWidget extends StatelessWidget {
  final notifier = MultiValueNotifier<Values, Foo>();
  
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers[
        ChangeNotifierProvider<MultiValueNotifier<Values, Foo>>.value(value: notifier),
      ];
      child: // ...
    );
  }
  
  valueOneReady(Foo foo) {
    notifier.add(Values.one, foo);
  }
  
  valueTwoReady(Foo foo) {
    notifier.add(Values.two, foo);
  }
}

And consume them in different widgets:

class WidgetOne extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ValueConsumer(
      name: Values.one,
      builder: (context, value, child) {
        // value is 'foo' from valueOneReady
        // ...
      }
    );
  }
}

class WidgetTwo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ValueConsumer(
      name: Values.two,
      builder: (context, value, child) {
        // value is 'foo' from valueTwoReady
        // ...
      }
    );
  }
}

In notifier, I use enum as Map key just to prevent access/provide un-intended value. You can remove it and use String or int as Map key, which is more suitable for OP's use case.

I share these code in my gist

Upvotes: 0

chrisrn
chrisrn

Reputation: 13

I don't know if there is a way to create Providers dynamically, like in the example of edit2, but if you want to create multiple providers of the same type without repeating code you could pass a type T to the class which extends ChangeNotifier.

class Counter<T> extends ChangeNotifier {
    late int value;

    Counter() { value = 0; }

    int get getValue => value;

    void increase() {
        value += 1;
        notifyListeners();
    }
}

Passing different types should create different classes in memory that will manage the state of these different counters. Illustration:

class A {}
class B {}
class C {}

MultiProvider(
    providers: [
        ChangeNotifierProvider(
            create: (_) => Counter<A>()),
        ChangeNotifierProvider(
            create: (_) => Counter<B>()),
        ChangeNotifierProvider(
            create: (_) => Counter<C>()),
    ],
    child: Home(),
);

Upvotes: 0

Baker
Baker

Reputation: 27990

Your state objects must be uniquely named types, otherwise, Provider can't distinguish them.

You could create a base Person class and then extend that base class into PersonA, PersonB, etc.

Then they can be controlled separately, but still rely on the base class' functionality.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class Person with ChangeNotifier {
  String name;

  Person(this.name);

  void changeName(String newName) {
    name = newName;
    notifyListeners();
  }
}

class PersonA extends Person {
  PersonA(String value) : super(value);
}

class PersonB extends Person {
  PersonB(String value) : super(value);
}

class ProviderDuplicatePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => PersonA('Billy Boy')),
        ChangeNotifierProvider(create: (_) => PersonB('Francis')),
      ],
      child: Scaffold(
        appBar: AppBar(
          title: Text('Provider Duplicate Types'),
        ),
        body: DuplicateProviderStateObjects(),
      )
    );
  }
}

class DuplicateProviderStateObjects extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Column(
            children: [
              Text('Provider One:'),
              Text(Provider.of<PersonA>(context).name),
              ElevatedButton(
                child: Text('Change Person A'),
                onPressed: () => Provider.of<PersonA>(context, listen: false).changeName("Kong"),
              )
            ],
          ),
          Column(
            children: [
              Text('Provider Two:'),
              Text(context.watch<PersonB>().name),
              ElevatedButton(
                child: Text('Change Person B'),
                onPressed: () => Provider.of<PersonB>(context, listen: false).changeName("Godzilla"),
              )
            ],
          ),
        ],
      ),
    );
  }
}

Upvotes: 2

Fiaz Ali
Fiaz Ali

Reputation: 143

There is no hard rule on how to structure your data. From what i understand from you question :

 class Person{
  String name;
  Report reportData;
  
  Person(this.name, this.reportData);
}

class Report{
  String title;
  Map<String, dynamic> data;
  
  Report(this.title, this.data);
}

class PersonsData with ChangeNotifier
{
  List<Person> l1 = [];
  
  void addData()
  {
    l1.add(Person('John', Report('report1', {'dataTitle' : 'datDescription'})));
  }
}

Now with PersonsData class you can manage your persons. Every person has a report type object which represents the data within.

Upvotes: 1

Fatemeh Hasheminasab
Fatemeh Hasheminasab

Reputation: 47

I'm not sure if I'm understanding your question correctly, but a provider does not contain data, it just makes it available, and if you're talking about the provider package it helps other parts of your app get notified when changes have been done to your data. while a model is what you can make instances of.

Upvotes: 0

Related Questions