flutterdartriverpodflutter-hooks

StateNotifierProvider not updating state using HookWidget


I am trying to use Riverpod state management. I have two TextFormField and I want to set the value of a Text by taking the sum of the values entered in each of the fields using a StateNotifierProvider.

In the following code, CashCounterData is a data model to be used by the StateNotifier, CashCounter. The notifier has two methods, setCount and setCash that are called in the onChanged method of each TextFormField.

final cashProvider = StateNotifierProvider<CashCounter, CashCounterData>((ref) => CashCounter());

class CashCounter extends StateNotifier<CashCounterData> {
  CashCounter() : super(_initialData);

  static const _initialData = CashCounterData(0, 0);

  void setCount(int value){
    state = CashCounterData(value, state.cash);
  }

  void setCash(value){
    state = CashCounterData(state.count, value);
  }

  int get count => state.count;
  int get cash => state.cash;
}

class CashCounterData {
  final int count;
  final int cash;

  const CashCounterData(this.count, this.cash);
}

Next, I implemented the UI and am trying to tie in the StateNotifierProvider defined above. However, when I enter values into each TextFormField, the Text widget is always displaying 0.

class CalculatableTextFormField extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final cashCounterProvider = useProvider(cashProvider.notifier);
    final TextEditingController _count = TextEditingController();
    final TextEditingController _cash = TextEditingController();
    return Scaffold(
      body: Form(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
                '${cashCounterProvider.count + cashCounterProvider.cash}'
            ),
            TextFormField(
              controller: _count,
              keyboardType: TextInputType.number,
              onChanged: (value)=>cashCounterProvider.setCount(int.parse(value)),
            ),
            TextFormField(
              controller: _cash,
              keyboardType: TextInputType.number,
              onChanged: (value)=>cashCounterProvider.setCash(int.parse(value)),
            )
          ],
        ),
      ),
    );
  }
}

What am I missing to get my desired behavior?


Solution

  • You are watching the notifier, not the state. The state is what gets changed, and therefore notifies listeners.

    It should work if you just change:

    final cashCounterProvider = useProvider(cashProvider.notifier);
    

    to:

    final cashCounterProvider = useProvider(cashProvider);
    

    Then, in your change handlers:

    onChanged: (value) => context.read(cashProvider.notifier).setCash(int.tryParse(value) ?? 0),
    

    When using a provider in a handler like this, prefer context.read as demonstrated above to avoid unnecessary rebuilds.

    You also need to use hooks if you are putting your TextEditingControllers in the build method.

    final TextEditingController _count = useTextEditingController();
    final TextEditingController _cash = useTextEditingController();
    

    All together, your solution is the following:

    class CalculatableTextFormField extends HookWidget {
      @override
      Widget build(BuildContext context) {
        final cashCounterProvider = useProvider(cashProvider);
        final TextEditingController _count = useTextEditingController();
        final TextEditingController _cash = useTextEditingController();
        return Scaffold(
          body: Form(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('${cashCounterProvider.count + cashCounterProvider.cash}'),
                TextFormField(
                  controller: _count,
                  keyboardType: TextInputType.number,
                  onChanged: (value) =>
                      context.read(cashProvider.notifier).setCount(int.tryParse(value) ?? 0),
                ),
                TextFormField(
                  controller: _cash,
                  keyboardType: TextInputType.number,
                  onChanged: (value) =>
                      context.read(cashProvider.notifier).setCash(int.tryParse(value) ?? 0),
                )
              ],
            ),
          ),
        );
      }
    }