5

Flutter riverpod is not notifying the Consumer on the state change when the StateNotifier's type is List, while the same implementation works just fine for other types.

here, I provided a minimal reproducable example:

import 'dart:math';

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

void main() {
  runApp(MyApp());
}

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

class CounterState extends StateNotifier<List<int>> {
  static final provider = StateProvider(
    (ref) => CounterState(),
  );

  int get last {
    print('last');
    return state.last;
  }

  int get length {
    print('len');
    return state.length;
  }

  // the body of this will be provided below
  add(int p) {}

  CounterState() : super(<int>[0]);
}

class MyHomePage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, watch) {
    void _incrementCounter() {
      final _count = Random.secure().nextInt(100);

      context.read(CounterState.provider.notifier).state.add(_count);
    }

    var count = watch(CounterState.provider.notifier).state.length;

    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Text(
          'You have pushed the button this many times: $count',
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        child: Icon(Icons.add),
      ),
    );
  }
}

as for the add method, I tried implementing it in a lot of ways, but neither works.

here is what I tried:

1: just add it straight away:

add(int p) {
  state.add(p);
}

2: I also tried the solution suggested in this answer:

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

3: I tried to destroy the list entirely, and reassign it:

add(int p) {
  final _state = [];

  // copy [state] to [_state]
  for (var item in state) {
    _state.add(item);
  }

  // empty the state
  state = [];

  // add the new element
  _state.add(p);

  // refill [state] from [_state]
  for (var item in _state) {
    state.add(item);
  }


  print(state.length); // it continues until here and prints
}
Adnan
  • 906
  • 13
  • 30

2 Answers2

5

Firstly, you are not creating the correct provider to listen to a StateNotifier. You need to change this:

static final provider = StateProvider(
  (ref) => CounterState(),
);

to this:

static final provider = StateNotifierProvider<CounterState, List<int>>(
  (ref) => CounterState(),
);

Please refer to the Riverpod documentation about the different types of providers.

Secondly, you are not actually watching for state changes, but you are just getting the state object from the notifier.

Change this line:

var count = watch(CounterState.provider.notifier).state.length;

to this:

final count = watch(CounterState.provider).length;

also, your increment method is not correct for StateNotifier providers. Please change this:

context.read(CounterState.provider.notifier).state.add(_count);

to this:

context.read(CounterState.provider.notifier).add(_count);

It should rebuild now when the state changes. However, you do need an implementation of your add method that actually changes the state object itself. I would suggest the second variant you mentioned, that is in my opinion the nicest way to do this:

add(int p) {
  state = [...state, p];
}
TmKVU
  • 2,910
  • 2
  • 16
  • 30
  • when I remove the `.state` and make the line just `final count = watch(CounterState.provider).length;`, I am getting this error: `The getter 'length' isn't defined for the type 'StateController'. Try importing the library that defines 'length', correcting the name to the name of an existing getter, or defining a getter or field named 'length'.` – Adnan Aug 09 '21 at 12:22
  • 1
    Ah I see your provider is a `StateProvider` instead of a `StateNotifierProvider`. I'll edit the answer to also include this fix – TmKVU Aug 09 '21 at 12:25
  • I updated it as your suggestion, but it still is not working. – Adnan Aug 09 '21 at 12:27
  • 1
    Did you update to the `StateNotifierProvider`? – TmKVU Aug 09 '21 at 12:29
  • Yes, I did that too but it still is not upadting. – Adnan Aug 09 '21 at 12:31
  • 1
    Did you also update your increment method? That is of course also not valid for a `StateNotifier` object – TmKVU Aug 09 '21 at 12:34
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/235784/discussion-between-adnan-and-tmkvu). – Adnan Aug 09 '21 at 12:37
  • 1
    I think you are a bit confused about `StateProvider` and `StateNotifierProvider`. You are creating a `StateNotifier` class so you need a `StateNotifierProvider`. You'll find more in [the documentation](https://pub.dev/documentation/riverpod/latest/riverpod/StateNotifierProvider-class.html) – TmKVU Aug 09 '21 at 12:37
2

@TmKVU explained well, so I'm skipping that part. You can also follow riverpod document.

here is my example of riverPod:

Your widget

import 'dart:math';

import 'package:stack_overflow/exports.dart';

class CounterState extends StateNotifier<List<int>> {
  static final provider = StateNotifierProvider(
    (ref) => CounterState(),
  );

  int get last {
    print('last');
    return state.last;
  }

  int get length {
    print('len');
    return state.length;
  }

  // the body of this will be provided below
  add(int p) {}

  CounterState() : super(<int>[0]);
}

class MyHomePageSSSS extends ConsumerWidget {
  @override
  Widget build(BuildContext context, watch) {
    void _incrementCounter() {
      final _count = Random.secure().nextInt(100);

      context.read(CounterState.provider.notifier).state =
          context.read(CounterState.provider.notifier).state..add(_count);
    }

    final countprovider = watch(CounterState.provider);

    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Text(
          'You have pushed the button this many times: ${countprovider.length}',
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        child: Icon(Icons.add),
      ),
    );
  }
}


Md. Yeasin Sheikh
  • 54,221
  • 7
  • 29
  • 56