River_pod: Passing StateNotifier as argument to a ConsumerWidget

Created on 23 Sep 2020  路  8Comments  路  Source: rrousselGit/river_pod

I am really new to Flutter, so excuse me if this is suppose to be like this. Feel free to remove this issue if neccessary.

Description
I have created a ConsumerWidget called ListViewWidget, this takes in a provider. Unfortunately, inside this scope, I cannot call
provider.state. Following exception is given:

_Class 'StateNotifierProvider' has no instance getter 'state'.
Receiver: Instance of 'StateNotifierProvider'
Tried calling: state_

file: main.dart
void main() {
  runApp(ProviderScope(child: MyApp()));
}

final provder = StateNotifierProvider((ref) => ListNotifier(List<Item>(5)));

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        ......
        home: MyHomePage());
  }
}

class MyHomePage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    return Scaffold(
      body: Center(child: ListViewWidget(provider)),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          context.read(provider).add(Item("exampleId"));
        },
      ),
    );
  }
}

file: list-view.dart

class ListViewWidget extends ConsumerWidget {
  final provider;

  ListViewWidget(this.provider, {Key key}) : super(key: key);

  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final item = watch(provider.state);

    return GridView.count(
      crossAxisCount: 3,
      children: List.generate(item.length, (index) {
        return Center(child: Icon(Icons.folder));
      }),
    );
  }
}

The only working solution for this is to send provider.state when I have the ConsumerWidget in a seperate file. Why shouldn't we be able to send provider as it is and then call provider.state inside the ConsumerWidget?

      body: Center(child: ListViewWidget(provider.state)),
bug needs triage

All 8 comments

Seems like you have missed specifying types and changing name on the parameter compared to the actual _provider.

I tried recreating the issue with the folowing code:

class NumbersNotifier extends StateNotifier<List<int>> {
  NumbersNotifier() : super([]);

  void add(int number) {
    state = [...state, number];
  }

  void delete(int number) {
    state = [
      for (final loopNumber in state) if (number != loopNumber) loopNumber,
    ];
  }
}
void main() {
  runApp(ProviderScope(child: MyApp()));
}

final _provider = StateNotifierProvider((ref) => NumbersNotifier());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: ListViewWidget(_provider)),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          context.read(_provider).add(5);
        },
      ),
    );
  }
}


// In another file
class ListViewWidget extends ConsumerWidget {
  final StateNotifierProvider<NumbersNotifier> passedProvider;

  ListViewWidget(this.passedProvider, {Key key}) : super(key: key);

  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final item = watch(passedProvider.state);

    return GridView.count(
      crossAxisCount: 3,
      children: List.generate(item.length, (index) {
        return Center(child: Icon(Icons.folder));
      }),
    );
  }
}

This seems to be working as expected. If I remove the type from the passedProvider I recieve the same error as you which I think is expected, but @rrousselGit can probably fill in here with more details :)

@socketopp @RobertBrunhage If your use ConsumerWidget then your don't need use any provider like parameter, ScopedReader watch all providers. Your can remove provider from the ListViewWidget and watch it with ScopedReader this is a power of the ConsumerWidget. Then if your need update List just do it from context context.read(anyProvider).add(5); in the StatelessWidget or any other.

@iamarnas Maybe I misunderstood but the question is how you would send a provider through the contructor which can be a valid case if you use private providers to limit the scope.

I updated my code example to make this more obvious!

@RobertBrunhage With Riverpod don't need send a provider through the contructor 馃榿 Consumer read them all in the UI.

@iamarnas If the provider is private (the provider can only be used in that file) and the widget is in another file you have to pass it through the constructor, or else you can not access it.

@RobertBrunhage Yes. But I personally privat providers only combine with anothers providers otherwise I leave them globals. Send a provider through the contructor is to boilerplate to me.

@RobertBrunhage, @iamarnas, I have just updated my code so you can copy and test it.

I just discovered that the error, i.e. difference between @RobertBrunhage and my code.
It seems that it works to pass the provider as parameter, but if you do, one have to specifify the type:
final StateNotifierProvider<ListNotifier> _provider;
When adding the type, the error goes away.

@socketopp Wrote that in the bottom of my first comment 馃槂

This seems to be working as expected. If I remove the type from the passedProvider I recieve the same error as you which I think is expected

Glad it is working for you now!

just be aware that if you have your provider not private you don't have to pass it through the constructor, as @iamarnas was talking about!

Was this page helpful?
0 / 5 - 0 ratings