Flutter - bottomModalSheet can't validate a textfield widget - flutter

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

Related

Flutter: Submit TextFormField on Enter

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

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 textfield onChanged don't work when textfield bring value from controller

I'm working on flutter project and came across a problem. the textfield supposed to take a value from controller which work perfectly but onChanged function wont work. However it is not working.i must retype value to show results .
how can i get result from onchanged when my textfield brings the value ?
code:
// search function that call api
class _SearchPageTState extends State<SearchPageT> {
GlobalState _store = GlobalState.instance;
List<dynamic> searchResults = [];
searchT(value) async {
SearchServicet.searchApiT(value).then((responseBody) {
List<dynamic> data = jsonDecode(responseBody);
setState(() {
data.forEach((value) {
searchResults.add(value);
});
});
});
}
#override
///////
//code textfield search onchanged
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
centerTitle: true,
),
body: ListView(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(10.0),
child: TextField(
autofocus: true,
controller: TextEditingController()
..text = '${_store.get('num')}',
onChanged: (value) {
searchResults.clear();
searchCodeighniterT(value);
},
textAlign: TextAlign.center,
decoration: InputDecoration(
contentPadding: EdgeInsets.only(left: 25.0),
labelText: 'number',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
),
suffixIcon: IconButton(
icon: Icon(Icons.search),
onPressed: null,
),
),
),
),
SizedBox(
height: 10.0,
),
ListView.builder(
physics: ScrollPhysics(),
shrinkWrap: true,
itemCount: searchResults.length,
itemBuilder: (BuildContext context, int index) {
return buildResultCard(context, searchResults[index]);
},
),
],
),
),
);
}
//here where shows the result
Widget buildResultCard(BuildContext context, data) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
new Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/bgwlgo.png"), fit: BoxFit.cover),
),
margin: EdgeInsets.all(5.0),
child: Column(
children: <Widget>[
new Card(
child: ListTile(
title: Text(
"DATE:",
),
subtitle: Text(
data['DATETIME'].toString(),
),
),
),
new Card(
child: ListTile(
title: Text(
"TRAITEMENT:",
),
subtitle: Text(
data['TRAITEMENT_DETAIL'].toString()
),
),
),
new Padding(
padding: EdgeInsets.all(9.0),
),
],
),
),
Divider(
color: Colors.black,
)
],
),
);
}
As discussed and understood in the comments, here are a couple of possible solutions for the issue.
If you are using a stateful Widget and need a controller for your TextFormField, you can use it to set the text on the TextFormField. Otherwise, you can just use the initialValue property:
class TextFieldIssue64061076 extends StatelessWidget {
final TextEditingController _textEditingController = TextEditingController();
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children:[
Text('Page 1'),
TextFormField(
controller: _textEditingController,
),
FlatButton(
child: Text('Submit'),
onPressed: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) {
return SecondPage64061076(text: _textEditingController.text);
}
)),
),
],
);
}
}
class SecondPage64061076 extends StatefulWidget {
final String text;
SecondPage64061076({
this.text
});
#override
_SecondPage64061076State createState() => _SecondPage64061076State();
}
class _SecondPage64061076State extends State<SecondPage64061076> {
final TextEditingController _textEditingController = TextEditingController();
#override
void initState() {
// You can either use a text controller to set the text on the text field
// or the initialValue below
if(widget.text != null && widget.text != ''){
_textEditingController.text = widget.text;
methodToCallOnChanged(widget.text);
}else{
_textEditingController.text = '';
}
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Page 2'),
TextFormField(
controller: _textEditingController,
onChanged: (value) => methodToCallOnChanged(value),
// As mentioned you could just use the initial value, it you don't need
// a Stateful Widget
// initialValue: widget.text ?? '',
),
]
),
);
}
void methodToCallOnChanged(data){
print('I was called onChange or when the view opened and there was data');
}
}
Widget build(BuildContext context) {
bool isRecording = false;
bool isShowSendButton = false;
/// make sure textcontroller and any variabloes or booleans are not defined
///in the build method
}
You can't be using the constructor of the TextEditingController and assign it to the controller.
The controller of the TextField only takes the variable assigned to the TextEditingController. Check the following code.
controller:theNameOfYourController
Expanded(
flex: 8,
child: Padding(
padding: const EdgeInsets.only(left: 8.0, right: 10),
child: TextFormField(
onChanged: (value){
messageFieldController.text=value;
},
//controller: messageFieldController,
minLines: 1,
maxLines: 2,
textCapitalization: TextCapitalization.sentences,
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
prefixIcon: IconButton(
onPressed: () {},
icon: const Icon(Icons.add, color: Colors.blue),
),
suffixIcon: processing == true
? const CircularProgressIndicator()
: IconButton(
onPressed: () {
try {
/*for (int i = 0;
i < widget.selectedMembers.length;
i++) {
sendMessage(
widget.selectedMembers[i].number);
}*/
for (int i = 0;
i < widget.selectedGroups.length;
i++) {
if (widget.selectedGroups.isNotEmpty) {
sendMessageToGroup(
widget.selectedGroups[i].id,
widget.selectedGroups[i].groupName);
}
}
} catch (err) {
Fluttertoast.showToast(
msg: 'Unable to send Message');
setState(() {
processing = false;
});
}
},
icon:
const Icon(Icons.send, color: Colors.blue),
),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(50),
),
),
),
),
),
)

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())