Flutter - TextField rebuilds widget on focus - flutter

The effect is that you can't open the keyboard. I did some digging around this issue elsewhere, and most issues are when the widget is stateful (but mine is not).The following is the LoginWidget. I'm using the provider package, which I suspect might be doing something underneath the covers. Can anyone spot something I don't see? :
class LoginPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
String email, password;
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
return ChangeNotifierProvider<LoginModel>(
builder: (context) => LoginModel(ViewState.Idle),
child: Consumer<LoginModel>(
builder: (context, model, child) => Scaffold(
appBar: AppBar(
title: Text("Sign in"),
),
body: Form(
key: formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextFormField(
validator: (input) {
if (input.isEmpty) {
return "Email required";
}
},
onSaved: (input) => email = input,
decoration: InputDecoration(labelText: 'Email'),
),
TextFormField(
validator: (input) {
if (input.isEmpty) {
return "Password required";
}
},
onSaved: (input) => password = input,
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
),
model.state == ViewState.Loading
? CircularProgressIndicator()
: RaisedButton(
onPressed: () async {
if (formKey.currentState.validate()) {
formKey.currentState.save();
bool success =
await model.login(email, password);
if (success) {
// Navigator.pushNamed(context, '/');
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => HomePage()),
);
}
}
},
child: Text('Sign in'),
),
if (model.state == ViewState.Error)
Center(child: Text(model.apiErrorMessage))
],
)),
)));
}
}

The solution was to move the final GlobalKey<FormState> formKey = GlobalKey<FormState>() out of the builder and make it static.

But if you have a list of "LoginPage" it will trigged widget use the same key.
I prefer wrap the parent widget with a Consumer widget.

Related

error message nt going even if my textfield is not empty

Before I reach to my alert demo.. I am stuck in the beginning..
In my alert dialog there is a textField in content,
I have set an error text if textfield is empty but even after typing this error text not disappearing..it remains there....what is my fault...
here is my code
import 'package:flutter/material.dart';
class HomeScreen extends StatefulWidget {
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
String name='';
TextEditingController? txtname;
get _errortext {
final str=txtname!.value.text;
if(str.isEmpty)
return 'Cant be emtpy';
else
return null;
}
#override
void initState() {
// TODO: implement initState
super.initState();
txtname=TextEditingController();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Demo Page'),),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('My Name is '+name),
SizedBox(height: 20.0,),
ElevatedButton(onPressed: (){
openDialog();
}, child: Text("Get Name")),
],),),);}
void openDialog() {showDialog(
context: context,
builder: (context)=>AlertDialog(
title: Text('Enter Your name here'),
content: TextField(
controller: txtname,
onChanged: (value){
setState(() {});},
decoration: InputDecoration(
hintText: 'Enter Name',
errorText: _errortext,
),),
actions: [
TextButton(onPressed: (){
}, child: Text('Submit')),
],
));
}}
To update inside dialog, you need to use StatefulBuilder and use its setState.
void openDialog() {
showDialog(
context: context,
builder: (context) => StatefulBuilder(
builder: (context, setState) => AlertDialog(
title: Text('Enter Your name here'),
content: TextField(
controller: txtname,
onTap: () {
setState(() {});
},
onChanged: (value) {
setState(() {});
},
decoration: InputDecoration(
hintText: 'Enter Name',
errorText: _errortext,
),
),
actions: [
TextButton(
onPressed: () {},
child: const Text('Submit'),
),
],
),
));
}
You can also separate the content StatefulWidget which is not needed in cases like this.

Flutter: how to prompt user for input using showDialog after tapping on ListTile, then using the input

Assuming i have the codes below,
Widget _myListView(BuildContext context) {
return ListView(
children: <Widget>[
ListTile(
title: Text('Sun'),
onTap:() async{
await showDialog<String>(
context:context,
--- not sure how to proceed from here --
}
}
Navigator.pop(context); //this code makes sure it go back to another page after done with the input processing.
),
ListTile(
title: Text('Moon'),
),
ListTile(
title: Text('Star'),
),
],
);
}
how to use the input onTap ListTile, run some method to use the input, and pop back to previous page.
Call function _showMyDialog(); and see data in the console:
I used Row() to work in landscape.
final myController = TextEditingController();
late String userData;
Future<void> _showMyDialog() async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Row(
children: [
Expanded(
child: TextField(
controller: myController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Enter any data',
hintText: 'Enter any data',
),
),
),
Expanded(
child: TextButton(
child: const Text('OK'),
onPressed: () {
userData = myController.text;
myController.text = '';
FocusManager.instance.primaryFocus?.unfocus();
print(userData);
Navigator.of(context).pop();
},
),
)
]
)
);
},
);
}
#override
void dispose() {
myController.dispose();
super.dispose();
}

How to add validation on the form in flutter

I did manage to get no error for the code but cannot validate the form and show the error message. I have 3 component dart code which is the password, input field, and button. There is also one body dart in the library. ..................
I did manage to get no error for the code but cannot validate the form and show the error message. I have 3 component dart code which is the password, input field, and button. There is also one body dart in the library. ..................
import 'package:flutter_auth/Screens/Login/components/background.dart';
import 'package:flutter_auth/Screens/Login/components/uploadpage.dart';
import 'package:flutter_auth/components/rounded_button.dart';
import 'package:flutter_auth/components/rounded_input_field.dart';
import 'package:flutter_auth/components/rounded_password_field.dart';
class Body extends StatelessWidget {
const Body({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Background(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
"LOGIN",
style: TextStyle(fontWeight: FontWeight.bold),
),
SizedBox(height: size.height * 0.03),
RoundedInputField(
hintText: "Username",
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value.length == 0)
return "Please enter email";
else if (!value.contains("#"))
return "Please enter valid email";
else
return null;
},
onChanged: (value) {},
),
PasswordField(
onSaved: (value) {},
),
RoundedButton(
text: "LOGIN",
press: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
},
),
],
),
),
);
}
}
class SecondScreen extends StatelessWidget {
goBackToPreviousScreen(BuildContext context) {
Navigator.pop(context);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Home Page"),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.upload_file,
color: Colors.white,
),
onPressed: () {
{
Navigator.push(
context,
MaterialPageRoute(builder: (context) => uploadpage()),
);
} // do something
},
)
],
),
body: Stack(fit: StackFit.expand, children: <Widget>[
Positioned(
bottom: 0,
width: MediaQuery.of(context).size.width,
child: Center(
child: RaisedButton(
color: Colors.purple[400],
textColor: Colors.white,
onPressed: () {
goBackToPreviousScreen(context);
},
child: Text('Logout')),
),
)
]));
}
Here's a basic example of how Forms work:
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
GlobalKey<FormState> formKey = new GlobalKey();
String formFieldValue;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Form(
key: formKey,
child: Column(
children: [
TextFormField(
validator: (input) {
if (input.isEmpty) {
return 'Please type something';
}
return null;
},
onSaved: (input) => formFieldValue = input,
),
RaisedButton(
onPressed: submitForm,
child: Text(
'Submit'
),
)
],
),
)
);
}
submitForm() {
final formState = formKey.currentState;
if (formState.validate()) {
formState.save();
// then do something
}
}
}
Here the validator does not get called automatically. You have to call it manually onPressed of a button or something.
Here you need to wrap your column in Form widget and give it a key. Onpressed you need to validate it by calling key.currentState.validate()
final _formreg = GlobalKey<FormState>();
Form(key:_formreg, child:Column(children:
[RoundedInputField() ]
));
RaisedButton(onPressed:()=> {
a=_formreg.currentState.validate();
} )
a is a boolean value

Deleting widget from list from a press within the widget

I have a list of custom TextFormField's that i added to them a delete icon
all I am trying to do is when I press the delete button it will be deleted from the list and the view
i tried adding a function to my form field with no success
I think my approach isn't the best way to implement what i want, I am open to any idea
here is the code
import 'package:flutter/material.dart';
typedef DeleteCallback = void Function(Key key);
class DynamicFormField extends FormField<String>{
DynamicFormField({
Key key,
FormFieldSetter<String> onSaved,
FormFieldValidator<String> validator,
String initialValue = "",
bool autovalidate = false,
DeleteCallback onDelete(Key key),
}) : super(
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
autovalidate: autovalidate,
builder: (FormFieldState<String> state) {
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
flex: 5,
child: TextFormField(
decoration: const InputDecoration(
hintText: 'Enter Player Name',
),
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
autovalidate: autovalidate,
),
),
IconButton(
icon: Icon(Icons.delete_outline),
onPressed: onDelete(key)
),
],
);
}
);
}
DynamicFormField(
key: UniqueKey(),
validator: (value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
onSaved: (input) => {players.add(Player(input))},
onDelete: f,
),
);
}
void f(Key key){
fields.removeWhere((item) => item.key == key);
}
tnx
I solved it using ListView builder
import 'package:flutter/material.dart';
import 'package:rummy/models/player.dart';
import 'package:rummy/screens/game_screen.dart';
class NewGame extends StatefulWidget {
NewGame({Key key}) : super(key: key);
#override
_NewGameState createState() => _NewGameState();
}
class _NewGameState extends State<NewGame> {
final _formKey = GlobalKey<FormState>();
List<Widget> fields;
List<Player> players;
_NewGameState() {
players = new List<Player>();
fields = new List();
print(players);
fields.add(generateField());
}
Widget generateField() {
return TextFormField(
decoration: const InputDecoration(
hintText: 'Enter Player Name',
),
onSaved: (input) => {players.add(Player(input))},
validator: (value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SizedBox.expand(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
children: <Widget>[
Form(
key: _formKey,
child: Expanded(
child: ListView(
children: <Widget>[
SizedBox(
height: MediaQuery.of(context).size.height,
child: Builder(
builder: (BuildContext context) {
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: fields.length,
itemBuilder:
(BuildContext context, int postion) {
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: fields[postion],
),
IconButton(
icon: Icon(Icons.delete_outline),
onPressed: () => {
setState(() {
print(postion);
fields.removeAt(postion);
})
}),
],
);
},
);
},
),
)
],
),
)),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () {
print("asdasd");
if (_formKey.currentState.validate()) {
players.clear();
_formKey.currentState.save();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => GameScreen(players),
));
} else
print(_formKey.currentState.validate());
},
child: Text('Submit'),
),
RaisedButton(
onPressed: () {
setState(() {
fields.add(generateField());
});
},
child: Text('Add New Player'),
),
],
),
],
mainAxisAlignment: MainAxisAlignment.center,
),
),
),
);
}
}
I used this
https://github.com/MobMaxime/Flutter-To-Do-App/blob/master/lib/screens/todo_list.dart

Keyboard keeps disappearing while entering data in Flutter TextFormField

I've this simple login screen with two TextFormField for email and password. When I try to enter text in any of these text boxes, keyboard appears momentarily and disappears every time I focus on text field to enter data.
class LogInPage extends StatefulWidget {
final String title;
LogInPage({Key key, this.title}) : super(key: key);
#override
_LogInPageState createState() => new _LogInPageState();
}
class _LogInPageState extends State<LogInPage> {
static final formKey = new GlobalKey<FormState>();
String _email;
String _password;
Widget padded({Widget child}) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
child: child,
);
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Container(
padding: const EdgeInsets.all(16.0),
child: Column(children: [
Card(
child:
Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Container(
padding: const EdgeInsets.all(16.0),
child: Form(
key: formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
padded(
child: TextFormField(
key: Key('email'),
decoration: InputDecoration(labelText: 'Email'),
autocorrect: false,
validator: (val) => val.isEmpty
? 'Email can\'t be empty.'
: null,
onSaved: (val) => _email = val,
)),
padded(
child: TextFormField(
key: Key('password'),
decoration:
InputDecoration(labelText: 'Password'),
obscureText: true,
autocorrect: false,
validator: (val) => val.isEmpty
? 'Password can\'t be empty.'
: null,
onSaved: (val) => _password = val,
)),
]))),
])),
])));
}
}
This is the form:
EDIT
I think the problem lies in the way I'm calling this page like below. Is it okay to call another page from FutureBuilder ?
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("LogIn Demo"),
),
body: FutureBuilder<FirebaseUser>(
future: Provider.of<FireAuthService>(context).currentUser(),
builder: (context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.error != null) {
return Text(snapshot.error.toString());
}
return snapshot.hasData
? UserProfilePage(snapshot.data)
: LogInPage(title: 'Login');
} else {
return Container(
child: CircularProgressIndicator(),
);
}
},
),
);
}
Clean your code first and rebuild, perform testing with real device as well.
static GlobalKey<FormState> _formKey = new GlobalKey<FormState>();