Is there a simple way to crossvalidate Flutter Form TextFormFields? - flutter

I have 3 TextFormFields for inputting of telephone numbers on a Flutter Form (daytime, evening and mobile).
Validation for each TextFormField (in the validator:) allows a blank string to be input.
But I don't want the form to be saved unless there is at least one phone number entered.
formKey.currentState.validate will obviously validate all individual fields as being valid.
So is there a simple way in the framework to cross validate all the TextFormFields and display an error without having to write individial validators for each TextFormField and include references to specific fieldnames (which I regard as being a bit of a dirty hack) e.g.
String _validatePhoneNumber(String value) {
// dirty bit - means I have to write a separate validator for each TextFormField rather than use a generic validator
if (value.isEmpty && this.eveningNumber.isEmpty && this.mobileNumber.isEmpty)
return 'At least one number must be included';
if (value.isEmpty) return null;
if (_invalidNumber(value))
return 'Enter a valid phone number';
return null;
}

You should add a TextEditingController to each TextFormField, that way you can check the value of each field when you submit your form.
For example:
class MyForm extends StatefulWidget {
#override
_MyFormState createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
final _formKey = GlobalKey<FormState>();
TextEditingController first = TextEditingController();
TextEditingController second = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Form(
key: _formKey,
child: ListView(
children: <Widget> [
TextFormField(
controller: firstController
// rest of your stuff
)
// rest of your text fields using subsequent controllers
// secondController, thirdControler...
]
)
)
)
}
}
Then whenever you want to check it's value you just call firstController.text or better if you want to know if it's empty you just call firstController.text.isEmpty

Related

Validation in Flutter

I want to know how to make a validation in flutter where IF a TextFormField is filled then when you hit "send" then it doesn't let you go to the next section until all other textformfields must be filled, BUT if one of the TextFormFields is NOT filled when you hit send then it lets you pass to the next section. This is for a job form where a section is NOT mandatory, but only if one field has been filled then it becomes mandatory.
If you have a Form widget that contains all your FormFields (not only text-ones, but also dropdowns and such), the validation occurs on all your fields at once if you write your submit code this way:
final _formKey = GlobalKey<FormState>();
var tecUser = TextEditingController();
var tecPwd = TextEditingController();
[...]
//inside your widget tree...
Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: tecUser,
validator: (value) {
//your validation code: return null when value is right
//or a string if there's some error
},
decoration: InputDecoration(hintText: "username".tr()),
),
const SizedBox(height: 10),
TextFormField(
controller: tecPwd,
validator: (value) {
//your validation code: return null when value is right
//or a string if there's some error
},
obscureText: true,
),
const SizedBox(height: 10),
OutlinedButton(child: const Icon(Icons.login), onPressed: () => _submit()),
[...]
void _submit() async {
if (_formKey.currentState!.validate()) {
//all validators returned null, so you can proceed with your logic
} else {
//this happens when at least one of the validators returned a string
//by default, the error string returned by the validators will be displayed
//near each field, so you won't have to worry about handling the error cases and the else here won't even be necessary
}
}
This is an excerpt from an actual login form.
EDIT:
Ok, now I understand what you want to do. You have a group of fields that aren't mandatory, but they instead are mandatory if at least one of them has some value.
You need to assign a different TextEditingController to each of this fields: then, you need to assign a validator to each FormField that does something like this:
//insert all the TextEditingController in a list
var tecList = <TextEditingController>[tec1, tec2...]
//then, set a validator like this
(value) {
bool notMandatory = true;
for (var tec in tecList){
notMandatory = notMandatory && tec.text.isEmpty;
}
if (!notMandatory) return "Fill all fields";
//some other validation here
}
If you use a TextEditingController you can use the .text.isNotEmpty statement an write yourself a litte if function to check everything.
TextEditingController controller = TextEditingController();
if (controller.text.isNotEmpty) {
print("have fun with your new job")
}

Is there any way to generate the same key for the "same" widget in Flutter without having any values to be based on

I have a widget which can have multiple input sections from the same type. When I delete the first child then it behaves weirdly like showing still the old value from that first widget. I figured out I need to use keys for the children but then my UX gets broken. Let me show you some code snippet, please:
class _ParentState extends State<ParentWidget> {
...
#override
Widget build(BuildContext context) {
return Column(
children: stateList
.mapIndexed((index, element) => ChildWidget(key: UniqueKey(), input: element, onChanged: (text) {
setState(() {
stateList[index].text = text;
});
}))
.toList(),
);
}
}
class _ChildState extends State<ChildWidget> {
final ctrl = TextEditingController();
#override
void initState() {
super.initState();
ctrl.text = widget.input.text;
}
#override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: ctrl,
onChanged: (value) {
widget.onChanged(value);
}
),
...
]
);
}
}
When I wrote the user experience is broken, I was meaning that whenever the user starts to type into the text field then the focus gets loosen character-by-character since the callback is invoked and then generates a new child widget from the state because it has now "different" key... How can I refactor this? I just need a key mechanism which results the same for a child while it is not disposed yet!?
TL;DR
Solution:
remove key
by default, flutter is smart enough to determine which widget is need to rebuild or not. Thats why, key is very rarely used.
ChildWidget(input: element,
onChanged: (text) {
setState(() {
....
When you set UniquieKey() you widget will have different key every time build method called.
which is: everytime you call SetState() your apps will build NEW widget.
if you want to set key, you can use index value
ChildWidget(
key: ValueKey('$index'),
input: element,
onChanged: (text) {
what caused your "user experience broken".
it because, in your onChange function, you call setState() and also you set UniqueKey to your widget. So.. when setState() has called, it will re-execute build method, and since your ChildWidget has Unique key, it will rebuild the widget.
every time user typing, ChildWidget is rebuild. that caused lost focus

How to display user data in an edit form using Flutter?

I'm building an application for the company I'm working for in Flutter. We are using the MVVM (Model, View, ViewModel) architecture with the other developer I'm working with.
I would like to display user data from my ViewModel to my edit form (those data are fetched through our API).
The problem is: the data won't display to my view. I have access to it and I can print it (see screenshots below)...
What I tried so far :
I used initialValue primarily and called, for instance, my 'lastName' variable (but it doesn't show anything)
I tried using a controller for each field. With this method, it shows my user data but I then have a weird keyboard issue where each time I want to type some content, the cursor just goes to the start and deletes the word.
Also, I noticed that my variable can be displayed in a Text() widget.
I'm pretty clueless and I would really love to get an answer on this bug.
class MyAccountViewModel with ChangeNotifier {
String _lastName;
MyAccountViewModel() {
// this._lastName = 'Hardcoded text';
ApiHelper api = new ApiHelper();
api.getUserData().then((Map<String, dynamic>response) {
print(response);
this._lastName = response['last_name'];
});
notifyListeners();
}
String get lastName => this._lastName;
set lastName(String value) {
this._lastName = value;
notifyListeners();
}
Widget editProfileForm(model, BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
initialValue: model.lastName,
),
],
),
);
}
current view
response after the API call
Thanks to the answer I received in this post, I managed to find a working solution.
As advised in the previous comments, I needed to instantiate a controller and bind for instance "lastName" from my api response to controller.text.
Here is a sample code using the MVVM architecture :
class MyAccountViewModel with ChangeNotifier {
TextEditingController _lastNameController;
MyAccountViewModel() {
_lastNameController = new TextEditingController();
ApiHelper api = new ApiHelper();
api.getUserData().then((Map<String, dynamic> response) {
this._lastNameController.text = response['last_name'];
notifyListeners();
});
}
TextEditingController get lastName => this._lastNameController;
set lastName(TextEditingController value) {
this._lastNameController = value;
notifyListeners();
}
}
Widget editProfileForm(model, BuildContext context) {
return Form(
key: _formKey,
child: TextFormField(
controller: model.lastName,
),
);
}
#Metr0me, have you tried using controller to update the value? It could look something like this,
Initialize the controller as final lastNameController = TextEditingController();
Assign text value to the controller when you have your model instance as,
MyAccountViewModel model = new MyAccount.....
lastNameController.text = model.lastName;
setState(() {}); //Refresh if you need to
Assign lastNameController to your form field as,
child: Column(
children: <Widget>[
TextFormField(
controller: lastNameController,
),
],
)
Set the data to the text field
setState (() {
lastNameController.text = model.lastName ;
});
Assign Controller to your textform field
TextFormField(
controller: lastNameController,
),

disappear of the text written in the text field after pressing done

I am using a text field in my app to write comments, when the user type a text and press a button the text should be written to the database. However, I have a problem which is when I write the text then press done or return from the keyboard, the text disappear then there is no comment to be added. Is there any idea to save the value of the text in the text field even after pressing done or return?
Not sure why your input is disappearing but you can use a TextEditingController and pass that controller to a TextField.
Then access the value of the TextField using controller.text .
Here is a little example
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
String databaseText;
TextEditingController controller = TextEditingController();
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: TextField(
controller: controller,
onEditingComplete: () {
databaseText = controller.text;
print(databaseText);
},
),
),
),
);
}
}
this issue happen because the TextEditingController is rebuilt after you add your text. for me I used static controller to solve this problem.
static TextEditingController textController = TextEditingController();
and I highly recommend reading this
String _text;
final formKey = GlobalKey<FormState>();
Form(
key: formKey,
child: Column(children: <Widget>[
TextFormField(
autocorrect: false,
decoration: InputDecoration(labelText: "Text:"),
onSaved: (str) => _text = str,
)
]));
Then just pass your _text variable inside your paramater as your key value.
Try modifing contentPadding in InputDecoration. For example:
contentPadding: EdgeInsets.symmetric(horizontal: 2, vertical: 0)
The corresponding Flutter bug report is #29542: "Text getting cleared when using TextEditingController AND StreamBuilder".
There, user bizz84 commented, asking:
So the reason that this works with StatefulWidget is that the state is retained even across rebuilds, so I can use it to hold my TextEditingControllers.
Is this correct?
And rrousselGit replied:
Exactly :-)
Generally speaking, don't create anything but primitives in a StatelessWidget/InheritedWIdget. For everything else, you'll need a State
So, this appears to be the official answer: if you are creating anything other than primitives, you will need a State.

How to fix 'Text is null' in flutter

I want to create an app which has a TabBarView with two tabs. On the first Tab there is a Textfield and on the other tab there is a text widget which should display the text which you entered into Textfield
but I always get an error because text is null.( I'm new in programming with flutter)
I tried to initialize the variable in TextOutput class but it didn't work because the variable is final.
TabBarView(
children: <Widget>[
TextCreatePage(), TextOutput()
],
class TextCreatePageState extends State<TextCreatePage> {
String textvalue;
#override
Widget build(BuildContext context) {
return Center(child: TextField(
onChanged: (String value) {
setState(() {
textvalue = value;
TextOutput(textvalue: textvalue,);
});
class TextOutput extends StatelessWidget {
final String textvalue;
TextOutput({this.textvalue});
#override
Widget build(BuildContext context) {
return Text(textvalue);
}
}
So, for anyone that search this and got here, I'm using this to manage null String variables.
1. Show Empty Text
String nullText; //for null-safety change to: String? nullText;
//now, inside of your widget build
Text(nullText ?? '');
2. Not show Text Widget
String nullText;
//now, inside of your widget build
if(nullText != null)
Text(nullText);
with null-safety
String? nullText;
//now, inside of your widget build
if(nullText != null)
Text(nullText!);
Also you can show like this, but this show the null word
String nullText; //for null-safety change to String? nullText;
//now, inside of your widget build
Text('$nullText');
Live Example https://dartpad.dev/faab5bc3c2df9573c0a75a5ce3d4b4b9
It's not clear from the information your provided in your question what code causes the error, but I guess it is this line:
return Text(textvalue);
If you change it to
return textvalue != null ? Text(textvalue) : Container();
your error should go away.
Hector Aguero's answer is what I've been doing to workaround the error.
I use this everywhere:
Text(nullText ?? '');
But I wonder what's stopping Flutter team to make Text() widget support null value?
// This should be acceptable and simply not rendering any text
// why they don't allow this is beyond me
Text(nullText);