Reputation: 177
I'm working on a Flutter application using Riverpod for state management. I have a TextFormField with a TextEditingController to keep track of the entered value. The problem is when I enter a value into the TextFormField, the screen does not update accordingly. Moreover, the ElevatedButton in my application is not responsive (it can't be pressed) even when the TextFormField is not empty.
Here is the snippet of my code:
final textFieldControllerProvider =
StateProvider<TextEditingController>((ref) => TextEditingController());
class MyWidget extends ConsumerWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final controller = ref.watch(textFieldControllerProvider);
return Scaffold(
appBar: AppBar(
title: const Text("Riverpod example"),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
TextFormField(
controller: controller,
onChanged: (value) {
ref.watch(textFieldControllerProvider.notifier).state.text =
value;
},
),
ElevatedButton(
onPressed: controller.text.isNotEmpty
? () {
print('Button pressed with value');
}
: null,
child: const Text('Submit'),
),
],
),
),
);
}
}
Actual operation:
Our two main issues:
Button responsiveness: buttons do not work as expected, even though they are set to activate when the text from the controller is not empty.
Cursor movement: the cursor moves unexpectedly to the left whenever text is entered.
We suspect that both the button unresponsiveness and the cursor moving to the left may be related to the widget update process.
Any suggestions on how to resolve these issues would be appreciated.
Upvotes: 1
Views: 143
Reputation: 4844
There are a number of errors in your code. Now I will help you fix them:
ref.watch
inside initState and callback. Use ref.read
instead:onChanged: (value) {
ref.read(textFieldControllerProvider.notifier).state.text = value;
},
TextEditingController
is. If you walk through the inheritance tree, it will look like this:Inheritance:
Object -> ChangeNotifier -> ValueNotifier<TextEditingValue> -> TextEditingController
This means that neither Provider
nor StateProvider
is adequate here. Use ChangeNotifierProvider
for this purpose:
final textFieldControllerProvider =
ChangeNotifierProvider<TextEditingController>(
(ref) => TextEditingController(),
);
Consumer
to avoid rebuilding the entire tree:Consumer(
builder: (context, ref, child) {
final text = ref.watch(
textFieldControllerProvider.select((value) => value.text),
);
print('#build ElevatedButton');
return ElevatedButton(
onPressed: text.isNotEmpty
? () {
print('Button pressed with value');
}
: null,
child: const Text('Submit'),
);
},
),
I used select
to keep track of only the field I want. In this case, we are tracking text
. Although this does not really matter here, because. text
is part of the main state, which we don't have access to.
Your complete code will look like this:
Wrapper to run in dartpad:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() => runApp(ProviderScope(child: MyApp()));
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: const Color.fromARGB(255, 18, 32, 47),
),
debugShowCheckedModeBanner: false,
home: const Scaffold(body: Center(child: MyWidget())),
);
}
}
Main code with all notes:
final textFieldControllerProvider =
ChangeNotifierProvider<TextEditingController>(
(ref) => TextEditingController(),
);
class MyWidget extends ConsumerWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
print('#build MyWidget');
return Scaffold(
appBar: AppBar(
title: const Text("Riverpod example"),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Consumer(
builder: (context, ref, child) {
final controller = ref.watch(textFieldControllerProvider);
print('#build TextFormField');
return TextFormField(
controller: controller,
);
},
),
Consumer(
builder: (context, ref, child) {
final text = ref.watch(
textFieldControllerProvider.select((value) => value.text),
);
print('#build ElevatedButton');
return ElevatedButton(
onPressed: text.isNotEmpty
? () {
print('Button pressed with value');
}
: null,
child: const Text('Submit'),
);
},
),
],
),
),
);
}
}
A simplified version would look like this (MyWidget
will be rebuilt whenever the text changes):
class MyWidget extends ConsumerWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final controller = ref.watch(textFieldControllerProvider);
print('#build MyWidget');
return Scaffold(
appBar: AppBar(
title: const Text("Riverpod example"),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
TextFormField(
controller: controller,
),
ElevatedButton(
onPressed: controller.text.isNotEmpty
? () {
print('Button pressed with value');
}
: null,
child: const Text('Submit'),
),
],
),
),
);
}
}
I also added print
methods so you can see the rebuild moments in the console.
Good coding!
Upvotes: 3
Reputation: 177
Short Solution.
final textFieldControllerProvider =
ChangeNotifierProvider<TextEditingController>(
(ref) => TextEditingController(),
);
class MyWidget extends ConsumerWidget {
const MyWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
print('#build MyWidget');
final controller = ref.watch(textFieldControllerProvider);
final text =
ref.watch(textFieldControllerProvider.select((value) => value.text));
return Scaffold(
appBar: AppBar(
title: const Text("Riverpod example"),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
TextFormField(
controller: controller,
),
ElevatedButton(
onPressed: text.isNotEmpty
? () {
print('Button pressed with value');
}
: null,
child: const Text('Submit'),
)
],
),
),
);
}
}
Upvotes: 0