TextFormField losing value when changing focus - flutter

I am trying to move to other TextFormField but whenever I lose focus from first TextFormField text became empty, I search about this issue but I don't find any solution till now.
var _formKey = GlobalKey<FormState>();
Note note;
TextEditingController titleController=TextEditingController();
TextEditingController descriptionController=TextEditingController();
#override
Widget build(BuildContext context) {
TextStyle textStyle=Theme.of(context).textTheme.title;
titleController.text=note.title;
descriptionController.text=note.description;
// TODO: implement build
return WillPopScope(
onWillPop: (){
moveToLastScreen();
},
child:Scaffold(
appBar: AppBar(
title: Text("appBarTitle"),
leading: IconButton(icon:Icon(Icons.arrow_back),onPressed: (){
moveToLastScreen();
},),
),
body: Form(
key: _formKey,
child: Padding(
padding: EdgeInsets.only(top: 15.0,left: 15.0,right: 10.0),
child: ListView(
children: <Widget>[
//1st element
Padding(
padding: EdgeInsets.only(top: 15.0,bottom: 15.0,),
child: TextFormField(
validator: (String value){
if(value.isEmpty)
{
return "Please enter Title";
}
},
controller: titleController,
style: textStyle,
onSaved: (value){
debugPrint("Something changed in title Text field");
updateTitle();
},
/*onChanged: (value){
debugPrint("Something changed in title Text field");
updateTitle();
},*/
decoration: InputDecoration(
labelText: "Title",
labelStyle: textStyle,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0)
)
),
),
),
//2nd element
Padding(
padding: EdgeInsets.only(top: 15.0,bottom: 15.0,),
child: TextFormField(
validator: (String value){ //2nd step for form with validation
if(value.isEmpty)
{
return "Please enter principle amount";
}
},
onSaved: (value){
debugPrint("Something changed in Description Text field");
updateDescription();
},
controller: descriptionController,
style: textStyle,
/*onChanged: (value){
debugPrint("Something changed in Description Text field");
updateDescription();
},*/
decoration: InputDecoration(
labelText: "Description",
labelStyle: textStyle,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0)
)
),
),
),
//3th element
Padding(
padding: EdgeInsets.only(top: 15.0,bottom: 15.0),
child: Row(
children: <Widget>[
Expanded(
child: RaisedButton(
color: Theme.of(context).primaryColorDark,
textColor: Theme.of(context).primaryColorLight,
child: Text("Save",textScaleFactor: 1.5,),
onPressed: (){
setState(() {
if(_formKey.currentState.validate()) {
debugPrint("Save Pressed");
_save();
}
});
}
),
),
Container(width: 5.0,),
Expanded(
child: RaisedButton(
color: Theme.of(context).primaryColorDark,
textColor: Theme.of(context).primaryColorLight,
child: Text("Delete",textScaleFactor: 1.5,),
onPressed: (){
setState(() {
debugPrint("Delete Pressed");
_delete();
});
}
),
),
],
),
),
],
),
)),
));
}
Please suggest me I am new in flutter.

Remove titleController.text=note.title; descriptionController.text=note.description; from your build method and place it in initState method.
You will lose the value in the textField because those lines get executed anytime there is a rebuild, thereby replacing the values gotten from the textFields and replacing it with note.title and note.description which are empty at that point.
In other words, remove those lines and add this to your code.
#override
void initState() {
super.initState();
titleController.text=note.title;
descriptionController.text=note.description;
}

Related

How to do ElevatedButton disabled?

I want the button to be inactive until 10 characters are entered in the field. When 10 characters were entered, the button was active. And when it is inactive it is gray, and when it is active it is blue. How can I do that?
Here is the input code with the button:
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Padding(
padding: EdgeInsets.fromLTRB(
20, MediaQuery.of(context).size.height * 0, 20, 0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
onChanged: (String value) {
setState(() {
_showIcon = value.isNotEmpty;
});
},
controller: _inputController,
decoration: InputDecoration(
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.black, width: 2.0),
),
hintText: "(1201) 565-0123 ",
hintStyle: TextStyle(color: Colors.grey, fontSize: 15),
helperText: 'Enter your phone number',
helperStyle: TextStyle(color: Colors.grey, fontSize: 15),
suffixIcon: _showIcon
? IconButton(
onPressed: () {
setState(() {
_inputController.clear();
_showIcon = false;
});
},
icon: const Icon(Icons.close, color: Colors.grey),
) : null,
),
keyboardType: TextInputType.number,
inputFormatters: [maskFormatter],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ElevatedButton(
onPressed: () {
},
child: const Icon(Icons.arrow_forward_rounded, size: 25),
style: ElevatedButton.styleFrom(
shape: CircleBorder(),
padding: EdgeInsets.all(15)
)
),
],
)
],
),
),
),
);
}
}
You can call setState(() {}); on onChanged to update the UI, or add listener on _inputController.
ElevatedButton(
onPressed:
_inputController.text.length < 10 ? null : () {},
...
Passing onPressed:null will provide disable state.
Updating UI can be done
TextField(
onChanged: (String value) {
setState(() {});
},
....)
Or
late final TextEditingController _inputController;
#override
void initState() {
super.initState();
_inputController = TextEditingController()
..addListener(() {
setState(() {});
});
}
Use a variable like isEnabled and passing null to the onPress function will disable the button.
bool isEnabled=false;
void callbackfunction(){
// add your logic here.
}
....
....
TextField(
onChanged: (String value) {
if (value.length == 10){
setState(()=> isEnabled = true;)
}
else{
isEnabled=false;
}
setState(() {
_showIcon = value.isNotEmpty;
});
},
....
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ElevatedButton(
onPressed: isEnabled ? callbackfunction : null,
child: const Icon(Icons.arrow_forward_rounded, size: 25),
style: ElevatedButton.styleFrom(
color: isEnabled ? Colors.blue : Colors.grey,
shape: CircleBorder(),
padding: EdgeInsets.all(15)
)
),
],
)
],
),
P.S Please check the syntax I have just provided you the concept.

Flutter textfield how to position floating label and error label

I have a flutter design
when user selects textfield should fill with blue color then floating label padding above the border and textfield content padding should change.
When the textfield has an error both floating label and error label change the color and I have to position them.
Can someone help me how to achieve this as Reusable TextField
Please refer to the image.
Image with Animation
Rather then using label text I would consider using Text() above your TextFormField(). You can try this code below to get your result
FocusNode _focus = FocusNode();
bool _isValidate = true;
GlobalKey<FormState> _key = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.only(bottom: 5, left: 11),
child: Text("Field Value",
style: TextStyle(
color:
_focus.hasFocus ? Colors.blue : Colors.white))),
TextFormField(
focusNode: _focus,
onTap: () {
FocusScope.of(context).requestFocus();
},
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(50)),
filled: true,
fillColor:
_focus.hasFocus ? Colors.blue : Colors.transparent),
),
Divider(),
Padding(
padding: EdgeInsets.only(bottom: 5, left: 11),
child: Text("Field Value",
style: TextStyle(
color: _isValidate ? Colors.white : Colors.red))),
Form(
key: _key,
child: TextFormField(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(50)),
errorStyle:
TextStyle(fontSize: 14, fontWeight: FontWeight.w400)),
validator: (val) {
if (val!.length >= 5) {
setState(() {
_isValidate = false;
});
return "Field Value";
} else {
setState(() {
_isValidate = true;
});
return null;
}
},
),
),
Divider(),
ElevatedButton(
onPressed: () {
final form = _key.currentState;
form!.validate();
},
child: Text("Submit"))
],
)),
);
}

SingleChildScrollView is not scrolling with Stack Widget Flutter

Here when I type inside text fields Keyboard covers the Login button. So I need to scroll down to the Login button when typing. I tried wrapping LayoutBuilder with SingleChildScrollView and tried using Positioned widget inside Stack but nothing solved my issue. And I set physics to AlwaysScrollableScrollPhysics() inside SingleChildScrollView but it also didn't solve the problem. I can't figure out what I've done wrong. I would be grateful if anyone can help me with this issue
Here's my code
Material(
child: SingleChildScrollView(
child: Stack(
overflow: Overflow.clip,
children: <Widget>[
Image.asset(
'assets/login-screen-img.jpg'
),
Padding(
padding: const EdgeInsets.fromLTRB(16.0, 220.0, 16.0, 0),
child: Card(
child: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 24.0),
child: Form(
//associating global key with the form(It keeps track of the form)
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Email', style: TextStyle(fontSize: 14.0, color: Colors.grey),),
TextFormField( // email field
cursorColor: Colors.brown[500],
decoration: InputDecoration(
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.brown[500])
),
//hintText: 'Enter your Email'
),
// validation
validator: (email) => email.isEmpty ? 'Enter the email' : null,
onChanged: (emailInput) {
setState(() {
email = emailInput;
});
},
),
SizedBox(height: 16.0),
Text('Password', style: TextStyle(fontSize: 14.0, color: Colors.grey),),
TextFormField( // password field
cursorColor: Colors.brown[500],
decoration: InputDecoration(
//hintText: 'Enter your Password'
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.brown[500])
)
),
// validation
validator: (password) => password.length < 6 ? 'Password must be more than 6 characters' : null,
obscureText: true, // hide when type
onChanged: (passwordInput) {
setState(() {
password = passwordInput;
});
},
),
SizedBox(height: 48.0,),
Center(
child: RaisedButton( // login button
child: Text('LOG IN', style: TextStyle(fontSize: 16.0, color: Colors.white),),
color: Colors.brown[500],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25)
),
padding: EdgeInsets.fromLTRB(66.0, 16.0, 66.0, 16.0),
onPressed: () async {
if(_formKey.currentState.validate()) {
// show loading screen
setState(() {
loading = true;
});
dynamic result = await _auth.signInWithEmailAndPassword(email, password);
if(result == null) {
// stop showing loading screen/widget
setState(() {
loading = false;
});
// show an error message
Fluttertoast.showToast(
msg: 'Could not sign in!',
toastLength: Toast.LENGTH_SHORT,
);
}
}
},
),
),
SizedBox(height: 24.0),
Center(child: Text('Don\'t have and account ?' )),
SizedBox(height: 16.0,),
Center(
child: FlatButton( // sign up button
child: Text('SIGN UP', style: TextStyle(fontSize: 16.0, color: Colors.brown[500] )),
onPressed: (){
Navigator.push(context, MaterialPageRoute(
builder: (context) => SignUp()
));
},
),
)
],
),
),
),
),
)
],
),
),
);
Screenshot of my UI
Here I found that the issue is with the height of the stack. As #sajithlakmal mentioned in the comments, height of the stack is small and there is nothing to scroll. But in my case, I don't want to make an extra height than the screen height because this is just a login screen. I could easily solve the issue by replacing Material widget with Scaffold. inside the body of the Scaffold gives the required height when typing and able to scroll down.
Here's the working code.
Scaffold(
body: SingleChildScrollView(
child: Stack(
overflow: Overflow.visible,
children: <Widget>[
Image.asset(
'assets/login-screen-img.jpg',
alignment: Alignment.topCenter,
),
Padding(
padding: const EdgeInsets.fromLTRB(16.0, 220.0, 16.0, 0),
child: Card(
child: Padding(
padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 24.0),
child: Form(
//associating global key with the form(It keeps track of the form)
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Email', style: TextStyle(fontSize: 14.0, color: Colors.grey),),
TextFormField( // email field
cursorColor: Colors.brown[500],
decoration: InputDecoration(
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.brown[500])
),
//hintText: 'Enter your Email'
),
// validation
validator: (email) => email.isEmpty ? 'Enter the email' : null,
onChanged: (emailInput) {
setState(() {
email = emailInput;
});
},
),
SizedBox(height: 16.0),
Text('Password', style: TextStyle(fontSize: 14.0, color: Colors.grey),),
TextFormField( // password field
cursorColor: Colors.brown[500],
decoration: InputDecoration(
//hintText: 'Enter your Password'
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.brown[500])
)
),
// validation
validator: (password) => password.length < 6 ? 'Password must be more than 6 characters' : null,
obscureText: true, // hide when type
onChanged: (passwordInput) {
setState(() {
password = passwordInput;
});
},
),
SizedBox(height: 48.0,),
Center(
child: RaisedButton( // login button
child: Text('LOG IN', style: TextStyle(fontSize: 16.0, color: Colors.white),),
color: Colors.brown[500],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25)
),
padding: EdgeInsets.fromLTRB(66.0, 16.0, 66.0, 16.0),
onPressed: () async {
if(_formKey.currentState.validate()) { // check validation
// show loading screen
setState(() {
loading = true;
});
dynamic result = await _auth.signInWithEmailAndPassword(email, password);
if(result == null) {
// stop showing loading screen/widget
setState(() {
loading = false;
});
// show an error message
Fluttertoast.showToast(
msg: 'Could not sign in!',
toastLength: Toast.LENGTH_SHORT,
);
}
}
},
),
),
SizedBox(height: 24.0),
Center(child: Text('Don\'t have and account ?' )),
SizedBox(height: 16.0,),
Center(
child: FlatButton( // sign up button
child: Text('SIGN UP', style: TextStyle(fontSize: 16.0, color: Colors.brown[500] )),
onPressed: (){
Navigator.push(context, MaterialPageRoute(
builder: (context) => SignUp()
));
},
),
)
],
),
),
),
),
)
],
),
),
);

Validator for a Custom InputTextField

I have created my own custom textinputfield, everything do has come up well but the thing is how to give that a validator upon the button click?
This is my custom inputfield,
class CustomInputField extends StatelessWidget {
bool _validate = false;
Icon fieldIcon;
String hintText;
TextInputType textType;
CustomInputField(this.fieldIcon, this.hintText, this.textType);
#override
Widget build(BuildContext context) {
return Container(
width: 300,
child: Material(
elevation: 5.0,
borderRadius: BorderRadius.all(Radius.circular(10.0)),
color: Colors.deepOrange,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(12.0),
child: fieldIcon,
),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(topRight: Radius.circular(10.0), bottomRight: Radius.circular(10.0)),
),
width: 250,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: _text,
decoration: InputDecoration(
border: InputBorder.none,
hintText: hintText,
fillColor: Colors.white,
errorText: _validate? 'Value can\'t be empty' : null,
filled: true,
),
keyboardType: textType,
style: TextStyle(
fontSize: 15.0,
color: Colors.black,
),
),
),
),
],
)
),
);
}
}
And this is how I'm calling that in different pages
CustomInputField(
Icon(
Icons.lock,
color: Colors.white,
),
'Password',
TextInputType.visiblePassword),
So upon click of the button if the text field is empty, i need to give in the errorText, so could anyone help me out?
You need to do a couple of things.
Create a GlobalKey type FormState for your Stateful Widget.
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
Create a TextFormField with a validator and onSave callback.
Form(
key: _formKey, //Give you form it's key.
child: Column(
children: <Widget>[
TextFormField(
validator: (input) {
if (input.isEmpty) { //Here I check if the field data is empty.
return "Field cannot be empty.;
}
},
//Update what ever variable you want with the validated data.
//Here I picked a variable named foo.
onSaved: (input) => foo = input,
],
),
),
Make a button that uses the _formKey to call the validate function and save function.
RaisedButton(
child: Text("Do Something"),
onPressed: () {
//Validate will run on the TextFormField and return the error provided.
if (_formKey.currentState.validate()) {
_formKey.currentState.save(); //If validated, we will update our foo variable.
}
},
),

Catch tap event on TextFormField

I am trying to catch the tap event on TextFormField into a flutter Form.
I use a GestureDetector to do that with the TextFormField as child but nothing is firing when a click on it :
#override
Widget build(BuildContext context) {
return new Scaffold(
key: _scaffoldKey,
appBar: new AppBar(title: const Text('Recherche de sorties')),
body: new DropdownButtonHideUnderline(
child: new Form(
key: _formKey,
autovalidate: _autovalidate,
child: new ListView(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
children: <Widget>[
new DatePicker(
labelText: 'Date',
selectedDate: widget.request.dateDebut,
initialDate: widget.request.dateDebut,
firstDate: new DateTime.now().add(new Duration(days: -1)),
lastDate: new DateTime.now().add(new Duration(days: 365 * 4)),
selectDate: (DateTime value) {
setState(() {
widget.request.dateDebut = value;
});
},
datePickerMode: DatePickerMode.day,
icon: const Icon(Icons.date_range),
),
new InputDecorator(
decoration: const InputDecoration(
labelText: 'Rayon',
hintText: '-- Choisissez un rayon --',
icon: const Icon(Icons.settings_backup_restore),
),
isEmpty: widget.request.rayon == null,
child: new DropdownButton<String>(
value: widget.request.rayon.toString(),
isDense: true,
onChanged: (String newValue) {
setState(() {
widget.request.rayon = int.parse(newValue);
});
},
items: _rayons.keys.map((int key) {
return new DropdownMenuItem<String>(
value: key.toString(),
child: new Text(_rayons[key]),
);
}).toList(),
),
),
new GestureDetector(
onTap: () async {
print("Container clicked");
Prediction p = await showGooglePlacesAutocomplete(
context: context,
apiKey: Consts.googlePlacesApiKey,
mode: Mode.fullscreen,
language: "fr",
components: [new Component(Component.country, "fr")]);
if (p != null) {
(_scaffoldKey.currentState).showSnackBar(
new SnackBar(content: new Text(p.description)));
}
},
child: new TextFormField(
// controller: controller,
decoration: const InputDecoration(
icon: const Icon(Icons.room),
hintText: 'Où êtes vous ?',
labelText: 'Localisation',
),
),
),
new Container(
padding: const EdgeInsets.all(20.0),
alignment: Alignment.center,
child: new Align(
alignment: const Alignment(0.0, -0.2),
child: new ButtonBar(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new RaisedButton(
child: const Text('ANNULER'),
onPressed: _fermerCritereRecherche,
),
new RaisedButton(
child: const Text('VALIDER'),
onPressed: _valider,
),
],
),
)),
]),
),
),
);
}
If i replace :
new GestureDetector(
onTap: () async {
print("Container clicked");
Prediction p = await showGooglePlacesAutocomplete(
context: context,
apiKey: Consts.googlePlacesApiKey,
mode: Mode.fullscreen,
language: "fr",
components: [new Component(Component.country, "fr")]);
if (p != null) {
(_scaffoldKey.currentState).showSnackBar(
new SnackBar(content: new Text(p.description)));
}
},
child: new TextFormField(
// controller: controller,
decoration: const InputDecoration(
icon: const Icon(Icons.room),
hintText: 'Où êtes vous ?',
labelText: 'Localisation',
),
),
),
By a simple Container it is working :
new GestureDetector(
onTap: () async {
print("Container clicked");
Prediction p = await showGooglePlacesAutocomplete(
context: context,
apiKey: Consts.googlePlacesApiKey,
mode: Mode.fullscreen,
language: "fr",
components: [new Component(Component.country, "fr")]);
if (p != null) {
(_scaffoldKey.currentState).showSnackBar(
new SnackBar(content: new Text(p.description)));
}
},
child: new Container(
width: 80.0,
height: 80.0,
margin: new EdgeInsets.all(10.0),
color: Colors.black),
),
Do you have any ideas how to make GestureDetector work with TextFormField ? Maybe with a controller but i have tried without any success
Thanks in advance
Simply use onTap Method of TextFormField:
TextFormField(
onTap: () {
print("I'm here!!!");
}
)
Wrap TextFormField widget With AbsorbPointer widget , then OnTap() works definitely
here is an example:-
GestureDetector(
onTap: () => dialog(),
child: AbsorbPointer(
child: TextFormField(
textInputAction: TextInputAction.newline,
decoration: new InputDecoration(
fillColor: Colors.grey,
border: OutlineInputBorder(
borderRadius:
BorderRadius.all(Radius.circular(6.0)),
borderSide:
BorderSide(color: Colors.grey[100]),
gapPadding: 4),
labelText: "Enter your mood",
labelStyle: TextStyle(
letterSpacing: 1,
color: Colors.grey,
fontSize: 13),
hintMaxLines: 1),
validator: (val) {
if (val == "") return "Field can't be empty";
},
keyboardType: TextInputType.text,
enabled: true,
textAlign: TextAlign.justify,
minLines: 3,
autofocus: false,
style: new TextStyle(
fontSize: 16.0,
color: Colors.black,
),
maxLines: 10,
),
),
),
Wrap AbsorbPointer Widget with Gesture Detector, and then work in onTap(). it will work fine.
I have found a solution by using the InputDecorator (from the flutter gallery) :
child: new InputDecorator(
decoration: const InputDecoration(
labelText: 'Localisation',
icon: const Icon(Icons.room),
),
child: widget.request.localisationLibelle != null
? new Text(widget.request.localisationLibelle)
: new Text("-- Choisissez un lieu --"),
),
Instead of using a TextFormField that catch the tap at the place of the GestureDetector I use a simple child Text of the InputDecorator widget.
I just solved this myself using Flutter 0.6.0.
The GestureDetector object takes in a behavior property from this enum to determine how to defer actions.
Small snippet of the GestureDetector taking priority over a TextFormField:
new GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.opaque,
child: new TextFormField(
enabled: onTap == null,
*other stuff here*
),
)
The onTap object is a Function object I declare outside of this. I also set the enabled property based on my onTap object, so I can ensure that if I want to capture a tap, the text form field is disabled.
onTap function is working but if you use it with InputDecoration { enabled: false }, it will not working.
decoration: InputDecoration(
enabled: false, // not working when on tap
isDense: true,
filled: true,
fillColor: _listRadioItemPosition.isEmpty ? Colors.grey[100] : Colors.white,
border: UnderlineInputBorder(borderSide: BorderSide(color: Color(0xFFC4C4C4), width: 1)),
),
onTap: (){
displayBottomSheetPosition(context);
},