setState does not update TextFormField when use initialValue - flutter

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',
),
),

Related

adding form with globalKey inside PageView.Builder

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,
...
)

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 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
}
}

Flutter: Best way to get all values in a form

I'm making a data collection app which has multiple TextFields, like more than 12. I'm using a Form key to validate all of them. I want values of all the text fields so I can save them to firestore. How do I do this? Here's my code:
import 'package:flutter/material.dart';
class MainForm extends StatefulWidget {
#override
_MainFormState createState() => _MainFormState();
}
class _MainFormState extends State<MainForm> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Center(
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
Text('Enter information about PG Owner'),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
autofocus: true,
textCapitalization: TextCapitalization.words,
textAlignVertical: TextAlignVertical.center,
onTap: () {},
decoration: InputDecoration(
prefixIcon: Icon(Icons.face),
labelText: 'Enter Name of Owner',
border: OutlineInputBorder()),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
validator: (value) {
if (value.length < 15) {
return 'Address seems very short!';
}
return null;
},
keyboardType: TextInputType.text,
decoration: InputDecoration(
prefixIcon: Icon(Icons.room),
labelText: 'Enter full address of Owner',
border: OutlineInputBorder()),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
keyboardType: TextInputType.number,
validator: (value) {
if (value.length < 9) {
return 'Phone number must be 9 digits or longer';
}
return null;
},
decoration: InputDecoration(
prefixIcon: Icon(Icons.phone),
labelText: 'Phone number of Owner',
border: OutlineInputBorder()),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
validator: (value) {
if (value.isEmpty) {
return 'Please enter a valid email address';
}
if (!value.contains('#')) {
return 'Email is invalid, must contain #';
}
if (!value.contains('.')) {
return 'Email is invalid, must contain .';
}
return null;
},
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
prefixIcon: Icon(Icons.mail_outline),
labelText: 'Enter Email',
border: OutlineInputBorder()),
),
),
)
],
),
),
),
);
}
}
Update: I know that proper way (I've read the docs) of getting values from a TextField is by creating a controller. But, In my case there are 14 TextFields which requires me to create 14 controllers. Is there a better way of doing this?
You can use something like this in the following code:
_formKey.currentState.save(); calls the onSaved() on each textFormField items, which assigns the value to all the fields and you can use them as required. Try using the _formKey.currentState.save(); just after _formKey.currentState.validate() is evaluated as true.
The form code looks like this:
String contactNumber;
String pin;
return Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
onSaved: (String value){contactNumber=value;},
keyboardType: TextInputType.phone,
inputFormatters: [WhitelistingTextInputFormatter.digitsOnly],
maxLength: 10,
decoration: InputDecoration(
labelText: "Enter Your Mobile Number",
hintText: "Number",
icon: Icon(Icons.phone_iphone)),
validator: (value) {
if (value.isEmpty || value.length < 10) {
return 'Please Enter 10 digit number';
}
return null;
},
),
TextFormField(
onSaved: (String value){pin=value;},
keyboardType: TextInputType.phone,
inputFormatters: [WhitelistingTextInputFormatter.digitsOnly],
maxLength: 10,
decoration: InputDecoration(
labelText: "Enter Your PIN",
hintText: "Number",
icon: Icon(Icons.lock)),
validator: (value) {
if (value.isEmpty || value.length < 6) {
return 'Please Enter 6 digit PIN';
}
return null;
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: RaisedButton(
color: Colors.black,
textColor: Colors.white,
onPressed: () {
if (_formKey.currentState.validate()) {
***_formKey.currentState.save();***
bloc.loginUser(contactNumber, pin);
}
},
child: Text('Login' /*style: TextStyle(fontSize: 30),*/)),
)
],
),
);
Using controller in TextFormField, you can get value of the TextFormField.
TextEditingController emailEditingController = TextEditingController();
TextFormField(
controller: emailEditingController,
validator: (value) {
if (value.isEmpty) {
return 'Please enter a valid email address';
}
if (!value.contains('#')) {
return 'Email is invalid, must contain #';
}
if (!value.contains('.')) {
return 'Email is invalid, must contain .';
}
return null;
},
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
prefixIcon: Icon(Icons.mail_outline),
labelText: 'Enter Email',
border: OutlineInputBorder()),
);
Get Value like:
String email=emailEditingController.text;
Updated Answer
Get value by using onSubmitted
onSubmitted: (String value){email=value;},
I am not satisfied with how Flutter make you handle the form values yourself, you need to create a TextEditingController instance for each field, assign it to the controller and remember to dispose all of them manually. This leads to a lot of boilerplate code and makes it more error-prone:
final _formKey = GlobalKey<FormState>();
final controller1 = TextEditingController();
final controller2 = TextEditingController();
final controller3 = TextEditingController();
#override
void dispose() {
super.dispose();
controller1.dispose();
controller2.dispose();
controller3.dispose();
}
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(children: [
TextFormField(controller: controller1),
TextFormField(controller: controller2),
TextFormField(
controller: controller3,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
final value1 = controller1.text;
final value2 = controller2.text;
final value3 = controller3.text;
// do something with the form data
}
},
child: const Text('Submit'),
),
]),
);
}
A much less cumbersome way is to use the flutter_form_builder package and replace TextFormField with the FormBuilderTextField widget which is a wrapper of the old plain TextField. You can see all of the supported input widgets here.
All you need to do now is to specify the name of each field in your form, and access it in _formKey.currentState?.value. See the example below:
final _formKey = GlobalKey<FormBuilderState>();
#override
Widget build(BuildContext context) {
return FormBuilder(
key: _formKey,
child: Column(children: [
FormBuilderTextField(name: 'field1'),
FormBuilderTextField(name: 'field2'),
FormBuilderTextField(
name: 'field3',
validator: FormBuilderValidators.required(
context,
errorText: 'Please enter some text',
),
),
ElevatedButton(
onPressed: () {
_formKey.currentState.save();
if (_formKey.currentState!.validate()) {
final formData = _formKey.currentState?.value;
// formData = { 'field1': ..., 'field2': ..., 'field3': ... }
// do something with the form data
}
},
child: const Text('Submit'),
),
]),
);
}
You can use flutter_form_bloc, you don't need to create any TextEditingController and can separate the Business Logic from the User Interface, in addition to offering other advantages.
dependencies:
flutter_bloc: ^0.21.0
form_bloc: ^0.4.1
flutter_form_bloc: ^0.3.0
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';
import 'package:form_bloc/form_bloc.dart';
void main() => runApp(MaterialApp(home: MainForm()));
class MainFormBloc extends FormBloc<String, String> {
final nameField = TextFieldBloc();
final addressField = TextFieldBloc(validators: [
(value) => value.length < 15 ? 'Address seems very short!' : null,
]);
final phoneNumberField = TextFieldBloc(validators: [
(value) =>
value.length < 9 ? 'Phone number must be 9 digits or longer' : null,
]);
final emailField = TextFieldBloc(validators: [Validators.email]);
#override
List<FieldBloc> get fieldBlocs => [
nameField,
addressField,
phoneNumberField,
emailField,
];
#override
Stream<FormBlocState<String, String>> onSubmitting() async* {
// This method is called when you call [mainFormBloc.submit]
// and each field bloc have a valid value.
// And you can save them in firestore.
print(nameField.value);
print(addressField.value);
print(phoneNumberField.value);
print(emailField.value);
yield currentState.toSuccess('Data saved successfully.');
// yield `currentState.toLoaded()` because
// you can't submit if the state is `FormBlocSuccess`.
// In most cases you don't need to do this,
// because you only want to submit only once.
yield currentState.toLoaded();
}
}
class MainForm extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider<MainFormBloc>(
builder: (context) => MainFormBloc(),
child: Builder(
builder: (context) {
final formBloc = BlocProvider.of<MainFormBloc>(context);
return Scaffold(
appBar: AppBar(title: Text('Main Form')),
body: FormBlocListener<MainFormBloc, String, String>(
onSuccess: (context, state) {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(state.successResponse),
backgroundColor: Colors.green,
),
);
},
onSubmissionFailed: (context, state) {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('Some fields have invalid data.'),
backgroundColor: Colors.red,
),
);
},
child: ListView(
children: <Widget>[
TextFieldBlocBuilder(
textFieldBloc: formBloc.nameField,
padding: const EdgeInsets.all(8.0),
autofocus: true,
textCapitalization: TextCapitalization.words,
textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration(
prefixIcon: Icon(Icons.face),
labelText: 'Enter Name of Owner',
border: OutlineInputBorder()),
),
TextFieldBlocBuilder(
textFieldBloc: formBloc.addressField,
padding: const EdgeInsets.all(8.0),
keyboardType: TextInputType.text,
decoration: InputDecoration(
prefixIcon: Icon(Icons.room),
labelText: 'Enter full address of Owner',
border: OutlineInputBorder()),
),
TextFieldBlocBuilder(
textFieldBloc: formBloc.phoneNumberField,
padding: const EdgeInsets.all(8.0),
keyboardType: TextInputType.number,
decoration: InputDecoration(
prefixIcon: Icon(Icons.phone),
labelText: 'Phone number of Owner',
border: OutlineInputBorder()),
),
TextFieldBlocBuilder(
textFieldBloc: formBloc.emailField,
padding: const EdgeInsets.all(8.0),
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
prefixIcon: Icon(Icons.mail_outline),
labelText: 'Enter Email',
border: OutlineInputBorder()),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: formBloc.submit,
child: Center(child: Text('SUBMIT')),
),
),
],
),
),
);
},
),
);
}
}
I came here from a similar search. All the answers found did not satisfy my need, hence I wrote a custom solution.
form key
final _signUpKey = GlobalKey<FormState>();
declare your TextEditingController
final Map<String, TextEditingController> sigUpController = {
'firstName': TextEditingController(),
'lastName': TextEditingController(),
'email': TextEditingController(),
'phone': TextEditingController(),
'password': TextEditingController(),
};
Pass controller to TextFormField like this
Form(
key: _signUpKey,
child: Column(
children: [
TextFormField(
controller: sigUpController['firstName'],
validator: validator,
autofocus: autofocus,
keyboardType: TextInputType.text,
style: const TextStyle(
fontSize: 14,
),
onTap: onTap,
onChanged: onChanged,
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r"[a-zA-Z]+|\s"),
),
],
),
// define the other TextFormField here
TextButton(
onPressed: () {
if (!_signUpKey.currentState!.validate()) {
return;
}
// To get data I wrote an extension method bellow
final data = sigUpController.data();
print('data: $data'); // data: {firstName: John, lastName: Doe, email: example#email.com, phone: 0000000000, password: password}
},
child: const Text('submit'),
)
],
),
);
Extension method to get data from Map<String, TextEditingController>
extension Data on Map<String, TextEditingController> {
Map<String, dynamic> data() {
final res = <String, dynamic>{};
for (MapEntry e in entries) {
res.putIfAbsent(e.key, () => e.value?.text);
}
return res;
}
}
Try using this flutter package flutter_form_builder, it will help you from repeating yourself by creating multiple controllers for each form field. In addition to that, it will help you in validating the form, and updating the form with simplicity by using only a simple form key to control the entire form.

How to create number input field in Flutter?

I'm unable to find a way to create an input field in Flutter that would open up a numeric keyboard and should take numeric input only. Is this possible with Flutter material widgets? Some GitHub discussions seem to indicate this is a supported feature but I'm unable to find any documentation about it.
You can specify the number as keyboardType for the TextField using:
keyboardType: TextInputType.number
Check my main.dart file
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
return new MaterialApp(
home: new HomePage(),
theme: new ThemeData(primarySwatch: Colors.blue),
);
}
}
class HomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new HomePageState();
}
}
class HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.white,
body: new Container(
padding: const EdgeInsets.all(40.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new TextField(
decoration: new InputDecoration(labelText: "Enter your number"),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
], // Only numbers can be entered
),
],
)),
);
}
}
For those who are looking for making TextField or TextFormField accept only numbers as input, try this code block :
for flutter 1.20 or newer versions
TextFormField(
controller: _controller,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
// for below version 2 use this
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
// for version 2 and greater youcan also use this
FilteringTextInputFormatter.digitsOnly
],
decoration: InputDecoration(
labelText: "whatever you want",
hintText: "whatever you want",
icon: Icon(Icons.phone_iphone)
)
)
for earlier versions of 1.20
TextFormField(
controller: _controller,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
WhitelistingTextInputFormatter.digitsOnly
],
decoration: InputDecoration(
labelText:"whatever you want",
hintText: "whatever you want",
icon: Icon(Icons.phone_iphone)
)
)
Through this option you can strictly restricted another char with out number.
inputFormatters: [WhitelistingTextInputFormatter.digitsOnly],
keyboardType: TextInputType.number,
For using above option you have to import this
import 'package:flutter/services.dart';
using this kind of option user can not paste char in a textfield
Set the keyboard and a validator
String numberValidator(String value) {
if(value == null) {
return null;
}
final n = num.tryParse(value);
if(n == null) {
return '"$value" is not a valid number';
}
return null;
}
new TextFormField(
keyboardType: TextInputType.number,
validator: numberValidator,
textAlign: TextAlign.right
...
https://docs.flutter.io/flutter/material/TextFormField/TextFormField.html
https://docs.flutter.io/flutter/services/TextInputType-class.html
To avoid paste not digit value, add after
keyboardType: TextInputType.number
this code :
inputFormatters: [FilteringTextInputFormatter.digitsOnly]
from https://api.flutter.dev/flutter/services/FilteringTextInputFormatter-class.html
For those who need to work with money format in the text fields:
To use only: , (comma) and . (period)
and block the symbol: - (hyphen, minus or dash)
as well as the: ⌴ (blank space)
In your TextField, just set the following code:
keyboardType: TextInputType.numberWithOptions(decimal: true),
inputFormatters: [BlacklistingTextInputFormatter(new RegExp('[\\-|\\ ]'))],
The simbols hyphen and space will still appear in the keyboard, but will become blocked.
If you need to use a double number:
keyboardType: TextInputType.numberWithOptions(decimal: true),
inputFormatters: [FilteringTextInputFormatter.allow(RegExp('[0-9.,]')),],
onChanged: (value) => doubleVar = double.parse(value),
RegExp('[0-9.,]') allows for digits between 0 and 9, also comma and dot.
double.parse() converts from string to double.
Don't forget you need:
import 'package:flutter/services.dart';
The TextField widget is required to set keyboardType: TextInputType.number,
and inputFormatters: <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly] to accept numbers only as input.
TextField(
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
], // Only numbers can be entered
),
Example in DartPad
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
theme: ThemeData(primarySwatch: Colors.blue),
);
}
}
class HomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return HomePageState();
}
}
class HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Container(
padding: const EdgeInsets.all(40.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("This Input accepts Numbers only"),
SizedBox(height: 20),
TextField(
decoration: InputDecoration(
focusedBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Colors.greenAccent, width: 5.0),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red, width: 5.0),
),
hintText: 'Mobile Number',
),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
], // Only numbers can be entered
),
SizedBox(height: 20),
Text("You can test be Typing"),
],
)),
);
}
}
You can use this two attributes together with TextFormField
TextFormField(
keyboardType: TextInputType.number
inputFormatters: [WhitelistingTextInputFormatter.digitsOnly],
It's allow to put only numbers, no thing else ..
https://api.flutter.dev/flutter/services/TextInputFormatter-class.html
As the accepted answer states, specifying keyboardType triggers a numeric keyboard:
keyboardType: TextInputType.number
Other good answers have pointed out that a simple regex-based formatter can be used to allow only whole numbers to be typed:
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
],
The problem with this is that the regex only matches one symbol at a time, so limiting the number of decimal points (e.g.) cannot be achieved this way.
Also, others have also shown that if one wants validation for a decimal number, it can be achieved by using a TextFormField and it's validator parameter:
new TextFormField(
keyboardType: TextInputType.number,
validator: (v) => num.tryParse(v) == null ? "invalid number" : null,
...
The problem with this is that it cannot be achieved interactively, but only at form submission time.
I wanted to allow only decimal numbers to be typed, rather than validated later. My solution is to write a custom formatter leveraging int.tryParse:
/// Allows only decimal numbers to be input.
class DecimalNumberFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
// Allow empty input and delegate formatting decision to `num.tryParse`.
return newValue.text != '' && num.tryParse(newValue.text) == null
? oldValue
: newValue;
}
}
Alternatively, one can use a regex for the custom formatter, which would apply to the whole input, not just a single symbol:
/// Allows only decimal numbers to be input. Limits decimal plates to 3.
class DecimalNumberFormatter extends TextInputFormatter {
#override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue, TextEditingValue newValue) {
// Allow empty input.
if (newValue.text == '') return newValue;
// Regex: can start with zero or more digits, maybe followed by a decimal
// point, followed by zero, one, two, or three digits.
return RegExp('^\\d*\\.?\\d?\\d?\\d?\$').hasMatch(newValue.text)
? newValue
: oldValue;
}
}
This way, I can also limit the number of decimal plates to 3.
Number type only
keyboardType: TextInputType.number
And more option with number pad
keyboardType: TextInputType.numberWithOptions(decimal: true,signed: false)
Just add this to your TextFormField
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')), ],
Example,
TextFormField(
controller: textController,
onChanged: (value) {
print(value);
},
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(RegExp(r'[0-9]')),
],
),
Here is code for actual Phone keyboard on Android:
Key part: keyboardType: TextInputType.phone,
TextFormField(
style: TextStyle(
fontSize: 24
),
controller: _phoneNumberController,
keyboardType: TextInputType.phone,
decoration: InputDecoration(
prefixText: "+1 ",
labelText: 'Phone number'),
validator: (String value) {
if (value.isEmpty) {
return 'Phone number (+x xxx-xxx-xxxx)';
}
return null;
},
),
You can try this:
TextFormField(
keyboardType: TextInputType.number,
decoration: InputDecoration(
prefixIcon: Text("Enter your number: ")
),
initialValue: "5",
onSaved: (input) => _value = num.tryParse(input),
),
For number input or numeric keyboard you can use keyboardType: TextInputType.number
TextFormField(
decoration: InputDecoration(labelText:'Amount'),
controller: TextEditingController(
),
validator: (value) {
if (value.isEmpty) {
return 'Enter Amount';
}
},
keyboardType: TextInputType.number
)
You can add input format with keyboard type, like this
TextField(
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],// Only numbers can be entered
keyboardType: TextInputType.number,
);
You can Easily change the Input Type using the keyboardType Parameter
and you have a lot of possibilities check the documentation TextInputType
so you can use the number or phone value
new TextField(keyboardType: TextInputType.number)
keyboardType: TextInputType.number would open a num pad on focus, I would clear the text field when the user enters/past anything else.
keyboardType: TextInputType.number,
onChanged: (String newVal) {
if(!isNumber(newVal)) {
editingController.clear();
}
}
// Function to validate the number
bool isNumber(String value) {
if(value == null) {
return true;
}
final n = num.tryParse(value);
return n!= null;
}
Set your keyboardType to TextInputType.number,
Eg: keyboardType: TextInputType.number,
TextFormField(
controller: yourcontroller,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: 'Mobile',
suffixIcon: Padding(
padding: EdgeInsets.only(),
child:
Icon(Icons.phone_outlined, color: Color(0xffff4876)),
),
),
validator: (text) {
if (text == null || text.isEmpty) {
return 'Please enter your Mobile No.';
}
return null;
},
),
I need en IntegerFormField with a controll of min/max. And the big problem is that OnEditingComlete is not called when the focus changes. Here is my solution:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:vs_dart/vs_dart.dart';
class IntegerFormField extends StatefulWidget {
final int value, min, max;
final InputDecoration decoration;
final ValueChanged<TextEditingController> onEditingComplete;
IntegerFormField({#required this.value, InputDecoration decoration, onEditingComplete, int min, int max})
: min = min ?? 0,
max = max ?? maxIntValue,
onEditingComplete = onEditingComplete ?? ((_) {}),
decoration = decoration ?? InputDecoration()
;
#override
_State createState() => _State();
}
class _State extends State<IntegerFormField> {
final TextEditingController controller = TextEditingController();
#override
void initState() {
super.initState();
controller.text = widget.value.toString();
}
#override
void dispose() {
super.dispose();
}
void onEditingComplete() {
{
try {
if (int.parse(controller.text) < widget.min)
controller.text = widget.min.toString();
else if (int.parse(controller.text) > widget.max)
controller.text = widget.max.toString();
else
FocusScope.of(context).unfocus();
} catch (e) {
controller.text = widget.value.toString();
}
widget.onEditingComplete(controller);
}
}
#override
Widget build(BuildContext context) {
return Focus(
child: TextFormField(
controller: controller,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
keyboardType: TextInputType.number,
decoration: widget.decoration,
),
onFocusChange: (value) {
if (value)
controller.selection = TextSelection(baseOffset: 0, extentOffset: controller.value.text.length);
else
onEditingComplete();
},
);
}
}
U can Install package intl_phone_number_input
dependencies:
intl_phone_number_input: ^0.5.2+2
and try this code:
import 'package:flutter/material.dart';
import 'package:intl_phone_number_input/intl_phone_number_input.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
var darkTheme = ThemeData.dark().copyWith(primaryColor: Colors.blue);
return MaterialApp(
title: 'Demo',
themeMode: ThemeMode.dark,
darkTheme: darkTheme,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(title: Text('Demo')),
body: MyHomePage(),
),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final TextEditingController controller = TextEditingController();
String initialCountry = 'NG';
PhoneNumber number = PhoneNumber(isoCode: 'NG');
#override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
InternationalPhoneNumberInput(
onInputChanged: (PhoneNumber number) {
print(number.phoneNumber);
},
onInputValidated: (bool value) {
print(value);
},
selectorConfig: SelectorConfig(
selectorType: PhoneInputSelectorType.BOTTOM_SHEET,
backgroundColor: Colors.black,
),
ignoreBlank: false,
autoValidateMode: AutovalidateMode.disabled,
selectorTextStyle: TextStyle(color: Colors.black),
initialValue: number,
textFieldController: controller,
inputBorder: OutlineInputBorder(),
),
RaisedButton(
onPressed: () {
formKey.currentState.validate();
},
child: Text('Validate'),
),
RaisedButton(
onPressed: () {
getPhoneNumber('+15417543010');
},
child: Text('Update'),
),
],
),
),
);
}
void getPhoneNumber(String phoneNumber) async {
PhoneNumber number =
await PhoneNumber.getRegionInfoFromPhoneNumber(phoneNumber, 'US');
setState(() {
this.number = number;
});
}
#override
void dispose() {
controller?.dispose();
super.dispose();
}
}
Here is code for numeric keyboard :
keyboardType: TextInputType.phone When you add this code in textfield it will open numeric keyboard.
final _mobileFocus = new FocusNode();
final _mobile = TextEditingController();
TextFormField(
controller: _mobile,
focusNode: _mobileFocus,
maxLength: 10,
keyboardType: TextInputType.phone,
decoration: new InputDecoration(
counterText: "",
counterStyle: TextStyle(fontSize: 0),
hintText: "Mobile",
border: InputBorder.none,
hintStyle: TextStyle(
color: Colors.black,
fontSize: 15.0.
),
),
style: new TextStyle(
color: Colors.black,
fontSize: 15.0,
),
);
Here is code for actual Phone keyboard in Flutter:
//Mobile
const TextField(
keyboardType: TextInputType.number,
decoration: InputDecoration(
prefixIcon: Icon(Icons.phone), hintText: 'Mobile'),
),
Here are all details on how to add numeric keybord, How to do validations, How to add stylings, and other stuff in dart/flutter.
I hope it can help you to learn in a better way.
Padding(
padding: const EdgeInsets.all(3.0),
child: TextFormField(
maxLength: 10,
keyboardType: TextInputType.number,
validator: (value) {
if (value.isEmpty) {
return 'Enter Number Please';
}
return null;
},
decoration: InputDecoration(
prefixIcon: Icon(Icons.smartphone),
prefixText: '+92',
labelText: 'Enter Phone Number',
contentPadding: EdgeInsets.zero,
enabledBorder: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
width: 2, color: Theme
.of(context)
.primaryColor,
)
),
focusColor: Theme
.of(context)
.primaryColor,
),
),
),
TextField(
controller: _controller,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(RegExp(r'[0.0-9.9]')),
])
You need to add the line
keyboardType: TextInputType.phone,
Within the block "TextFormField". Example below:
TextField(
controller: _controller,
keyboardType: TextInputType.phone,
),
It should look like this: