6

So I have this ChangeNotifierProvider high in my widget tree as I am seeing many children widgets to listen to its value.

What I am currently doing is that I pass down the Provider.of(context) object from the parent widget into it's children via constructors whenever I am to reuse some values/functions on my children widgets. For example, everytime I create a Provider.of(context) object for my children widgets, it seems that it does not carry over the updated values I have on the Parent Provider but rather this one has my default null/0/'empty' ones like it has only been created. This lead me to pass down the initial Provider.of(context) object to each children that will use the updated values and functions of the ChangeNotifier.

This setup is working for me, however, when my Widget Tree has started being complex, I am constantly passing down values through each widget and to some that do not even use it at all just for its children to listen to the main provider.

I think what I may be doing now is anti-pattern of the Provider Architecture, I am hoping you guys can help me on a more optimized and efficient way of doing this.

Thank you very much!

P.S. There are some things in the documentation that I am not yet quite grasping properly.

Edits Below to include sample code and visualization:

provider_type.dart

class ProviderType extends ChangeNotifier{
    String valueA = '';
    String valueB = '';
}

home.dart

import ..provider_type.dart
...
    Widget build(BuildContext context){
        return ChangeNotifierProvider<ProviderType>(
            create: (context) => ProviderType(),
            child: ScreenColumn();
        );
    }
...

screen_column.dart

import ..screen_a.dart
import ..screen_b.dart
class ScreenColumn extends StatelessWidget{
    Widget build(BuildContext context){
        var providerType = Provider.of<ProviderType>(context);

        return Column(
            children: <Widget>[
                ScreenA(providerType: providerType),
                ScreenB(providerType: providerType),
            ],
        );
    }
}

screen_a.dart

class ScreenA extends StatelessWidget{
    final ProviderType providerType;

    ScreenA({this.providerType});

    Widget build(BuildContext context){
        return Text(
            '${providerType.valueA}'
        );
    }
}

screen_b.dart

import ..screen_c.dart
class ScreenB extends StatelessWidget{
    final ProviderType providerType;

    ScreenB({this.providerType});

    Widget build(BuildContext context){
        return ScreenC(providerType: providerType);
    }
}

screen_c.dart

class ScreenC extends StatelessWidget{
    final ProviderType providerType;

    ScreenB({this.providerType});

    Widget build(BuildContext context){
        return Column(
        children: <Widget>[
            Text(
                '${providerType.valueA}'
            )
            Text(
                '${providerType.valueB}'
            )
            Text(
                '${providerType.valueC}'
            )
        ]
        );
    }
}

Visualization

enter image description here

So what I am currently doing is to pass down the object providerType from ScreenColumn to Screens A, B, and C just so each of them have the same "Source of Values". Cause when I try to make different Provider.of objects and use them, they do not share the same updated values when I do some computation.

Is there something I can do to make this more efficient or is there a better way that I need to do?

AverageCoder
  • 321
  • 6
  • 15
  • I do not quite understand your problem. Could you share a small code snippet to help visualize the issue? – Rémi Rousselet Feb 19 '20 at 11:33
  • Oh my, it's you! I'll just make some quick samples – AverageCoder Feb 19 '20 at 11:35
  • Hey @RémiRousselet, I have updated my question with the samples that you asked. Hoping it helps – AverageCoder Feb 19 '20 at 11:55
  • What do you mean by "they do not share the same updated values when I do some computation." – Rémi Rousselet Feb 19 '20 at 13:21
  • That phrase applies when I do not inherit to the children the same Provider.of object of the parent. For example, in my parent Provider.of object, I have update the value of "x" to "1", that same value carries over to the children when I pass it down. But when I make another Provider.of object, when I try to call the value of "x" again, it'll tell me that the value of "x" was my initial value of "0" and not like the same one as the parent Provider.of object. – AverageCoder Feb 19 '20 at 13:27
  • I'm not saying that it's a bug eh, I am just explaining what I am doing right now to access the same values of the Provider.of object and perhaps there's a better way to improve what I currently do as there'll be a ton of child widgets who'll have to inherit the same Provider.of object. – AverageCoder Feb 19 '20 at 13:28
  • Sorry I still do not understand. Could you showcase that? – Rémi Rousselet Feb 19 '20 at 14:13
  • Hey @RémiRousselet, I'm so sorry bro. After some trial and error, I realized that I am actually wrong with my understanding and implementation, and have just missed some core concepts. It appears that I can just actually create new Provider.of objects on each child widget and they'll just listen to my parent provider for both values and functions. My experimentation proved it. -_- I don't know what to say, sorry for the trouble and thank you for taking time to help me. My bad. – AverageCoder Feb 19 '20 at 15:11
  • So problem, have a good day! – Rémi Rousselet Feb 19 '20 at 15:51

1 Answers1

9

To those who may be wondering or are searching for answers to the same question, look at my sample code below that shows how you can reuse/share your Provider Values and Functions at any point in your widget tree as long as they are under your Parent Provider.

And yes, you can actually just create Provider.of Objects anywhere in your tree without passing down the initial Provider.of object that you have created.

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

class ProviderType extends ChangeNotifier {
  String value = DateTime.now().toString();

  changeValue() {
    value = DateTime.now().toString();
    notifyListeners();
  }
}

void main() => runApp(AppIndex());

class AppIndex extends StatelessWidget {
  const AppIndex({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<ProviderType>(
      create: (context) => ProviderType(),
      child: MaterialApp(
        home: Home(),
      ),
    );
  }
}

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var providerType = Provider.of<ProviderType>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('Sample App'),
      ),
      body: ScreenColumn(),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: () => providerType.changeValue(),
        label: Text('ChangeValue'),
      ),
    );
  }
}

class ScreenColumn extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
        child: Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: <Widget>[
        ScreenA(),
        ScreenB(),
        ScreenC(),
        ScreenC(),
      ],
    ));
  }
}

class ScreenA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var providerType = Provider.of<ProviderType>(context);
    return Card(
      color: Colors.red,
      elevation: 8.0,
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Text(providerType.value),
      ),
    );
  }
}

class ScreenB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var providerType = Provider.of<ProviderType>(context);
    return Card(
      color: Colors.blue,
      elevation: 8.0,
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            Text(providerType.value),
            ScreenC(),
            ScreenC(),
          ],
        ),
      ),
    );
  }
}

class ScreenC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // var providerType = Provider.of<ProviderType>(context);

    return Card(
      color: Colors.green,
      elevation: 8.0,
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            Text('This is Screen B with no Provider.of Object'),
            ScreenD(),
            ScreenD(),
            ScreenD(),
          ],
        ),
      ),
    );
  }
}

class ScreenD extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var providerType = Provider.of<ProviderType>(context);
    return Card(
      color: Colors.yellow,
      elevation: 8.0,
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            Text(
                'This is Screen D. A Provider.of object was created here without inheriting the Parent\'s Provider.of object.'),
            Text(providerType.value),
          ],
        ),
      ),
    );
  }
}
AverageCoder
  • 321
  • 6
  • 15
  • hi @AverageCoder, does that also apply to buildMethods in Flutter or is it a better practice to pass 1 Provider as an argument down here? – Nuqo May 16 '21 at 18:59
  • 2
    Hello @Nuqo, do you mean passing down the instance of the provider to each widget as a parameter? From my understanding, it would be counter-intuitive to do that as that would be troublesome for widgets way down in your widget tree - you would make unecessary passes on widgets not using it. The great thing with provider is you can call the instance of a top level provider object anywhere down in your widget tree with "provider.of" or with other helpers so you won't have to pass them down to each widget. In my case here, I have a single main provider for the whole widget tree. – AverageCoder May 17 '21 at 19:59
  • Excellent answer! – apxcode May 29 '21 at 19:11