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