Validating 2 things for TextFormField - flutter

I have a textformfield which validates min length to be at least 3 characters. Later on, I'll validate with backend that the username is unique.
But currently, I'm having trouble showing 2 error text for 2 errors. Namely, one for min length ( "Please enter at least 3 characters for your username") and one for non-unique username ('Your username is popular, please try another one.')
Questions:
How to make the suffix icon appear only when at least 1 character is typed? instead of by default, I know it's linked to hasMinLengthUnique = false.
How to show different error text for respective errors?
And later on, how to put a ternary operator on the navigation to another page based on value hasMinLengthUnique = true.
Bcos right now, whether hasMinLengthUnique = true or false, the navigation doesn't happen.
Form(
key: _formKey,
autovalidateMode: AutovalidateMode.always,
child: TextFormField(
controller: _controller,
validator: (value) {
return (0 < value.length && value.length < 3)
? "Please enter at least 3 characters for your username"
: null;
},
onChanged: (value) {
setState(() {
value.length > 2
// && username must be unique
? hasMinLengthUnique = true
: hasMinLengthUnique = false;
});
},
style: TextStyle(color: Colors.white),
autofocus: true,
maxLength: 15,
decoration: InputDecoration(
suffix: hasMinLengthUnique
? IconButton(
icon:
Icon(Icons.check_circle, color: Colors.green))
: IconButton(
icon: Icon(Icons.cancel, color: Colors.red)),
errorBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Colors.blue, width: 1.0),
),
errorStyle: TextStyle(color: Color(0xff4aa3f8)),
focusedErrorBorder: UnderlineInputBorder(
borderSide: BorderSide.none,
),
border: OutlineInputBorder(),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(15)),
// borderSide: BorderSide(width: 1, color: Colors.white),
),
),
),
),
),
),
SizedBox(
height: MediaQuery.of(context).size.height * 0.18,
),
Padding(
padding: EdgeInsets.only(
right: MediaQuery.of(context).size.height * 0.015,
),
child: DelayedDisplay(
delay: Duration(seconds: 3),
child: Align(
alignment: Alignment.centerRight,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
onPrimary: Colors.white,
primary: Color(0xff3a327f)),
child: Icon(Icons.chevron_right, size: 27),
onPressed: () async {
// if (formKey.currentState.validate()) {
Navigator.push(context, _createRoute());
// }
})),
))
]),
));

Here is the code
It checks whether a username is greater than 2 or not
Then it will check whether your username is popular or not (try typing admin)
It will update your hasMinLengthUnique accordingly that will update your UI of IconButton()
It will update your ElevatedButton() as well. It becomes disable if hasMinLengthUnique is false and become enable if hasMinLengthUnique is true.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool hasMinLengthUnique = false;
var _controller = TextEditingController();
GlobalKey<FormState> _formKey = GlobalKey();
var defUsername = ["admin"];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Container(
color: Colors.lightBlue[900],
child: Column(
children: [
Form(
key: _formKey,
autovalidateMode: AutovalidateMode.always,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: _controller,
validator: (value) {
if (value.length < 3) {
return "Please enter at least 3 characters for your username";
}
if (value.contains(defUsername[0])) {
return "Username is too popular";
}
return null;
},
onChanged: (value) {
setState(() {
if (value.contains(defUsername[0]) ||
value.length <= 2) {
hasMinLengthUnique = false;
} else {
hasMinLengthUnique = true;
}
});
},
style: TextStyle(color: Colors.white),
autofocus: true,
maxLength: 15,
decoration: InputDecoration(
contentPadding:
EdgeInsets.symmetric(vertical: 0, horizontal: 10),
labelText: "Username",
suffix: hasMinLengthUnique
? Icon(Icons.check_circle, color: Colors.green)
: _controller.text.isEmpty
? Icon(null, color: Colors.red)
: Icon(Icons.cancel, color: Colors.red),
border: OutlineInputBorder(),
),
),
),
),
SizedBox(
height: MediaQuery.of(context).size.height * 0.18,
),
Padding(
padding: EdgeInsets.only(
right: MediaQuery.of(context).size.height * 0.015,
),
child: Container(
child: Align(
alignment: Alignment.centerRight,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
onPrimary: Colors.white, primary: Color(0xff3a327f)),
child: Icon(Icons.chevron_right, size: 27),
onPressed: hasMinLengthUnique
? () {
print("go to next screen");
}
: null,
),
),
),
)
],
),
),
),
);
}
}
Thank you!

Have you tried if else statement in onChanged Value
var isValidate;
onChanged(){
if(1st condition){
setState(){}
}
else if(2nd condition){
setState(){}
}
}

Related

setState not updating the value in the screen using Flutter desktop

So I'm trying to update the value of the drop down list using the setState, but when I select a value from the list, the value on the screen won't be able to change
import 'package:demo_app_admin/Custom/custom_Font.dart';
import 'package:flutter/material.dart';
import '/Custom/Custom_Raised_Bottun.dart';
import '/Custom/Header.dart';
import '/Custom/inputField.dart';
class LoginView extends StatefulWidget {
#override
_LoginViewState createState() => _LoginViewState();
}
class _LoginViewState extends State<LoginView> {
#override
Widget build(BuildContext context) {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final isKeyboard = MediaQuery.of(context).viewInsets.bottom != 0;
String _dropdownvalue = "Global admin";
List<String> _items = <String> ["Global admin", "Institution admin"];
return Scaffold(
body: Container(
width: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(begin: Alignment.topCenter, colors: [
Colors.teal[700]!,
Colors.teal[200]!,
Colors.teal[400]!
]),
),
child: Column(
children: <Widget>[
const SizedBox(
height: 80,
),
if (!isKeyboard)
Header(
padding: const EdgeInsets.all(20),
crossAxisAlignment: CrossAxisAlignment.start,
head: "Welcome",
headTextStyle:
const TextStyle(color: Colors.white, fontSize: 40),
sizedBoxHeight: 10,
subtitle: "Sign In to Your Admin Account",
subtitleTextStyle:
const TextStyle(color: Colors.white, fontSize: 18),
),
Expanded(
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(60),
topRight: Radius.circular(60),
)),
child: Padding(
padding: const EdgeInsets.all(30),
child: SingleChildScrollView(
reverse: true,
padding: EdgeInsets.all(32),
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
const SizedBox(
height: 40,
),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10)),
child: Column(
children: <Widget>[
InputField(
labelText: 'Email',
padding: EdgeInsets.all(10),
borderSide: BorderSide(color: Colors.grey),
hintText: "Enter your email",
color: Colors.grey,
inputBorder: InputBorder.none,
obscureText: false,
enableSuggestion: true,
autoCorrect: true,
onSaved: (value) {},
// (value){controller.email = value!;},
validator: (value) {
if (value == null) {
print("ERROR");
}
},
),
InputField(
labelText: 'Password',
padding: EdgeInsets.all(10),
borderSide: BorderSide(color: Colors.grey),
hintText: "Enter your password",
color: Colors.grey,
inputBorder: InputBorder.none,
obscureText: true,
enableSuggestion: false,
autoCorrect: false,
onSaved: (value) {},
// (value){ controller.password = value!;},
validator: (value) {
if (value == null) {
print("ERROR");
}
},
),
],
),
),
const SizedBox(
height: 40,
),
here is the issue
DropdownButton<String>(
value: _dropdownvalue,
icon: Icon(Icons.keyboard_arrow_down),
items: _items.map((String _items) {
return DropdownMenuItem<String>(
value: _items,
child: CustomFont(text: _items),
);
}).toList(),
onChanged: (value) {
setState(() => _dropdownvalue = value!);
}),
const SizedBox(
height: 40,
),
CustomRaisedButton(
width: 200,
height: 50.0,
margin: const EdgeInsets.symmetric(horizontal: 50),
color: Colors.teal.shade500,
borderRadius: BorderRadius.circular(10),
text: "LOGIN",
textColor: Colors.white,
fontSize: 17,
fontWeight: FontWeight.bold,
function: () {}
// {
// _formKey.currentState?.save();
// if(_formKey.currentState?.validate() !=null){
// controller.signInWithEmailAndPassword();
// }
// },
),
SizedBox(
height: 10,
),
],
),
),
),
),
))
],
),
),
);
}
}
The issue is here, you are declaring variables inside build. Therefore, variables get reset to default value on every build. Means setState.
Declare variables outside the build method.
class _LoginViewState extends State<LoginView> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
String _dropdownvalue = "Global admin";
List<String> _items = <String>["Global admin", "Institution admin"];
#override
Widget build(BuildContext context) {
final isKeyboard = MediaQuery.of(context).viewInsets.bottom != 0;
return Scaffold(
More about StatefulWidget.

How to create contact list using Flutter?

I was create an dynamic contact list.
When I enter the number in add contact textfield. Automatically another text field will open. When I erase the text field the below the empty will delete automatically.
I tried several ways but id didn't work.
In my code I used text field on changed method when I enter the number it open the new contact field every number I added, I want only one contact field.
import 'package:flutter/material.dart';
class Contactadd extends StatefulWidget {
const Contactadd({Key? key}) : super(key: key);
#override
_ContactaddState createState() => _ContactaddState();
}
class _ContactaddState extends State<Contactadd> {
String dropdownValue = "Mobile";
List<Widget> cardList = [];
Widget card1() {
return Container(
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: const Color(0xFFE8DBDB),
borderRadius: BorderRadius.circular(20)),
child: Row(
children: [
const SizedBox(
width: 10,
),
dropdown(),
Container(
height: 40,
width: 200,
margin: const EdgeInsets.all(5),
child: TextField(
keyboardType: TextInputType.number,
// controller: dropdownController,
decoration: const InputDecoration(
contentPadding: EdgeInsets.only(left: 10),
border: InputBorder.none),
onChanged: (_) {
String dataa = _.toString();
if (dataa.length == 1) {
print(_ + "=================");
cardList.add(_card());
setState(() {});
} else if (dataa.length < 1) {
cardList.removeLast();
}
},
// addCardWidget,
),
),
],
),
);
}
Widget _card() {
return Container(
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: const Color(0xFFDE6868),
borderRadius: BorderRadius.circular(20)),
child: Row(
children: [
const SizedBox(
width: 10,
),
dropdown(),
Container(
height: 40,
width: 200,
margin: const EdgeInsets.all(5),
child: TextFormField(
keyboardType: TextInputType.number,
decoration: const InputDecoration(
contentPadding: EdgeInsets.only(left: 10),
border: InputBorder.none),
onChanged: (_) {
String dataa = _.toString();
if (dataa.isEmpty) {
print("true");
} else {
print("false");
}
if (dataa.length == 1 || dataa.length == 0) {
print(_ + "=================");
cardList.add(_card());
setState(() {});
} else {
cardList.removeLast();
}
})),
],
),
);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text("Contact List"),
),
body: SingleChildScrollView(
child: Column(
children: [
card1(),
Container(
height: 430,
width: MediaQuery.of(context).size.width,
child: ListView.builder(
itemCount: cardList.length,
itemBuilder: (context, index) {
return _card();
}),
),
],
),
),
),
);
}
}
The complete code this will help you to create view like your requirment
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Contactadd(),
);
}
}
class Contactadd extends StatefulWidget {
#override
_ContactaddState createState() => _ContactaddState();
}
class _ContactaddState extends State<Contactadd> {
Map<int, dynamic> contactMap = new Map();
#override
void initState() {
contactMap.addAll(
{0: 1},
);
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text("Contact List"),
),
body: Column(
children: [
for (var i = 0; i < contactMap.length; i++) ...[
Container(
margin: EdgeInsets.all(10),
child: TextField(
onChanged: (value) {
if (value.toString().isEmpty) {
contactMap.removeWhere((key, value) => key == i + 1);
} else {
contactMap.addAll(
{i + 1: 1},
);
}
setState(() {});
},
keyboardType: TextInputType.number,
autocorrect: true,
decoration: InputDecoration(
hintStyle: TextStyle(color: Colors.grey),
filled: true,
contentPadding: EdgeInsets.only(bottom: 0.0, left: 8.0),
fillColor: Colors.white70,
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
borderSide:
BorderSide(color: Colors.lightBlueAccent, width: 1),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
borderSide: BorderSide(color: Colors.lightBlueAccent),
),
),
),
),
],
],
),
),
);
}
}

Separate suffixIcon for password visibility

How to separate suffixIcon for password visibility? If I press icon on field password it also does the same thing on field confirm password.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool isObsecure = false;
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: const Text('Text Form Field'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
obscureText: isObsecure,
decoration: InputDecoration(
hintText: 'Password',
suffixIcon: IconButton(
onPressed: () {
setState(() {
isObsecure = !isObsecure;
});
},
icon: Icon(
isObsecure ? Icons.visibility_off : Icons.visibility,
),
),
),
),
TextFormField(
obscureText: isObsecure,
decoration: InputDecoration(
hintText: 'Confirm Password',
suffixIcon: IconButton(
onPressed: () {
setState(() {
isObsecure = !isObsecure;
});
},
icon: Icon(
isObsecure ? Icons.visibility_off : Icons.visibility,
),
),
),
),
],
),
),
),
);
}
}
you have to define 2 flags. 1 for password visibility and 1 for confirm:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool isObsecure = false;
bool isConfirmObsecure = false;
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: const Text('Text Form Field'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextFormField(
obscureText: isObsecure,
decoration: InputDecoration(
hintText: 'Password',
suffixIcon: IconButton(
onPressed: () {
setState(() {
isObsecure = !isObsecure;
});
},
icon: Icon(
isObsecure ? Icons.visibility_off : Icons.visibility,
),
),
),
),
TextFormField(
obscureText: isConfirmObsecure,
decoration: InputDecoration(
hintText: 'Confirm Password',
suffixIcon: IconButton(
onPressed: () {
setState(() {
isConfirmObsecure = !isConfirmObsecure;
});
},
icon: Icon(
isConfirmObsecure
? Icons.visibility_off
: Icons.visibility,
),
),
),
),
],
),
),
),
);
}
}
///You can use the provider also to hide and visible the password icon. Hope this will work for you.
////This is my forgot password screen
import 'package:flutter/material.dart';
import 'package:form_field_validator/form_field_validator.dart';
import 'package:provider/provider.dart';
import 'package:traveling/Provider/common/ObscureTextState.dart';
import 'package:traveling/helpers/AppColors.dart';
import 'package:traveling/helpers/AppStrings.dart';
import 'package:traveling/screens/Employee/home/BottomNavBar.dart';
class ForgotPasswordA extends StatefulWidget {
#override
_ForgotPasswordAState createState() => _ForgotPasswordAState();
}
class _ForgotPasswordAState extends State<ForgotPasswordA> {
/// These variables are used for getting screen height and width
double? _height;
double? _width;
///controllers are used to get text which user enter in TextFiled
TextEditingController confirmPasswordController = TextEditingController();
TextEditingController passwordController = TextEditingController();
GlobalKey<FormState> _formkey = GlobalKey();
String? confirmPassword, password;
#override
void initState() {
// TODO: implement initState
super.initState();
}
///this widget is the main widget and it is used for building a UI in the app
#override
Widget build(BuildContext context) {
_height = MediaQuery.of(context).size.height;
_width = MediaQuery.of(context).size.width;
return Material(
child: Scaffold(
backgroundColor: AppColors.white,
body:Consumer<LoginProvider>(
builder:(context, obs, child) {
///Consumer is used to get value from the loginProvider class
return GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
},
child: Container(
height: _height,
width: _width,
margin: EdgeInsets.only(top: 70),
padding: EdgeInsets.only(bottom: 5),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
SizedBox(height: _height! / 22),
appText(),
SizedBox(height: _height! / 18),
Container(
width: _width! * .85,
child: Form(
///define global key for validation
key: _formkey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
children: <Widget>[
SizedBox(height: _height! / 50),
TextFormField(
controller: passwordController,
validator: (value) {
if (value!.isEmpty) {
return "Please Enter Password!";
} else if (value.length < 6) {
return "Please Enter more than 6 digit .";
} else {
return null;
}
},
obscureText:
Provider.of<LoginProvider>(context, listen: false)
.isTrue,
keyboardType: TextInputType.emailAddress,
autofillHints: [AutofillHints.email],
cursorColor: AppColors.black,
style: TextStyle(
color: Color(0xff919AAA),
),
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide(
color: AppColors.textFieldColor,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide(
color: AppColors.textFieldColor,
),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide(
color: AppColors.red,
),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide(
color: AppColors.red,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide(
color: AppColors.textFieldColor,
),
),
floatingLabelBehavior: FloatingLabelBehavior.never,
suffixIcon: IconButton(
onPressed: () {
Provider.of<LoginProvider>(context, listen: false)
.toggleObs();
},
icon: Provider.of<LoginProvider>(context,
listen: false)
.switchObsIcon,
),
labelText: 'Password',
errorStyle: TextStyle(color: AppColors.red),
hintStyle: TextStyle(
color: Color(0xff919AAA),
),
),
),
//passwordTextFormField(),
SizedBox(height: _height! / 30.0),
///password visibility handle by provider state management
TextFormField(
controller: confirmPasswordController,
validator: (value) {
if (value!.isEmpty) {
return "Please Enter Password!";
} else if (value.length < 6) {
return "Please Enter more than 6 digit .";
} else {
return null;
}
},
obscureText:
Provider.of<LoginProvider>(context, listen: false)
.isConfirmPassword,
keyboardType: TextInputType.emailAddress,
autofillHints: [AutofillHints.email],
cursorColor: AppColors.black,
style: TextStyle(
color: Color(0xff919AAA),
),
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide(
color: AppColors.textFieldColor,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide(
color: AppColors.textFieldColor,
),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide(
color: AppColors.red,
),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide(
color: AppColors.red,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
borderSide: BorderSide(
color: AppColors.textFieldColor,
),
),
floatingLabelBehavior: FloatingLabelBehavior.never,
suffixIcon: IconButton(
onPressed: () {
Provider.of<LoginProvider>(context, listen: false)
.toggleObsData();
},
icon: Provider.of<LoginProvider>(context,
listen: false)
.switchObsIcons,
),
labelText: 'Confirm Password',
errorStyle: TextStyle(color: AppColors.red),
hintStyle: TextStyle(
color: Color(0xff919AAA),
),
),
),
],
),
),
),
SizedBox(height: _height! / 28),
SizedBox(height: _height! / 22),
submitButton(),
SizedBox(height: _height! / 35),
],
),
),
),
);}
),
),
);
}
///app-text Widget is used for app logo and icon in top side
Widget appText() {
return Container(
width: _width! * 0.90,
child: Column(
children: <Widget>[
Image.asset(
"assets/traveling-text-icon.png",
height: 60,
),
SizedBox(
height: 5,
),
],
),
);
}
///for validation we used a submit button
Widget submitButton() {
return Card(
elevation: 20.0,
shadowColor: Colors.blue[100],
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0))),
child: Container(
height: 60,
width: _width! * .85,
decoration: BoxDecoration(
// boxShadow: [BoxShadow(color: AppColors.grey, blurRadius: 20.0)],
borderRadius: BorderRadius.circular(20.0),
gradient: LinearGradient(colors: [
AppColors.blue,
Colors.blue.shade900,
])),
child: MaterialButton(
onPressed: () {
if(passwordController.text==confirmPasswordController.text){
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => EmployeeBottomNavBar()));}
else{
print("password did not match");
}
},
child: Text(
AppStrings.submit,
style: TextStyle(
color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold),
),
),
),
);
}
}
////provider class
import 'package:flutter/material.dart';
import 'package:traveling/helpers/AppColors.dart';
class LoginProvider extends ChangeNotifier {
bool _isTrue = true;
bool _isConfirmPassword = true;
bool get isTrue => _isTrue;
bool get isConfirmPassword => _isConfirmPassword;
get switchObsIcons {
return _isConfirmPassword ? Icon(Icons.visibility_off,color: AppColors.textFieldTextColor,) : Icon(Icons.visibility,color: AppColors.textFieldTextColor,);
}
get switchObsIcon {
return _isTrue ? Icon(Icons.visibility_off,color: AppColors.textFieldTextColor,) : Icon(Icons.visibility,color: AppColors.textFieldTextColor,);
}
void toggleObsData() {
_isConfirmPassword = !_isConfirmPassword;
notifyListeners();
}
void toggleObs() {
_isTrue = !_isTrue;
notifyListeners();
}
}

Flutter fixed button position

I'm developing an application on flutter and I'm having a problem with the position of the ElevatedButton. When the Validator returns the error message below the TextFormField, the widget expands downward and the position of the add button changes. I would like to keep the add button pinned to the same position as the beginning of the app
button in normal position
Button out of its original position after returning the validator
My code:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Lista de Compras"),
backgroundColor: Colors.green,
centerTitle: true,
),
body: Column(
children: <Widget>[
Container(
padding: EdgeInsets.fromLTRB(15, 10, 5, 10),
child: Form(
key: _formKey,
child: Row(
children: <Widget>[
Theme(
data: ThemeData(
primaryColor: Colors.green,
hintColor: Colors.green),
child: Expanded(
child: TextFormField(
controller: _controlador,
validator: (value) {
if (value.isEmpty) {
return "Insira um item";
}
return null;
},
decoration: InputDecoration(
enabledBorder: OutlineInputBorder(
borderSide:
BorderSide(color: Colors.green),
borderRadius:
BorderRadius.circular(100)),
border: OutlineInputBorder(
borderRadius:
BorderRadius.circular(100)),
labelText: "Novo item",
hintText: "Insira um item",
hintStyle: TextStyle(color: Colors.grey),
labelStyle:
TextStyle(color: Colors.green),
suffixIcon: IconButton(
onPressed: () => _controlador.clear(),
icon: Icon(Icons.clear,
color: Colors.grey),
))))),
Padding(padding: EdgeInsets.only(right: 6)),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.green,
shape: CircleBorder(),
padding: EdgeInsets.all(12)),
child: Icon(Icons.add, color: Colors.white),
onPressed: () {
if (_formKey.currentState.validate()) {
_addCompras();
}
}),
],
),
)),
This is an expected behaviour, when TextFormField show an errorText this will append a text below the TextFormField adding extra height for about 22. Check working example below :
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Column(
children: [
MeasureSize(
child: FormWidget(),
onChange: (size) {
print(size);
},
),
MeasureSize(
child: FormWidget(hideError: false),
onChange: (size) {
print(size);
},
),
FormWidget(
hideError: false,
addPaddingToTrailingButton: true,
),
],
),
),
);
}
}
class FormWidget extends StatelessWidget {
final bool hideError;
final bool addPaddingToTrailingButton;
FormWidget({
this.hideError = true,
this.addPaddingToTrailingButton = false,
});
#override
Widget build(BuildContext context) {
Widget trailingButton = ElevatedButton(
style: ElevatedButton.styleFrom(
shape: CircleBorder(),
padding: EdgeInsets.all(12),
),
child: Icon(Icons.add),
onPressed: () {},
);
if (addPaddingToTrailingButton) {
trailingButton = Padding(
padding: const EdgeInsets.only(bottom: 22),
child: trailingButton,
);
}
return Container(
color: Colors.grey[300],
margin: const EdgeInsets.symmetric(vertical: 16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(width: 8),
Expanded(
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(100),
),
labelText: "Label text field",
hintText: "Hint text field",
errorText: hideError ? null : 'Error text shown',
suffixIcon: IconButton(
onPressed: () {},
icon: Icon(Icons.clear),
),
),
),
),
SizedBox(width: 8),
trailingButton,
],
),
);
}
}
typedef void OnWidgetSizeChange(Size size);
class MeasureSizeRenderObject extends RenderProxyBox {
Size oldSize;
final OnWidgetSizeChange onChange;
MeasureSizeRenderObject(this.onChange);
#override
void performLayout() {
super.performLayout();
Size newSize = child.size;
if (oldSize == newSize) return;
oldSize = newSize;
WidgetsBinding.instance.addPostFrameCallback((_) {
onChange(newSize);
});
}
}
class MeasureSize extends SingleChildRenderObjectWidget {
final OnWidgetSizeChange onChange;
const MeasureSize({
Key key,
#required this.onChange,
#required Widget child,
}) : super(key: key, child: child);
#override
RenderObject createRenderObject(BuildContext context) {
return MeasureSizeRenderObject(onChange);
}
}
I have adding a background so it will show the height difference :
TextField without errorText shown.
TextField with errorText shown.
TextField with errorText shown, with additional padding on icon button.
Check dartpad here
the answer by Allan above is very great.
But if you are facing issues learning that do it this way:
Add Elevated button to Column and add SizedBox with height 22 as its children. Here the height is calculated as (Height of TextFormField with error - of TextFormField without error) using flutter inspector.
Column(
children: [
ElevatedButton(...),
SizedBox(
height: 22,
),
],
),
Now add helperText: ' ', in TextFormField InputDecoration:
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return "Insira um item";
}
return null;
},
decoration: InputDecoration(
helperText: ' ',
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.green),
borderRadius: BorderRadius.circular(100)),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(100)),
labelText: "Novo item",
hintText: "Insira um item",
hintStyle: TextStyle(color: Colors.grey),
labelStyle: TextStyle(color: Colors.green),
),
),
Here you can see the dartpad for full code.
This will get you this result:

raised button does not shows answer until I select a drop down item even after assigning a default value

I was making a simple S.I Calculator which has a field for principal amount intrest, and time along with that i have a dropdown to select the currency.
Problem is when I press 'calculate' RaisedButton(looking at the image might help) nothing appears on screen (Initially I thought there is problem with buttton but later ) I found out that after pressing RaisedButton if I selected any item from dropdown then answer appears why is this happening, I have set an inital value as _selected = 'Rupee' am I doing something wrong?.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Simple Intrest Calculator',
home: MyHomePage(),
theme: ThemeData(
brightness: Brightness.dark,
primaryColor: Colors.indigo,
accentColor: Colors.indigoAccent,
),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _currencies = ['Rupee', 'Dollar', 'Pound'];
final _minPadding = 5.0;
var _selected = 'Rupee';
TextEditingController principalCntr = TextEditingController();
TextEditingController roiCntr = TextEditingController();
TextEditingController termCntr = TextEditingController();
String _amount = '0';
#override
Widget build(BuildContext context) {
TextStyle styles = Theme.of(context).textTheme.headline6;
return Scaffold(
//resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: Text('S.I Calculator'),
centerTitle: true,
),
body: Container(
margin: EdgeInsets.symmetric(vertical: 0, horizontal: _minPadding * 2),
child: ListView(
children: [
getImg(),
Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: TextField(
controller: principalCntr,
keyboardType: TextInputType.number,
style: styles,
decoration: InputDecoration(
labelText: 'Principal',
hintText: 'Enter principal amount',
labelStyle: styles,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
),
),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: TextField(
controller: roiCntr,
keyboardType: TextInputType.number,
style: styles,
decoration: InputDecoration(
labelText: 'Intrest',
hintText: 'Enter intrest rate',
labelStyle: styles,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
),
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: termCntr,
keyboardType: TextInputType.number,
style: styles,
decoration: InputDecoration(
labelText: 'Time',
hintText: 'Time in years',
labelStyle: styles,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
),
),
),
),
SizedBox(width: 50,),
Expanded(
child: DropdownButton(
value: _selected,
onChanged: (newVal) {
setState(() {
_selected = newVal;
});
},
items: _currencies.map((selectedVal) {
return DropdownMenuItem(
value: selectedVal,
child: Text(selectedVal),
);
}).toList(),
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: RaisedButton(
color: Theme.of(context).accentColor,
textColor: Theme.of(context).primaryColor,
child: Text('Calculate'),
onPressed: (){
_amount = _calculateReturns();
},
),
),
SizedBox(width: 20),
Expanded(
child: RaisedButton(
color: Theme.of(context).primaryColorDark,
textColor: Theme.of(context).primaryColorLight,
child: Text('Clear', style: TextStyle(fontSize: 20,),),
onPressed: (){
debugPrint('pressed');
},
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(_amount, style: styles,),
),
],
),
),
);
}
String _calculateReturns(){
double principal = double.parse(principalCntr.text);
double roi = double.parse(roiCntr.text);
double year = double.parse(termCntr.text);
double sI = principal + (principal*roi*year)/100;
String res = 'After $year years, you get $sI';
return res;
}
Widget getImg() {
AssetImage img = AssetImage('images/money.jpg');
Image image = Image(image: img, width: 125, height: 125);
return Container(
child: image,
margin: EdgeInsets.all(_minPadding * 10),
);
}
}
This is after i click Raised Button
This is after i select an item from dropdown but do not click on the button again:
Code walkthrough:
made a list of _currencies.
added textinput fields dropdown button and two raised buttons.
when calculate button is pressed _calculateReturns() is called which returns a value and stores it in _amount
Output is just a Text widget.
you forgot to update the screen when pressing on the button, remember every time you need to change the content of the page or update it you have to call the method :
setState(() {
// code here for values to be updated
});
so in your onPressed function of Raised button you have to add it like that :
onPressed: (){
_amount = _calculateReturns();
setState(() { });
}