StateNotifierProvider not updating state using HookWidget

Issue

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),
            )
          ],
        ),
      ),
    );
  }
}

Answered By – Alex Hartford

Answer Checked By – Cary Denson (FlutterFixes Admin)

Leave a Reply

Your email address will not be published.