Flutter: Submit TextFormField on Enter - flutter

I'm working on a desktop application for Windows with Flutter. I want to listen to keyboard keys. I made a Login page and it has two TextFormFields, (one for Username and the other for Password).
When I press the 'enter' key on the keyboard, I want the form to be submitted as I pressed on 'Login' button. So if I pressed the 'enter' key from either Username or Password text fields, I want the app to act exactly as if I pressed the 'Login' Button.
Here is a picture of the Login Page:
--> Login Page
Here is the code of Login Page:
import 'package:flutter/material.dart';
import '../../api/services/login_services.dart';
import '../../api/models/login_models.dart';
import '../../constants/constant_methods.dart';
import '../main_page/main_screen.dart';
import '../../constants/constant_variables.dart';
class LoginPage extends StatefulWidget {
const LoginPage({Key? key}) : super(key: key);
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
TextEditingController usernameController = TextEditingController();
TextEditingController passwordController = TextEditingController();
late LoginRequestModel loginRequestModel;
bool isUsernameEmpty = false;
bool isPasswordEmpty = false;
bool secure = true;
bool notSecure = false;
bool isLoading = false;
#override
void initState() {
super.initState();
loginRequestModel = LoginRequestModel(
usernameController.text,
passwordController.text,
);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
decoration: const BoxDecoration(
gradient: loginPageGradient,
),
child: Center(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/images/cloud_soft_logo.png',
height: 90,
),
const SizedBox(height: 15),
Text(
'Hoomy\'s Real Estate',
style: Theme.of(context).textTheme.headline4,
),
const SizedBox(height: 20),
Container(
width: 310,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: borderRadius(10.0),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 20),
Text(
'Welcome',
style: Theme.of(context)
.textTheme
.headline6!
.copyWith(
fontWeight: FontWeight.w600,
foreground: Paint()..shader = linearGradient),
),
Text(
'Please Log in to Your Account',
style: Theme.of(context).textTheme.headline2,
),
const SizedBox(height: 10),
loginTextField("Username", "Username can't be empty",
usernameController),
loginTextField("Password", "Password can't be empty",
passwordController),
forgetPasswordButton(),
loginButton(context),
const SizedBox(height: 40),
],
),
),
],
),
),
),
),
),
);
}
Widget loginTextField(
String labelText,
String errorText,
TextEditingController controller,
) {
return SizedBox(
width: 250,
child: TextFormField(
decoration: InputDecoration(
labelText: labelText,
errorText: labelText == "Username"
? isUsernameEmpty
? errorText
: null
: isPasswordEmpty
? errorText
: null,
suffixIcon: labelText == "Username"
? IconButton(
onPressed: () {}, icon: const Icon(Icons.person, size: 20))
: IconButton(
onPressed: () {
setState(() {
secure = !secure;
});
},
icon: Icon(
secure ? Icons.visibility_off : Icons.visibility,
size: 20,
),
)),
obscureText: labelText == "Username" ? notSecure : secure,
controller: controller,
),
);
}
Padding forgetPasswordButton() {
return Padding(
padding: const EdgeInsets.all(20).copyWith(right: 30),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Theme(
data: ThemeData(splashColor: Colors.transparent),
child: TextButton(
child: const Text('Forget Password'),
onPressed: () {},
),
),
],
),
);
}
DecoratedBox loginButton(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: borderRadius(50.0),
gradient: loginButtonGradient,
),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.transparent,
fixedSize: const Size(250, 50),
shape: RoundedRectangleBorder(
borderRadius: borderRadius(50.0),
)),
child: isLoading
? CircularProgressIndicator(
color: Colors.blue[800],
backgroundColor: Colors.grey,
)
: const Text('Login'),
onPressed: () {
setState(() {
usernameController.text.isEmpty
? isUsernameEmpty = true
: isUsernameEmpty = false;
passwordController.text.isEmpty
? isPasswordEmpty = true
: isPasswordEmpty = false;
});
setState(() {
isLoading = true;
});
LoginService.login(usernameController.text, passwordController.text)
.then((response) {
setState(() {
isLoading = false;
});
if (response) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const MainScreen(),
),
);
} else if (!response &&
usernameController.text.isNotEmpty &&
passwordController.text.isNotEmpty) {
showErrorDialog(context, "Invalid Username or Password!");
}
});
}),
);
}
Future<dynamic> showErrorDialog(BuildContext context, String dialogContent) {
return showDialog(
context: context,
builder: (BuildContext context) {
return Center(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: SizedBox(
width: 350,
child: AlertDialog(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: borderRadius(10.0)),
title: Text("Error",
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: Colors.red)),
content: Text(dialogContent,
style: Theme.of(context)
.textTheme
.headline2!
.copyWith(color: Colors.black87)),
actions: <Widget>[
TextButton(
child: Text("OK",
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: Colors.blue)),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
),
),
);
},
);
}
}

Add the field onFieldSubmitted and a FormKey:
// Widget attribute
final _formKey = GlobalKey<FormState>();
// Widget method: build()
Form(
key: _formKey,
child:
TextFormField(
onFieldSubmitted: (value) {
print('ENTER pressed');
// Will trigger validation for ALL fields in Form.
// All your TextFormFields (email, password) share
// the SAME Form and thus the SAME _formKey.
if (_formKey.currentState!.validate()) {
print('ALL FIELDS ARE VALID, GO ON ...');
}
}
)
)
Docs
https://api.flutter.dev/flutter/material/TextFormField-class.html

Related

Flutter - bottomModalSheet can't validate a textfield widget

I have attached a project which is having a bottom modal sheet. Which sheet contains three TextField as name, number and email. So here I have implemented CRUD (Create, read, update and delete) operation and it's fine working. But without validating the TextField it shows in the HomePage. although if I miss to enter name or number still it's passing the data to the homepage card. I have tried many validating options but didn't worked out. If anyone can please help me.
My code:
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Map<String, dynamic>> _contacts = [];
bool _isLoading = true;
final bool _validatename = true;
final bool _validatenumber = true;
final bool _validateemail = true;
void _refreshContacts() async {
final data = await Contact.getContacts();
setState(() {
_contacts = data;
_isLoading = false;
});
}
#override
void initState() {
super.initState();
_refreshContacts();
}
final _nameController = TextEditingController();
final _numberController = TextEditingController();
final _emailController = TextEditingController();
final bool _validate = false;
void _showForm(int? id) async {
if (id != null) {
final existingContact = _contacts.firstWhere((element) => element['id'] ==id);
_nameController.text = existingContact['name'];
_numberController.text = existingContact['number'];
_emailController.text = existingContact['email'];
}
showModalBottomSheet(context: context,
elevation: 5,
isScrollControlled: true,
builder: (_) => Container(
padding: EdgeInsets.only(top: 15, left: 15, right: 15, bottom: MediaQuery.of(context).viewInsets.bottom + 120),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
TextField(
controller: _nameController,
decoration: const InputDecoration(
hintText: "Name",
),
),
const SizedBox(
height: 10.0,
),
TextField(
keyboardType: TextInputType.number,
controller: _numberController,
decoration: const InputDecoration(
hintText: "Numbers",
),
),
const SizedBox(
height: 10.0,
),
TextField(
// keyboardType: TextInputType.emailAddress,
controller: _emailController,
decoration: const InputDecoration(
hintText: "Email Address",
),
),
const SizedBox(
height: 20.0,
),
Row(
children: [
ElevatedButton(
onPressed: () async {
if (id == null) {
await _addContact();
}
if (id != null) {
await _updateContact(id);
}
Navigator.of(context).pop();
_nameController.text = '';
_numberController.text = '';
_emailController.text = '';
},
child: Text(id == null ? 'Create New' : 'Update'),
),
const SizedBox(
width: 10.0,
),
ElevatedButton(onPressed: () async {
_nameController.text = '';
_numberController.text = '';
_emailController.text = '';
}, child: const Text("Clear")),
const SizedBox(
width: 10.0,
),
ElevatedButton(onPressed: (){
Navigator.pop(context);
}, child: const Text("Go Back")),
],
),
]),
));
}
Future<void> _addContact() async {
await Contact.createContact(
_nameController.text, _numberController.text, _emailController.text
);
_refreshContacts();
}
Future<void> _updateContact(int id) async {
await Contact.updateContact(id, _nameController.text, _numberController.text, _emailController.text );
_refreshContacts();
}
void _deleteContact(int id) async {
await Contact.deleteContact(id);
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Sccessfully Contact Deleted")));
_refreshContacts();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Contact App",),
backgroundColor: Colors.blueAccent,
centerTitle: true,
toolbarHeight: 80,
),
body: _isLoading ? const Center(child: CircularProgressIndicator(),) :
ListView.builder(
itemCount: _contacts.length,
itemBuilder: (context, index) =>
Card(
elevation: 5,
shape: const Border(
right: BorderSide(color: Colors.blue, width: 10.0),
),
color: Colors.orange[200],
margin: const EdgeInsets.all(15.0),
child: Material(
elevation: 20.0,
shadowColor: Colors.blueGrey,
child: ListTile(
title: Text(_contacts[index]['name'], style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(_contacts[index]['number'], style: const TextStyle(color: Colors.grey, fontSize: 18),),
const SizedBox(
height: 5.0,
),
Text(_contacts[index]['email'], style: const TextStyle(fontSize: 17, color: Colors.black),),
],
),
trailing: SizedBox(
width: 100,
child: Row(
children: [
IconButton(onPressed: () => _showForm(_contacts[index]['id']), icon: const Icon(Icons.edit, color: Colors.blueGrey,)),
IconButton(onPressed: () => _deleteContact(_contacts[index]['id']), icon: const Icon(Icons.delete, color: Colors.red,)),
],
),
),
),
),
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add, size: 28,),
onPressed: () => _showForm(null),
),
);
}
}
Above codes are from homepage. I need only the validating part + if anyone can know how to show the each card in another page using page route. Actually this is a contact app. I have tried the new screen to show the full details but couldn't.
You can return when any of the field is empty like
ElevatedButton(
onPressed: () async {
if (_nameController.text.isEmpty ||
_numberController.text.isEmpty ||
_emailController.text.isEmpty) {
return;
}
},
child: Text(id == null ? 'Create New' : 'Update'),
),
But it will be better to use Form widget TextFormFiled with validator . Find more on validation
final _formKey = GlobalKey<FormState>();
showModalBottomSheet(
context: context,
elevation: 5,
isScrollControlled: true,
builder: (_) => Container(
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
controller: _nameController,
decoration: const InputDecoration(
hintText: "Name",
),
),
Row(
children: [
ElevatedButton(
onPressed: () async {
final isValided =
_formKey.currentState?.validate();
if (isValided == true) {}
},
child: Text(id == null ? 'Create New' : 'Update'),
),

How can I give message when the text field is same as before?

When the name is already there
Without any change in the text field when clicking its showing
I was trying to update the name in firebase, it was working fine, but each time when I'm clicking edit without any change it's uploading to firebase. I don't want that to happen. Please help me out
Here is my code:
`import 'package:flutter/material.dart';
class EditProfile extends StatefulWidget {
#override
_EditProfileState createState() => _EditProfileState();
}
class _EditProfileState extends State<EditProfile> {
final TextEditingController nameController = TextEditingController();
final _keyChange = GlobalKey<FormState>();
bool btnTextChange = false;
TextEditingController _editingController = TextEditingController(text:"Johny Boy");
String initialText = "Johny Boy";
Widget _editTitleContainer() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.only(left: 20,top: 20,bottom: 20),
child: Text(
"Name",
),
),
Container(
decoration: new BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
color: Colors.white,
),
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.symmetric(horizontal: 20),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 20),
child:
Form(
key: _keyChange,
child: TextField(
enableInteractiveSelection: true,
onSubmitted: (newValue){
setState(() {
initialText = newValue;
});
},
onChanged: (String? value) {
value!.isEmpty ? changeButtonColor(false) : changeButtonColor(true);
},
controller: _editingController,
decoration: InputDecoration(
border: InputBorder.none,
),)
)
),
],
);
}
#override
void dispose() {
_editingController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("sample"),
),
body: SafeArea(
child: Container(
child: Column(
children: [
_editTitleContainer(),
Container(
alignment: Alignment.center,
margin:EdgeInsets.only(top: 20),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 20),
),
onPressed: () {
if (_keyChange.currentState!.validate()) {
_keyChange.currentState!.save();
successMessage();
}
},
child: Text(
"EDIT",
style: TextStyle(
color: Color(0xFF527DAA),
letterSpacing: 1.5,
fontSize: 15,
fontWeight: FontWeight.bold,
fontFamily: 'OpenSans',
),
),
),
),
],
),
),
),
);
}
void successMessage() {
String name = _editingController.text.trim();
if (name.isNotEmpty) {
setState(() {
showDialog(
context: context,
builder: (ctx) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16)),
title: Text("Name Changed Success"),
actions: [
TextButton(
child: Text("Ok"),
onPressed: () {
Navigator.of(ctx).pop();
},
),
],
);
},
);
});
}else {
setState(() {
showDialog(
context: context,
builder: (ctx) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16)),
title: Text("Error!"),
content: Text("Enter Your Name"),
actions: [
TextButton(
child: Text("Close"),
onPressed: () {
Navigator.of(ctx).pop();
},
),
],
);
},
);
});
}
}
}`.
You only need to compare the initial text and the text of _editingController. If they are not the same, then you can update and show successMessage. It will look like this:
ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 20),
),
onPressed: () {
if (_keyChange.currentState!.validate()) {
// HERE!
if (initialText != _editingController.text) {
_keyChange.currentState!.save();
successMessage();
}
}
},

Setstate() not working to update suffixIcon inside AlertDialog ? - Flutter

I'm trying to update the suffixIcon in Form for TextFormField's InputDecoration , to check the length of the giving String by onChange function ,but no update . In add I did test on Textcontroller to give it value when the length >= 8 it's worked ,I wish I give a good idea for the issue . thanks in advance for your solution .
import 'package:flutter/material.dart';
bulidDialogChangePass() {
return DialogPass();
}
class DialogPass extends StatefulWidget {
const DialogPass({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() {
return DialogPassState();
}
}
class DialogPassState extends State<DialogPass> {
TextEditingController _fPasswordValue = TextEditingController();
TextEditingController _sPasswordValue = TextEditingController();
final _formKey = GlobalKey<FormState>();
ValueChanged<String>? onChanged;
bool _showIcons = false;
bool _startValidateFPass = false;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 25.0),
child: Wrap(
children: [
Container(
child: Row(
children: [
Text("Password :"),
TextButton(
onPressed: () {
_showDialog(context);
},
child: Wrap(
direction: Axis.vertical,
alignment: WrapAlignment.end,
children: [Text("Change")],
),
)
],
),
),
],
),
);
}
_showDialog(BuildContext context) async {
var size = MediaQuery.of(context).size;
return await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Stack(
clipBehavior: Clip.none,
children: <Widget>[
Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: EdgeInsets.all(8.0),
child: TextFormField(
controller: _fPasswordValue,
onChanged: (value) {_onChange(_fPasswordValue.text);},
decoration: InputDecoration(
labelText: "New Password",
suffixIcon: _showIcons && _startValidateFPass ? _setIcon() :null,
),
),
),
Padding(
padding: EdgeInsets.all(8.0),
child: TextFormField(
controller: _sPasswordValue,
decoration: InputDecoration(
labelText: "Confirm Password",
suffixIcon: _showIcons && _startValidateFPass ? _setIcon() :null, // I will change to check aothers soon
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Wrap(
children: [
OutlinedButton(
style: OutlinedButton.styleFrom(
fixedSize: Size(size.width / 2 - 10, 20),
padding: EdgeInsets.symmetric(horizontal: 50),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20)),
),
onPressed: () {},
child: Text("CANCEL",
style: TextStyle(
fontSize: 14,
letterSpacing: 2.2,
color: Colors.black)),
),
OutlinedButton(
style: OutlinedButton.styleFrom(
fixedSize: Size(size.width / 2 - 10, 20),
backgroundColor: Colors.blue[400],
primary: Colors.black,
padding: EdgeInsets.symmetric(horizontal: 50),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20)),
),
onPressed: () {},
child: Text("Save",
style: TextStyle(
fontSize: 14,
letterSpacing: 2.2,
color: Colors.black)),
),
],
),
),
],
),
),
],
),
);
});
}
_onChange(String value) {
setState(() {
_startValidateFPass = true;
if (value.length >= 8) {
_sPasswordValue.text = "test"; // test to check setState
_showIcons = true;
}
});
}
Icon _setIcon() {
Icon icon;
if(_showIcons){
_sPasswordValue.text = "test"; // test to check setState
print('dfsdfsd');
icon = Icon(
Icons.done_all,
color: Colors.green,
);
}else{
icon = Icon(
Icons.highlight_off,
color: Colors.red,
);
}
return icon;
}
}
Make another stateful widget and make Alertdialog as its child or use stateful builder to accomplish this as
showDialog(
context: context,
builder: (context) {
String contentText = "Your Dialog";
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Text("Title"),
content: Text(contentText),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context),
child: Text("Cancel"),
),
TextButton(
onPressed: () {
setState(() {
contentText = "Changed state";
});
},
child: Text("Change"),
),
],
);
},
);
},
);

flutter: how to autofill password in login form

I am trying when user check the checkbox it will autofill the password when user login again, i tried autofillhints in password textfield and wrap the widgets in AutofillGroup, but it is not working, when i login once and login again i am required to enter the password again.
and i am using api for fetching username and password.
here is my login code
class Login extends StatefulWidget {
Login({Key key, this.title}) : super(key: key);
final String title;
#override
_LoginState createState() => _LoginState();
}
class _LoginState extends State<Login> {
var name,password,token;
bool _passwordVisibilty;
void initState(){
_passwordVisibilty=false;
_formatDateTime();
}
String formattedDate;
String _formatDateTime() {
var now = new DateTime.now();
var formatter = new DateFormat('yyyy-MM-dd');
formattedDate = formatter.format(now);
print(formattedDate);
}
//svar localhostUrl="http://10.0.2.2:8000/login";
var herokuUrl="https://attendance-demo.herokuapp.com/login";
bool isSameuser = true;
final String username='';
final String email='';
final String designation='';
Future login() async {
try{
Dio dio=new Dio();
var data={
'username': user.username,
'password': user.password,
'date':formattedDate
};
await dio
.post(localhostUrlLogin,data: json.encode(data))
.then((onResponse) async {
//edited code here
SharedPreferences myPrefs=await SharedPreferences.getInstance();
String name=(onResponse.data['User']['username']);
String password=(onResponse.data['User']['password']);
if(name==""&&password==""){
myPrefs.setString('username', name);
myPrefs.setString('password', password);
}
else{
user.username=name;
user.password=password;
}
});}catch(e){
print("error "+e.toString());
showAlertDialog(BuildContext context) {
// Create button
Widget okButton = FlatButton(
child: Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
);
// Create AlertDialog
AlertDialog alert = AlertDialog(
title: Text("Error",style: TextStyle(fontWeight: FontWeight.bold)),
content: Text("Invalid name or password.",style: TextStyle(fontSize: 17),),
actions: [
okButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
User user = User('', '');
//--------------------------
passwordtext(){
return TextFieldContainer(
child: TextFormField(
controller: TextEditingController(text: user.password),
autofillHints: [AutofillHints.password], //here is used autfodillhints
onEditingComplete: ()=>TextInput.finishAutofillContext(), //and i used this too
decoration: InputDecoration(
border: InputBorder.none,
icon: Icon(Icons.lock,color: Colors.blue,),
suffixIcon: GestureDetector(
onTap: () {
setState(() {
_passwordVisibilty = !_passwordVisibilty;
});
},
child: Icon(
_passwordVisibilty ? Icons.visibility : Icons.visibility_off,
),
),
labelText: 'Password'),
obscureText: !_passwordVisibilty,
onChanged: (value){
user.password=value;
},
),
);
}
return Scaffold(
body: SingleChildScrollView(
child: AutofillGroup(
child: Center(
child: Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(36.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(
height: 155.0,
width: 200.0,
child: Image.asset(
"assets/image/company_logo.png",
fit: BoxFit.contain,
),
),
SizedBox(height: 50.0),
RoundedInputField(
labelText: "Username",icon: Icons.email,fontsize: textFontSize,
controller: TextEditingController(text: user.username),
onChanged: (value){
user.username=value;
},
),
SizedBox(height: 15.0),
passwordtext(),
SizedBox(
height: 0.0,
),
Align(
alignment: Alignment.centerLeft,
child:Row(children: <Widget>[
Checkbox(
checkColor: Colors.greenAccent,
activeColor: Colors.blue,
value: isSameuser,
onChanged: (bool newValue) {
if(newValue!=null){
setState(() {
isSameuser = newValue;
});
}},
),
Text("Save password?")
],)
),
SizedBox(
height: 23.0,
),
FlatButton(
color: Colors.blue[500],
textColor: Colors.white,
padding: EdgeInsets.all(15.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(buttonRoundRadius)),
child: Row(
children:<Widget>[
Expanded(child:
Text("Login",
textAlign: TextAlign.center,
style: TextStyle(fontSize: textFontSize),),)
,]),
onPressed: () {
login();
}
)
]),
),
),
),
))
);
}
}
Try this:
Use SharedPrefrences.
check if username and password is null, if it is null then store the username and password in the preferences.
if it is not null set the value of the textController to the values in the sharedPrefrences.

Flutter: form shifts when validation is false

i'm building a UI for a login form with validation.
Here is the page:
I'm overall very pleased with the look of the page and it works fine. There is just one exception: when the user inputs invalid parameters in the fields (one or more) the form shifts and the button responsable for the password visibility is not centered anymore.
I searched online and they suggest to wrap it in an Expanded widget, but it doesn't solve my problem.
Here's a image, that shows the problem:
Here is the code:
import 'package:email_validator/email_validator.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:client/providers/cloudfirestore_provider.dart';
import 'package:client/screens/forgotpass_screen.dart';
import 'package:client/screens/register_screen.dart';
class LoginScreen extends StatefulWidget {
#override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
FirebaseAuth auth = FirebaseAuth.instance;
final _formKey = GlobalKey<FormState>();
final email = TextEditingController();
final password = TextEditingController();
bool _obscureText = true;
IconData iconData = Icons.visibility;
var isLoading = false;
final focus = FocusNode();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (ctx) => SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: Colors.yellow,
width: double.infinity,
height: MediaQuery.of(context).padding.top,
),
Stack(
alignment: Alignment.center,
children: [
Container(
color: Colors.yellow,
width: double.infinity,
height: (MediaQuery.of(context).size.height -
MediaQuery.of(context).padding.top) *
0.4,
),
ClipRRect(
borderRadius: BorderRadius.circular(100.0),
child: Container(
child: Image.asset('assets/images/logo.jpg'),
height: 150,
width: 150,
),
),
],
),
Container(
height: (MediaQuery.of(context).size.height -
MediaQuery.of(context).padding.top) *
0.6,
padding: EdgeInsets.symmetric(vertical: 30, horizontal: 60),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Hi, there!",
style: TextStyle(fontSize: 20, color: Colors.grey[900]),
),
Container(
height: 210,
child: Form(
key: _formKey,
child: Column(
children: [
Expanded(
child: TextFormField(
textInputAction: TextInputAction.next,
onFieldSubmitted: (v) {
FocusScope.of(context).requestFocus(focus);
},
controller: email,
decoration: InputDecoration(labelText: "Email"),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (EmailValidator.validate(value)) {
return null;
}
return "Please enter a valid email";
},
),
),
Expanded(
child: Stack(
children: [
TextFormField(
focusNode: focus,
controller: password,
decoration:
InputDecoration(labelText: "Password"),
obscureText:
_obscureText, // create little eye to show password
validator: (value) {
if (value.length < 6) {
return 'Minimum 6 characters';
}
return null;
},
),
IconButton(
icon: Icon(iconData),
onPressed: () {
setState(
() {
_obscureText = !_obscureText;
if (iconData == Icons.visibility) {
iconData = Icons.visibility_off;
} else {
iconData = Icons.visibility;
}
},
);
})
],
alignment: Alignment.bottomRight,
),
),
SizedBox(
height: 20,
),
Container(
width: 200,
height: 50,
child: FlatButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50.0),
),
color: Colors.deepOrange,
textColor: Colors.white,
onPressed: () async {
Scaffold.of(ctx).hideCurrentSnackBar();
setState(() {
isLoading = true;
});
if (_formKey.currentState.validate()) {
login(email, password) async {
try {
await FirebaseAuth.instance.signOut();
await FirebaseAuth.instance
.signInWithEmailAndPassword(
email: email.text,
password: password.text);
if (FirebaseAuth.instance.currentUser
.emailVerified ==
false)
Scaffold.of(ctx).showSnackBar(
SnackBar(
content: Text(
"Please verify your email."),
),
);
else
Scaffold.of(ctx).showSnackBar(
SnackBar(
content:
Text("Logging you in ..."),
),
);
setState(() {
isLoading = false;
});
} catch (e) {
print(e.toString());
if (e.toString() ==
"[firebase_auth/wrong-password] The password is invalid or the user does not have a password.") {
Scaffold.of(ctx)
.hideCurrentSnackBar();
Scaffold.of(ctx).showSnackBar(
SnackBar(
content: Text(
"The password is incorrect."),
),
);
} else if (e.toString() ==
"[firebase_auth/user-not-found] There is no user record corresponding to this identifier. The user may have been deleted.") {
Scaffold.of(ctx)
.hideCurrentSnackBar();
Scaffold.of(ctx).showSnackBar(
SnackBar(
content: Text(
"There is no email adress connected to this account."),
),
);
} else if (e.toString() ==
"[firebase_auth/too-many-requests] We have blocked all requests from this device due to unusual activity. Try again later.") {
Scaffold.of(ctx)
.hideCurrentSnackBar();
Scaffold.of(ctx).showSnackBar(
SnackBar(
content: Text(
"We have blocked all requests from this device due to unusual activity. Try again later."),
),
);
} else {
Scaffold.of(ctx)
.hideCurrentSnackBar();
Scaffold.of(ctx).showSnackBar(
SnackBar(
content: Text(
"Pls check your internet connection and try again."),
),
);
}
setState(() {
isLoading = false;
});
}
}
await login(email, password).whenComplete(
() => CloudFirestoreProvider().addUser(
email.text,
password.text,
));
} else
setState(() {
isLoading = false;
});
},
child: (isLoading)
? CircularProgressIndicator(
backgroundColor: Colors.white,
)
: Text('Submit',
style: TextStyle(fontSize: 22)),
),
),
],
),
),
),
Expanded(
child: Container(
alignment: Alignment.bottomCenter,
child: Row(
children: [
FlatButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ForgotPassScreen(),
),
);
},
child: Text(
"Forgot Password ?",
style: TextStyle(color: Colors.deepOrange),
),
),
Expanded(child: SizedBox()),
FlatButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RegisterScreen(),
),
);
},
child: Text(
"Register",
style: TextStyle(color: Colors.deepOrange),
),
),
],
),
),
)
],
),
),
],
),
),
),
);
}
}
EDIT:
I tried swapping the Stack method for the suffixIcon suggested. And it works except for the position of the Icon that is off-centred.
Here's the updated code:
Expanded(
child: TextFormField(
focusNode: focus,
controller: password,
decoration: InputDecoration(
labelText: "Password",
suffixIcon: IconButton(
color: Colors.grey[900],
icon: Icon(iconData),
onPressed: () {
setState(
() {
_obscureText = !_obscureText;
if (iconData ==
Icons.visibility) {
iconData = Icons.visibility_off;
} else {
iconData = Icons.visibility;
}
},
);
})),
obscureText:
_obscureText, // create little eye to show password
validator: (value) {
if (value.length < 6) {
return 'Minimum 6 characters';
}
return null;
},
),
),
Put your icon in the sufficient property of the input Decoration on your password text field.
inputDecoration(suffixIcon: IconButton())