adding form with globalKey inside PageView.Builder - flutter

I have form and TextFromFilde inside PageView.builder, everytime chinge page it show me this error Duplicate GlobalKey detected in widget tree.
and some time that TextFormFilde is hideing.
all problome is GlobalKey, if I delete it every thing is working perfict but text filde is unfocused in every page I had to tap agin on it to type data
import 'package:flutter/material.dart';
void main() => runApp(Myapp());
class Myapp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: PageViewTest(),
);
}
}
class PageViewTest extends StatefulWidget {
#override
State<PageViewTest> createState() => _PageViewTestState();
}
List<TextEditingController> tecList;
var _formKey;
List controller = [
TextEditingController(),
TextEditingController(),
TextEditingController(),
];
List<String> _signing_hint_text = [
'type your domain',
'type your email',
'type your password',
];
List<String> _signing_input_label = [
'Domain',
'Email',
'Password',
];
Size mDeviceSize(BuildContext context) {
return MediaQuery.of(context).size;
}
PageController _pageController = PageController(initialPage: 0);
class _PageViewTestState extends State<PageViewTest> {
#override
void initState() {
// TODO: implement initState
_formKey = GlobalKey<FormState>();
// _pageController = PageController();
tecList = List.generate(3, (index) {
return TextEditingController();
});
super.initState();
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
margin: EdgeInsets.all(30),
child: PageView.builder(
controller: _pageController,
itemCount: 3,
itemBuilder: (context, index) {
return Column(
children: [
Form(
autovalidateMode: AutovalidateMode.onUserInteraction,
key: _formKey,
child: TextFormField(
autofocus: true,
textAlign: TextAlign.right,
textInputAction: TextInputAction.next,
style: TextStyle(color: Color(0xff030303)),
cursorColor: Color(0xff5e6593),
controller: tecList[index],
keyboardType: TextInputType.text,
decoration: InputDecoration(
hintText: _signing_hint_text[index],
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Color(0xff5e6593),
),
),
contentPadding:
EdgeInsets.symmetric(vertical: 5.0, horizontal: 10),
labelText: _signing_input_label[index],
labelStyle: TextStyle(color: Color(0xff5e6593)),
floatingLabelBehavior: FloatingLabelBehavior.auto,
border: OutlineInputBorder(),
alignLabelWithHint: true,
hintStyle: TextStyle(
color: Color(0xff5e6593),
fontSize: 15,
fontWeight: FontWeight.normal),
),
),
),
ElevatedButton(
onPressed: () {
_pageController.nextPage(
duration: Duration(milliseconds: 800),
curve: Curves.ease);
},
child: Text('click')),
],
);
}),
),
),
);
}
}

You can not use one formKey for all Form widgets in each page, you should define formKey for each page of your page view and use index to know which one is for which page. For example define three different formKey and use index like this:
List<GlobalKey<FormState>> formKeys = [GlobalKey<FormState>(),GlobalKey<FormState>(),GlobalKey<FormState>()];
and inside your PageView.builder, do this:
Form(
autovalidateMode: AutovalidateMode.onUserInteraction,
key: formKeys[index],
...
)
But For focus each TextFormField when page change, first define a list like this:
List<FocusNode> focusList = [FocusNode(), FocusNode(), FocusNode()];
then do this inside PageView.builder:
PageView.builder(
onPageChanged: (value) {
FocusScope.of(context).requestFocus(focusList[value]);
},
controller: _pageController,
itemCount: 3,
...
}
And also do not forget to pass those focusNode to TextFormField:
child: TextFormField(
focusNode: focusList[index],
autofocus: true,
textAlign: TextAlign.right,
...
)

Related

Save input values between widget rebuilds with Bloc Flutter

I have a form builded with Bloc package.
There are two options with textfields in it.
Switching between option i've made also with bloc and Visibility widget.
When I choose an option widget rebuilds, name and price values deletes.
What is the best way to save this values between choosing options?
Here is my Bloc code
class FormBloc extends Bloc<FormEvent, MyFormState> {
FormBloc() : super(MyFormState()) {
on<RadioButtonFormEvent>(_setRadioButtonState);
}
void _setRadioButtonState(
RadioButtonFormEvent event, Emitter<MyFormState> emit) async {
emit(RadioButtonFormState(
buttonIndex: event.buttonIndex,
buttonName: event.buttonName,
));
}
}
class MyFormState {}
class RadioButtonFormState extends MyFormState {
final int buttonIndex;
final String buttonName;
RadioButtonFormState({
required this.buttonIndex,
required this.buttonName,
});
}
abstract class FormEvent extends Equatable {}
class RadioButtonFormEvent extends FormEvent {
final int buttonIndex;
final String buttonName;
RadioButtonFormEvent({
required this.buttonIndex,
required this.buttonName,
});
#override
List<Object?> get props => [buttonIndex, buttonName,];
}
Here is Form code
class FormInput extends StatelessWidget {
const FormInput({super.key});
#override
Widget build(BuildContext context) {
final formBlocWatcher = context.watch<FormBloc>().state;
final nameController = TextEditingController();
final priceController = TextEditingController();
final formOneController = TextEditingController();
final formTwoController = TextEditingController();
final formThreeController = TextEditingController();
String formOptionController = '';
bool optionOneIsActive = true;
bool optionTwoIsActive = false;
if (formBlocWatcher is RadioButtonFormState) {
switch (formBlocWatcher.buttonIndex) {
case 0:
formOptionController = formBlocWatcher.buttonName;
break;
case 1:
optionTwoIsActive = true;
optionOneIsActive = false;
formOptionController = formBlocWatcher.buttonName;
break;
}
}
return Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
top: 15,
left: 15,
right: 15),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: nameController,
decoration: const InputDecoration(hintText: 'Name'),
),
const SizedBox(height: 10),
TextField(
controller: priceController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(hintText: 'Price'),
),
const SizedBox(height: 15),
OptionsWidget(),
Visibility(
visible: optionOneIsActive,
child: TextField(
controller: formOneController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(hintText: 'Form one'),
)),
Visibility(
visible: optionTwoIsActive,
child: Column(
children: [
TextField(
controller: formTwoController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(hintText: 'Form two'),
),
TextField(
controller: formThreeController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(hintText: 'Form three'),
),
],
)),
const SizedBox(height: 10),
ElevatedButton(
onPressed: () {
BlocProvider.of<ProductsListBloc>(context).add(
AddProductEvent(
productName: nameController.text,
productPrice: int.parse(priceController.text),
productDescOne: formOneController.text,
productDescTwo: formTwoController.text,
productDescThree: formThreeController.text,
formOption: formOptionController,
),
);
},
child: Text('Create New'),
),
],
),
);
}
}
class OptionsWidget extends StatelessWidget {
OptionsWidget({super.key});
int value = 0;
Widget CustomRadioButton(String text, int index, BuildContext context) {
final formBloc = BlocProvider.of<FormBloc>(context);
final blocWatch = context.watch<FormBloc>().state;
if (blocWatch is RadioButtonFormState) {
value = blocWatch.buttonIndex;
}
return OutlinedButton(
onPressed: () {
formBloc.add(RadioButtonFormEvent(
buttonIndex: index,
buttonName: text,
));
},
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
side: BorderSide(color: (value == index) ? Colors.blue : Colors.grey),
),
child: Text(
text,
style: TextStyle(
color: (value == index) ? Colors.blue : Colors.grey,
),
));
}
#override
Widget build(BuildContext context) {
return Row(
children: [
CustomRadioButton("option one", 0, context),
const SizedBox(width: 15),
CustomRadioButton("option two", 1, context),
],
);
}
}
Your FormInput class should be extends from StatefulWidget, not StatelessWidget.
After this change, you should remove all TextEditingController assignments from the build() method and move them into initState().

How to shift focus to next custom textfield in Flutter?

As per: How to shift focus to next textfield in flutter?, I used FocusScope.of(context).nextFocus() to shift focus. But this doesn't work when you use a reusable textfield class. It only works when you directly use TextField class inside Column.
import 'package:flutter/material.dart';
void main() {
return runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final focus = FocusScope.of(context);
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
CustomTextField(
textInputAction: TextInputAction.next,
onEditingComplete: () => focus.nextFocus(),
),
const SizedBox(height: 10),
CustomTextField(
textInputAction: TextInputAction.done,
onEditingComplete: () => focus.unfocus(),
),
],
),
),
),
);
}
}
class CustomTextField extends StatelessWidget {
final TextInputAction textInputAction;
final VoidCallback onEditingComplete;
const CustomTextField({
this.textInputAction = TextInputAction.done,
this.onEditingComplete = _onEditingComplete,
});
static _onEditingComplete() {}
#override
Widget build(BuildContext context) {
return TextField(
textInputAction: textInputAction,
onEditingComplete: onEditingComplete,
);
}
}
In this code, if I click next in keyboard it will not shift focus to next textfield. Please help me with this.
That's because the context doesn't have anything it could grab the focus from. Replace your code with this:
void main() => runApp(MaterialApp(home: MyApp()));
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final focus = FocusScope.of(context);
return Scaffold(
appBar: AppBar(),
body: Column(
children: <Widget>[
CustomTextField(
textInputAction: TextInputAction.next,
onEditingComplete: () => focus.nextFocus(),
),
SizedBox(height: 10),
CustomTextField(
textInputAction: TextInputAction.done,
onEditingComplete: () => focus.unfocus(),
),
],
),
);
}
}
You need to wrap your fields in a form widget with a form key and use a TextFormField instead of textField widget. Set the action to TextInputAction.next and it should work! You can also use TextInput.done to trigger the validation.
Here a fully working exemple:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class LogInPage extends StatefulWidget {
LogInPage({Key key}) : super(key: key);
#override
_LogInPageState createState() => _LogInPageState();
}
class _LogInPageState extends State<LogInPage> {
final _formKey = new GlobalKey<FormState>();
bool isLoading = false;
String firstName;
String lastName;
String password;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
backgroundColor: Colors.black,
body: body(),
);
}
Widget body() {
return Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
showInput(
firstName,
TextInputType.name,
Icons.drive_file_rename_outline,
"FirstName",
TextInputAction.next,
onSaved: (value) => firstName = value.trim()),
showInput(lastName, TextInputType.name,
Icons.drive_file_rename_outline, "LastName", TextInputAction.next,
onSaved: (value) => lastName = value.trim()),
showInput(null, TextInputType.text, Icons.drive_file_rename_outline,
"Password", TextInputAction.done,
isPassword: true, onSaved: (value) => password = value),
Padding(
padding: EdgeInsets.symmetric(vertical: 10),
),
showSaveButton(),
],
),
);
}
Widget showInput(String initialValue, TextInputType textInputType,
IconData icon, String label, TextInputAction textInputAction,
{#required Function onSaved, bool isPassword = false}) {
return Padding(
padding: EdgeInsets.fromLTRB(16.0, 20.0, 16.0, 0.0),
child: new TextFormField(
style: TextStyle(color: Theme.of(context).primaryColorLight),
maxLines: 1,
initialValue: initialValue,
keyboardType: textInputType,
textInputAction: textInputAction,
autofocus: false,
obscureText: isPassword,
enableSuggestions: !isPassword,
autocorrect: !isPassword,
decoration: new InputDecoration(
fillColor: Theme.of(context).primaryColor,
hintText: label,
hintStyle: TextStyle(color: Theme.of(context).primaryColorDark),
filled: true,
contentPadding: new EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0),
border: new OutlineInputBorder(
borderRadius: new BorderRadius.circular(12.0),
),
icon: new Icon(
icon,
color: Theme.of(context).primaryColorLight,
)),
validator: (value) {
return value.isEmpty && !isPassword
? "You didn't filled this field."
: null;
},
onSaved: onSaved,
onFieldSubmitted:
textInputAction == TextInputAction.done ? (value) => save() : null,
),
);
}
Widget showSaveButton() {
return RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(100))),
color: Theme.of(context).primaryColor,
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 25),
child: isLoading
? SizedBox(height: 17, width: 17, child: CircularProgressIndicator())
: Text(
"Sauvegarder",
style: TextStyle(color: Theme.of(context).primaryColorLight),
),
onPressed: save,
);
}
void save() async {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
//TODO
}
}
}
FocusNode textSecondFocusNode = new FocusNode();
TextFormField textFirst = new TextFormField(
onFieldSubmitted: (String value) {
FocusScope.of(context).requestFocus(textSecondFocusNode);
},
);
TextFormField textSecond = new TextFormField(
focusNode: textSecondFocusNode,
);
// render textFirst and textSecond where you want

How to toggle the suffix icon on the textfields separately

I have three pass fields which have icons to show/hide the pass. The default obscureText is true and when the user clicks in the icon, it calls a method _toggle that will turn the obscure text false, showing the textField content.
But, when the user clicks in the icon, it toggles to all the 3 textfields but i wanted toggle only the field clicked. How can I treat this?
My text fields (X 3):
TextFormField(
controller: _controller1,
decoration: _getInputDecoration("Write your current pass"),
keyboardType: TextInputType.text,
obscureText: _isToggle,
My get input decoration (with the icon inside a Gesture detector) :
suffixIcon:
Padding(
padding: EdgeInsetsDirectional.only(end: 12.0),
child: GestureDetector(
child: _isToggle ? Icon(Icons.lock_outline_rounded, color: Colors.black,) :
Icon(Icons.lock_open_rounded, color: Colors.black,),
onTap: _toggle,
)
),
This is the _toggle method:
void _toggle() {
setState(() {
_isToggle = !_isToggle;
});
}
Please check the code for dynamically setting the obscureText when you have multiple TextEditingController.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List _controller = List<TextEditingController>.generate(
3, (index) => TextEditingController());
List<bool> _isToggle = List<bool>.generate(3, (index) => true);
void _toggle(int index) {
setState(() {
_isToggle[index] = !_isToggle[index];
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Padding(
padding: EdgeInsets.all(10),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
for (int i = 0; i < 3; i++)
TextFormField(
controller: _controller[i],
//decoration: _getInputDecoration("Write your current pass"),
keyboardType: TextInputType.text,
obscureText: _isToggle[i],
decoration: InputDecoration(
suffixIcon: Padding(
padding: EdgeInsetsDirectional.only(end: 12.0),
child: GestureDetector(
child: _isToggle[i]
? Icon(
Icons.lock_outline_rounded,
color: Colors.black,
)
: Icon(
Icons.lock_open_rounded,
color: Colors.black,
),
onTap: () => _toggle(i),
),
),
),
),
],
),
),
),
);
}
}
You need to seperate _isToggle variable for each TextFormField. And set only the tapped TextFormField.
EDIT:
TextEditingController _controller1 = TextEditingController();
TextEditingController _controller2 = TextEditingController();
TextEditingController _controller3 = TextEditingController();
List<bool> toggleList = List<bool>();
void _toggle(int index) {
setState(() {
toggleList[index] = !toggleList[index];
// _isToggle = !_isToggle;
});
}
List<Widget> widgetList = List<Widget>();
InputDecoration _getInputDecoration(String string, int index) {
return InputDecoration(
isDense: true,
suffixIcon: Padding(
padding: EdgeInsetsDirectional.only(end: 12.0),
child: GestureDetector(
child: toggleList[index]
? Icon(
Icons.lock_outline_rounded,
color: Colors.black,
)
: Icon(
Icons.lock_open_rounded,
color: Colors.black,
),
onTap: () {
_toggle(index);
},
),
),
);
}
addList() {
widgetList.add(TextFormField(
controller: _controller1,
decoration: _getInputDecoration("Write your current pass", 0),
keyboardType: TextInputType.text,
obscureText: toggleList[0],
));
widgetList.add(TextFormField(
controller: _controller2,
decoration: _getInputDecoration("Write your current pass", 1),
keyboardType: TextInputType.text,
obscureText: toggleList[1],
));
widgetList.add(TextFormField(
controller: _controller3,
decoration: _getInputDecoration("Write your current pass", 2),
keyboardType: TextInputType.text,
obscureText: toggleList[2],
));
}
#override
Widget build(BuildContext context) {
return ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: widgetList.length,
itemBuilder: (BuildContext context, int index) {
return widgetList[index];
});
It is because you're using a single variable(_isToggle) for all the fields.
Using 3 separate booleans would solve the problem.
bool _isToggle1=false;
bool _isToggle2=false;
bool _isToggle3=false;
suffixIcon:
Padding(
padding: EdgeInsetsDirectional.only(end: 12.0),
child: GestureDetector(
child: _isToggle1 ? Icon(Icons.lock_outline_rounded, color: Colors.black,) :
Icon(Icons.lock_open_rounded, color: Colors.black,),
onTap: ()=>setState(()=>_isToggle1=!_isToggle1),
)
),

How to validate form if at least n TextFormField are filled and retrieve their value in Flutter?

I have three different TextFormFields inside a Form, but only two of them can be filled at the same time.What I would like to achieve, is that whenerver two of them are filled, the other one should not be enabled.
They should be aware of changes in other fields at any time.
Below them is a RaisedButton that should be enabled when this condition is met.
Moreover, I need to do some logic with their values when the said button is pressed.
This is what I have right now:
class LPFilterCalculator extends StatefulWidget {
#override
State<StatefulWidget> createState() => _LPFilterCalculatorState();
}
class _LPFilterCalculatorState extends State<LPFilterCalculator> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: PersistentAppBar("Low Pass Filter").build(context),
drawer: DrawerMenu(),
body: Column(
children: <Widget>[
LowPassInputForm(),
],
),
);
}
}
/// Inputform class for Calculators
class LowPassInputForm extends StatefulWidget {
#override
State<StatefulWidget> createState() => _LowPassInputFormState();
}
class _LowPassInputFormState extends State<LowPassInputForm> {
ValueNotifier<bool> pressed = ValueNotifier(false);
final _formKey = GlobalKey<FormState>();
final resistanceTextController = TextEditingController();
final capacitorTextController = TextEditingController();
final frequencyTextController = TextEditingController();
#override
Widget build(BuildContext context) {
print('state update');
return Form(
onChanged: () => {},
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Column(
children: <Widget>[
TextFormField(
controller: resistanceTextController,
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter.digitsOnly
],
keyboardType: TextInputType.number,
decoration:
const InputDecoration(hintText: 'Enter resistance value'),
),
TextFormField(
controller: capacitorTextController,
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter.digitsOnly
],
keyboardType: TextInputType.number,
decoration:
const InputDecoration(hintText: 'Enter capacitor value'),
),
TextFormField(
controller: frequencyTextController,
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter.digitsOnly
],
keyboardType: TextInputType.number,
decoration: const InputDecoration(
hintText: 'Enter desired cutoff frequency'),
),
RaisedButton(
elevation: 5.0,
onPressed: () {
calculateLowPass();
},
),
],
),
],
),
),
);
}
void calculateLowPass() {
var resistance = resistanceTextController.text;
var capacitor = capacitorTextController.text;
var frequency = frequencyTextController.text;
// do calculations
}
#override
void dispose() {
frequencyTextController.dispose();
super.dispose();
}
}
You should note I'm not really using some properties as well as functionalities of objects, as I'm not really sure which is the correct or best way to approach this in Flutter.
Any tips would be more than welcomed!
i am sure there are better way of doing it, but here is how i did it, i used the enabled property on TextFormField and created a method for each one that take the controller of the other two TextFormField, i also created one for the button to check the all the text form fields, here is the full code:
/// Inputform class for Calculators
class LowPassInputForm extends StatefulWidget {
#override
State<StatefulWidget> createState() => _LowPassInputFormState();
}
class _LowPassInputFormState extends State<LowPassInputForm> {
ValueNotifier<bool> pressed = ValueNotifier(false);
final _formKey = GlobalKey<FormState>();
bool isEnabled = true;
final resistanceTextController = TextEditingController();
final capacitorTextController = TextEditingController();
final frequencyTextController = TextEditingController();
bool checkResistanceController() =>
frequencyTextController.text.isEmpty ||
capacitorTextController.text.isEmpty;
bool checkCapacitorController() =>
frequencyTextController.text.isEmpty ||
resistanceTextController.text.isEmpty;
bool checkFrequencyController() =>
resistanceTextController.text.isEmpty ||
capacitorTextController.text.isEmpty;
bool enableButton() =>
!checkFrequencyController() ||
!checkCapacitorController() ||
!checkResistanceController();
#override
Widget build(BuildContext context) {
print('state update');
return Form(
onChanged: () => setState(() {
checkResistanceController();
}),
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Column(
children: <Widget>[
TextFormField(
enabled: checkResistanceController(),
controller: resistanceTextController,
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter.digitsOnly
],
keyboardType: TextInputType.number,
decoration:
const InputDecoration(hintText: 'Enter resistance value'),
),
TextFormField(
enabled: checkCapacitorController(),
controller: capacitorTextController,
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter.digitsOnly
],
keyboardType: TextInputType.number,
decoration:
const InputDecoration(hintText: 'Enter capacitor value'),
),
TextFormField(
enabled: checkFrequencyController(),
controller: frequencyTextController,
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter.digitsOnly
],
keyboardType: TextInputType.number,
decoration: const InputDecoration(
hintText: 'Enter desired cutoff frequency'),
),
RaisedButton(
elevation: 5.0,
onPressed:
enableButton() ? () => calculateLowPass() : null),
],
),
],
),
),
);
}
void calculateLowPass() {
var resistance = resistanceTextController.text;
var capacitor = capacitorTextController.text;
var frequency = frequencyTextController.text;
// do calculations
}
#override
void dispose() {
frequencyTextController.dispose();
super.dispose();
}
}
You need to use the enabled property on the TextFormField and check whether the other two fields are empty. If any of the fields is empty then you enable the current field.
You could save your data in an Object model:
class Object {
// for the sake of this example the fields here are strings, but in practice it's better to change them to double
String resistance;
String capacitor;
String frequency;
Object({
this.resistance = '',
this.capacitor = '',
this.frequency = '',
});
}
Your updated state looks like this with the added Object instance:
class _LowPassInputFormState extends State<LowPassInputForm> {
final _formKey = GlobalKey<FormState>();
final resistanceTextController = TextEditingController();
final capacitorTextController = TextEditingController();
final frequencyTextController = TextEditingController();
Object data = Object();
#override
Widget build(BuildContext context) {
return Form(
onChanged: () => {},
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Column(
children: <Widget>[
TextFormField(
enabled: data.capacitor.isEmpty || data.frequency.isEmpty,
onChanged: (val) => setState(() => data.resistance = val),
controller: resistanceTextController,
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter.digitsOnly
],
keyboardType: TextInputType.number,
decoration:
const InputDecoration(hintText: 'Enter resistance value'),
),
TextFormField(
enabled: data.resistance.isEmpty || data.frequency.isEmpty,
onChanged: (val) => setState(() => data.capacitor = val),
controller: capacitorTextController,
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter.digitsOnly
],
keyboardType: TextInputType.number,
decoration:
const InputDecoration(hintText: 'Enter capacitor value'),
),
TextFormField(
enabled: data.resistance.isEmpty || data.capacitor.isEmpty,
onChanged: (val) => setState(() => data.frequency = val),
controller: frequencyTextController,
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter.digitsOnly
],
keyboardType: TextInputType.number,
decoration: const InputDecoration(
hintText: 'Enter desired cutoff frequency'),
),
RaisedButton(
elevation: 5.0,
onPressed: () {
calculateLowPass();
},
),
],
),
],
),
),
);
}
void calculateLowPass() {
// use data to do calculations
}
}

setState does not update TextFormField when use initialValue

everyone.
I am using Form and TextFieldForm without any own TextEditController. Have 3 TextFieldForm (Value_1, Value_2, Total) with initial values. When i am editing first one, the Total textfield should contain result of calculation . To update widget i am using setState. The problem that variable _total and Text widget always has a correct calculation value, but the Total textfield does not want to update.
why? is it posible to do without using own TextEditController?
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: TestForm(),
);
}
}
class TestForm extends StatefulWidget {
#override
_TestFormState createState() => _TestFormState();
}
class _TestFormState extends State<TestForm> {
GlobalKey<FormState> _formKey = GlobalKey();
int _value1 = 0;
int _value2 = 20;
int _total = 0;
#override
Widget build(BuildContext context) {
print('rebuild');
return Scaffold(
appBar: AppBar(title: Text('test form')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: ListView(
children: <Widget>[
TextFormField(
initialValue: _value1.toString(),
decoration: InputDecoration(
labelText: 'Value_1',
),
keyboardType: TextInputType.number,
onChanged: (value) {
setState(() {
_total = int.parse(value) * _value2;
print('total: ' + _total.toString());
});
},
),
TextFormField(
initialValue: _value2.toString(),
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: 'Value_2',
),
),
TextFormField(
initialValue: _total.toString(),
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: 'Total',
),
),
SizedBox(height: 20),
Text('total: ' + _total.toString()),
],
),
),
),
);
}
}
If you have a reactive data source, aka data that can change based on either network updates or other data, one hack that worked for me was to use a Key.
By making a Key of the reactive data (toString()), the form field will change every time the Key changes.
So in this case you could do:
TextFormField(
key: Key(_total.toString()), // <- Magic!
initialValue: _total.toString(),
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: 'Total',
),
),
I used something as simple as this:
TextFormField(
controller: TextEditingController()..text = '23232',
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: 'Total',
),
),