Validator for a Custom InputTextField - flutter

I have created my own custom textinputfield, everything do has come up well but the thing is how to give that a validator upon the button click?
This is my custom inputfield,
class CustomInputField extends StatelessWidget {
bool _validate = false;
Icon fieldIcon;
String hintText;
TextInputType textType;
CustomInputField(this.fieldIcon, this.hintText, this.textType);
#override
Widget build(BuildContext context) {
return Container(
width: 300,
child: Material(
elevation: 5.0,
borderRadius: BorderRadius.all(Radius.circular(10.0)),
color: Colors.deepOrange,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(12.0),
child: fieldIcon,
),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(topRight: Radius.circular(10.0), bottomRight: Radius.circular(10.0)),
),
width: 250,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: _text,
decoration: InputDecoration(
border: InputBorder.none,
hintText: hintText,
fillColor: Colors.white,
errorText: _validate? 'Value can\'t be empty' : null,
filled: true,
),
keyboardType: textType,
style: TextStyle(
fontSize: 15.0,
color: Colors.black,
),
),
),
),
],
)
),
);
}
}
And this is how I'm calling that in different pages
CustomInputField(
Icon(
Icons.lock,
color: Colors.white,
),
'Password',
TextInputType.visiblePassword),
So upon click of the button if the text field is empty, i need to give in the errorText, so could anyone help me out?

You need to do a couple of things.
Create a GlobalKey type FormState for your Stateful Widget.
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
Create a TextFormField with a validator and onSave callback.
Form(
key: _formKey, //Give you form it's key.
child: Column(
children: <Widget>[
TextFormField(
validator: (input) {
if (input.isEmpty) { //Here I check if the field data is empty.
return "Field cannot be empty.;
}
},
//Update what ever variable you want with the validated data.
//Here I picked a variable named foo.
onSaved: (input) => foo = input,
],
),
),
Make a button that uses the _formKey to call the validate function and save function.
RaisedButton(
child: Text("Do Something"),
onPressed: () {
//Validate will run on the TextFormField and return the error provided.
if (_formKey.currentState.validate()) {
_formKey.currentState.save(); //If validated, we will update our foo variable.
}
},
),

Related

how to get save the value from textfromfield and pass it back to page?

import 'package:flutter/material.dart';
import 'package:mmitra/widgets/header.dart';
import 'home.dart';
class CreateAccount extends StatefulWidget {
#override
_CreateAccountState createState() => _CreateAccountState();
}
class _CreateAccountState extends State<CreateAccount> {
late String username;
final _key = Global Key<FormState>();
#override
Widget build(BuildContext parentContext) {
return Scaffold(
appBar: header(context, titleText: 'Create Account'),
body: ListView(children: [
Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.only(top: 25),
child: Center(
child: Text(
'Create a username',
style: TextStyle(fontSize: 25.0),
),
),
),
Padding(
padding: EdgeInsets.all(16.0),
child: Form(
child: TextFormField(
key: _key,
// controller: myController,
onSaved: (val) => username = val!,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelStyle: TextStyle(fontSize: 15),
labelText: 'Username',
hintText: 'Must be at least 3 Characters'),
),
),
),
GestureDetector(
onTap: () {
_key.currentState!.save();
Navigator.pop(context, username
);
},
child: Container(
height: 50,
width: 300,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(7)),
child: Center(
child: Text(
'Submit',
style:
TextStyle(fontSize: 15, fontWeight: FontWeight.bold),
),
),
),
),
],
),
),
]),
);
}
}
and I'm getting below shown errors:
Exception caught by gesture
The following Cast Error was thrown while handling a gesture
Null check operator used on a null value
I just want to get the textfromfield value and i want to pass it to the home.dart page in order to create the document in firebase collection
you must define a EditTextControll() ex:textController and set it to TextFormField to controller paramerter , and then get text as textController.text and pass with Navigatoer .
for pass wihtin first screen to two screen
Firstly /
TextEditingController controller = TextEditingController();
Secoundly /
body: Form(
child: TextFormField(
controller: controller,
onTap: (){
//here SettingsScreen() is example
// name is paramerter in the secound screen
Navigator.push(context,
MaterialPageRoute(builder: (context)=>SettingsScreen(name:controller.text),),
);
},
),
),
You added key in TextFormField
Form(
child: TextFormField(
key: _key,
// controller: myController,
onSaved: (val) => username = val!,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelStyle: TextStyle(fontSize: 15),
labelText: 'Username',
hintText: 'Must be at least 3 Characters'),
),
),
),
Remove it from TextFormField and Add it in Form
Just Like:
Form(
key: _key,
child: TextFormField(
// controller: myController,
onSaved: (val) => username = val!,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelStyle: TextStyle(fontSize: 15),
labelText: 'Username',
hintText: 'Must be at least 3 Characters'),
),
),
),
This will Solve your Issue.

How to validate flutter text field for Email or Phone or Null?

How to validate same field for either email or phone or not null??
TextFormField(
// validator: ???,
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(16.0),
hintText: "hint_phone_no_email_address",
filled: true,
fillColor: Colors.grey.withOpacity(0.1),
),
),
I want to validate when I press my button
RaisedButton(
onPressed: () {
// call validate function from here.....
},
textColor: Colors.white,
padding: const EdgeInsets.all(0.0),
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: <Color>[
Color(0xFF0D47A1),
Color(0xFF1976D2),
Color(0xFF42A5F5),
],
),
),
child:
Text('Next', style: TextStyle(fontSize: 20)),
),
),
Please let me know...
1. Define Validation methods
Here I use email_validator for verifying the emails and a Regular Expression for the phone numbers. You can either also check intl_phone_field for the phone numbers, or libphonenumber (Though, not yet supported for Web or Desktop):
bool isEmail(String input) => EmailValidator.validate(input);
bool isPhone(String input) => RegExp(
r'^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$'
).hasMatch(input);
2. Define your TextFormField
Then, in your TextFormField, define a GlobalKey<FormFieldState> and a validator where you test for both emails and phone numbers:
TextFormField(
key: _key.value,
validator: (value) {
if (!isEmail(value) && !isPhone(value)) {
return 'Please enter a valid email or phone number.';
}
return null;
},
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(16.0),
hintText: "Enter your phone number or email",
filled: true,
fillColor: Colors.grey.withOpacity(0.1),
),
),
3. Validate the TextFormField on button press
When the user press the button, validate the TextFormField and navigate if valid.
ElevatedButton(
onPressed: () {
if (_key.value.currentState.validate()) {
// Navigate to next page
}
},
style: ButtonStyle(
padding: MaterialStateProperty.all(const EdgeInsets.all(0.0)),
foregroundColor: MaterialStateProperty.all(Colors.white),
backgroundColor: MaterialStateProperty.all(Color(0xFF0D47A1))
),
child: Text('Next', style: TextStyle(fontSize: 20)),
),
Full source code
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:email_validator/email_validator.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
);
}
bool isEmail(String input) => EmailValidator.validate(input);
bool isPhone(String input) =>
RegExp(r'^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$')
.hasMatch(input);
class HomePage extends HookWidget {
#override
Widget build(BuildContext context) {
final _key = useState(GlobalKey<FormFieldState>());
return Scaffold(
body: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(16.0),
child: Column(
children: [
TextFormField(
key: _key.value,
validator: (value) {
if (!isEmail(value) && !isPhone(value)) {
return 'Please enter a valid email or phone number.';
}
return null;
},
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(16.0),
hintText: "Enter your phone number or email",
filled: true,
fillColor: Colors.grey.withOpacity(0.1),
),
),
const SizedBox(height: 16.0),
ElevatedButton(
onPressed: () {
if (_key.value.currentState.validate()) {
// Navigate to next page
}
},
style: ButtonStyle(
padding: MaterialStateProperty.all(const EdgeInsets.all(0.0)),
foregroundColor: MaterialStateProperty.all(Colors.white),
backgroundColor:
MaterialStateProperty.all(Color(0xFF0D47A1))),
child: Text('Next', style: TextStyle(fontSize: 20)),
),
],
),
),
);
}
}
Wrap the textFormFields with a Form
Give the Form a key and create this key [_formKey] in initState
Create validator for each TextFormField that needs to be validated when your button is pressed.
Call _formKey.currentState.validate() which returns true if and only if ALL fields are validated otherwise false.
class _SomeClassState extends State<SomeClass>{
GlobalKey<FormState> _formKey; // DECLARE a formKey
#override
void initState(){
super.initState();
_formKey = GlobalKey(); // INSTANTIATE the key here
...
}
/// 4. validate function
void validateController(){
if(!_formKey.currentState.validate()){
// value is false.. textFields are rebuilt in order to show errorLabels
return;
}
// action WHEN values are valid
}
#override
Widget build(BuildContext context){
return Scaffold(
...
// 1. Form should be at the top of the widget tree
Form(
key: _formKey, // 2.
child:
...
TextFormField(
validator:(string) => string.isEmpty // 3. add your validating function here
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(16.0),
hintText: "hint_phone_no_email_address",
filled: true,
fillColor: Colors.grey.withOpacity(0.1),
),
),
...
...
RaisedButton(
onPressed: validateControllers, //4. calls function that validates [when button is pressed ONLY]
textColor: Colors.white,
padding: const EdgeInsets.all(0.0),
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: <Color>[
Color(0xFF0D47A1),
Color(0xFF1976D2),
Color(0xFF42A5F5),
],
),
),
child: Text('Next', style: TextStyle(fontSize: 20)),
),
),
...
For section 3, where you plan to create a validator for either 'Email' or 'Phone number', you can use RegExp.
if you want to validate many fields, it is advisable to use the Form widget
first make a form key
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
Form(
key: formKey,
child: Column(
children: <Widget>[
TextFormField(
validator: (String value) {
if (value.isEmpty) {
return 'Email is Required';
}
if (!RegExp(
r"^([a-zA-Z0-9_\-\.]+)#([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$")
.hasMatch(value)) {
return 'Please enter a valid Email';
}
return null;
},
decoration: InputDecoration(
contentPadding: const EdgeInsets.all(16.0),
hintText: "hint_phone_no_email_address",
filled: true,
fillColor: Colors.grey.withOpacity(0.1),
),
),
//validating when the raised button is pressed
RaisedButton(
onPressed: () {
// call validate function from here.....
validateEmail(),
},
textColor: Colors.white,
padding: const EdgeInsets.all(0.0),
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: <Color>[
Color(0xFF0D47A1),
Color(0xFF1976D2),
Color(0xFF42A5F5),
],
),
),
child:
Text('Next', style: TextStyle(fontSize: 20)),
),
),
validateEmail()async{
FormState form = formKey.currentState;
form.save();
if (!form.validate()) {
print('Invalid Email');
}else{
print('Credentials are valid');
}
}

DropdownButton<String> not changing value onChanged

I'm working with a DropDownButton, I have created a showModalBottomSheet that is called whenever I press the Add button. Inside of these Sheet I have inserted some textfields and a dropDownButton. The data from this sheet will be sent to firestore. I could successfully create the DropDownButton. But when pressed it doesn't change to the value I have selected.
The function:
void _CreateButtons(context){
TextEditingController _buttonName = TextEditingController();
TextEditingController _comodoName = TextEditingController();
String _iconName;
var _icons = ['None', 'Lock', 'LightBulb', 'Check', 'Cold', 'Alarm', 'Television', 'Bed'];
var _formKey = GlobalKey<FormState>();
showModalBottomSheet(context: context, builder: (BuildContext bc){
return Center(
child: Container(
color: Colors.black.withOpacity(0.3),
height: MediaQuery.of(context).size.height * 0.6,
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 20, 12, 20),
child: Column(
children: [
Form(
key: _formKey,
child: Column(
children: [
TextFormField(
maxLines: 1,
maxLength: 10,
controller: _buttonName,
textAlign: TextAlign.center,
decoration: InputDecoration(
hintText: "Button name",
enabledBorder: OutlineInputBorder(
borderRadius: new BorderRadius.circular(25.0),
borderSide: new BorderSide(),
),
focusedBorder: OutlineInputBorder(
borderRadius: new BorderRadius.circular(25.0),
borderSide: new BorderSide(color: Colors.blue),
),
),
validator: (val){
if (val.length == 0){
return "Button name cannot be empty";
}else{
return null;
}
},
),
SizedBox(height: 20),
TextFormField(
maxLines: 1,
maxLength: 20,
textAlign: TextAlign.center,
controller: _comodoName,
decoration: new InputDecoration(
hintText: "Room name",
enabledBorder: OutlineInputBorder(
borderRadius: new BorderRadius.circular(25.0),
borderSide: new BorderSide(),
),
focusedBorder: OutlineInputBorder(
borderRadius: new BorderRadius.circular(25.0),
borderSide: new BorderSide(color: Colors.blue),
),
),
validator: (val){
},
),
],
),
),
DropdownButton<String>(
items: _icons.map((String dropDownStringItem) {
return DropdownMenuItem<String>(
value: dropDownStringItem,
child: Row(
children: [
Icon(getIconData(dropDownStringItem)),
Text(dropDownStringItem),
],
),
);
}).toList(),
onChanged: (String name){
setState(() {
_iconName = name;
});
},
value: _iconName,
),
I call this function inside the stageful widget:
Padding(
padding: EdgeInsets.only(left: screenSize/1.2, top: screenSize*1.7),
child: FloatingActionButton(
backgroundColor: Color(0xFF0F52BA),
onPressed: (){
_CreateButtons(context);
},
child: Icon(Icons.add),
)
),
The _CreateButtons is inside the Stageful widget.
The problem is that you have used function to render UI and store state. But when you call setstate it rebuild all you UI of StatefulWidget. That is why your function - _CreateButtons called again with the same value. That is why, you should add all your bottom sheet code to different StatefulWidget to handle states correctly.
Move your codes to any StatefulWidget:
class MyCustomBottomSheet extends StatefulWidget {
createState() => _MyCustomBottomSheetState();
}
class _MyCustomBottomSheetState extends State<MyCustomBottomSheet> {
TextEditingController _buttonName = TextEditingController();
TextEditingController _comodoName = TextEditingController();
String _iconName;
var _icons = ['None', 'Lock', 'LightBulb', 'Check', 'Cold', 'Alarm', 'Television', 'Bed'];
var _formKey = GlobalKey<FormState>();
Widget build(BuildContext context) {
return Center(/// UI codes of build buttons);
}
}
and call this widget when your FloatingActionButton pressed like following:
FloatingActionButton(
backgroundColor: Color(0xFF0F52BA),
onPressed: () => showModalBottomSheet(context: context, builder: (ctx) => MyCustomBottomSheet()),
child: Icon(Icons.add),
)

How to reuse a widget in flutter

I am learning flutter I want a particular widget to be reused with different images at run time.
How to attain this is giving me difficulties I would like to know how to get this.
I am writing the peice of code kindly suggest what is a correct way
scaffold: CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
trailing: Image.asset('assets/Menu_Button.png'),
automaticallyImplyLeading: false,
backgroundColor: Colors.blueAccent,
),
child: SafeArea(
child: Material(
child: Container(
child: Column(
children: [
Column(
children: <Widget>[
new Stack(children: <Widget>[
new Container(
child: background.Row1
),
Container(
color: Colors.blueAccent,
child: Image.asset('assets/card_bg.png')
),
]
),
Container(
color: Colors.blueAccent,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ShowingOptions('assets/image1.png').Options,//*****calling function with parameter so that it can put widget**********//
ShowingOptions('assets/image2.png').Options,
ShowingOptions('assets/image3.png').Options,
ShowingOptions('assets/image4.png').Options,
],
),
background.Row2,
background.Row3
],
),
),
))
),
);
}
}
/**********function defination starts *************/
ShowingOptions(image) {
Widget Options = padding: EdgeInsets.only(bottom: 5, left: 7,
right: 7, top: 5),
child: Container(
height: 55.0,
width: 55.0,
child: Padding(
padding: EdgeInsets.all(1),
child: CircleAvatar(
backgroundColor: Colors.transparent,
radius: 10,
child: new Image.asset(image, height: 150, width:
150),
)),
decoration: new BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
border: new Border.all(
color: Colors.orange,
width: 5.0,
),
)
),
);
}
}
/**********function defination ends *************/
What Iam doing is making a funciton and when I am calling the function 'showOptions('assets/image1')' I am passing the image that I need to show.
Inside the function defination I am writing a widget that I wanted to be placed whenevr I call that funcition bys showing the image that I have passed
the way I implemented this whole is not working want a solution. I know this is not the proper way as I am new I would like to have some guidance.
Create a Custom Widget,
Create a Stateless or Stateful Class
declare Required Vairables
return your Custom Widget
below is Example of CustomButton with onPressed event.
//Create a Stateless or Stateful Class
class CustomButton extends StatelessWidget {
//declare Required Vairables
final String buttonText;
final VoidCallback onPressed;
final bool loading;
//constructor
CustomButton({this.buttonText,this.onPressed,this.loading});
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 30,right: 30),
child: Container(
decoration: BoxDecoration(borderRadius: BorderRadius.all(Radius.circular(100)),
color: Colors.red),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.all(Radius.circular(100)),
splashColor: Colors.green,
onTap: onPressed,
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Center(child:
loading==true?
CircularProgressIndicator(backgroundColor: Colors.white,)
:
Text(buttonText,style: TextStyle(fontSize: 30,color: Colors.white),)),
),
),
),
),
),
),
],
);
}
}
Use :
CustomButtonSmall(buttonText: "Direction",onPressed: (){})
Here is a wonderfully explained example of how to achieve this easily.
I just copy the code from there. You basically have to create a separate class for the widget you have in mind:
import 'package:flutter/material.dart';
class AppTextFormField extends StatelessWidget {
//
AppTextFormField({
this.controller,
this.hintText,
this.helpText,
this.prefixIcon,
this.suffixIcon,
this.isPassword,
this.enabled,
this.readOnly,
this.borderColor,
});
final TextEditingController controller;
final String hintText;
final String helpText;
final IconData prefixIcon;
final IconData suffixIcon;
final bool isPassword;
final bool enabled;
final bool readOnly;
final Color borderColor;#override
Widget build(BuildContext context) {
return Container(
child: TextFormField(
controller: controller,
readOnly: null == readOnly ? false : true,
obscureText: null == isPassword ? false : true,
decoration: InputDecoration(
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.greenAccent,
width: 1.0,
),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.greenAccent,
width: 1.0,
),
),
border: OutlineInputBorder(
borderSide: BorderSide(
color: null == borderColor ? Colors.teal : borderColor,
width: 1.0,
),
),
hintText: null == hintText ? '' : hintText,
helperText: null == helpText ? '' : helpText,
prefixIcon: null == prefixIcon ? null : Icon(prefixIcon),
suffix: null == suffixIcon ? null : Icon(suffixIcon),
enabled: null == enabled ? true : false,
),
),
);
}
}
And then in the class where you want to use the widget you call the it like this:
Container(
child: Column(
children: [
AppTextFormField(
controller: _emailController,
helpText: 'Email',
hintText: 'Email',
prefixIcon: Icons.email,
),
AppTextFormField(
controller: _passwordController,
helpText: 'Password',
hintText: 'Password',
isPassword: true,
prefixIcon: Icons.lock_open,
),
You don't have to use all the methods, just the ones you need.

Validator error message changes TextFormField's height

When the error message shows up, it reduces the height of the TextFormField. If I understood correctly, that's because the height of the error message is taking into account in the height specified.
Here's a screen before :
and after :
Tried to put conterText: ' ' to the BoxDecoration (as I've seen on another topic) but it didn't help.
An idea ?
EDIT : OMG completly forgot to put the code, here it is :
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
height: 40.0,
child: _createTextFormField(loginEmailController, Icons.alternate_email, "Email Adress", false, TextInputType.emailAddress),
),
Container(
height: 40.0,
child: _createTextFormField(loginPasswordController, Icons.lock, "Password", true, TextInputType.text),
),
SizedBox(
width: double.infinity,
child: loginButton
)
],
),
);
}
Widget _createTextFormField(TextEditingController controller, IconData icon, String hintText, bool obscureText, TextInputType inputType){
return TextFormField(
keyboardType: inputType,
controller: controller,
obscureText: obscureText,
/* style: TextStyle(
fontSize: 15.0,
), */
decoration: InputDecoration(
/* contentPadding:
EdgeInsets.symmetric(vertical: 5.0, horizontal: 8.0), */
border: OutlineInputBorder(borderRadius: BorderRadius.circular(5.0)),
icon: Icon(
icon,
color: Colors.black,
size: 22.0,
),
//hintText: hintText,
labelText: hintText,
),
validator: (value) {
if (value.isEmpty) {
return 'Enter some text';
}
return null;
},
);
}
In your Code - you need to comment out the 40 height given to each container.
Container(
// height: 40.0,
child: _createTextFormField(
loginEmailController,
Icons.alternate_email,
"Email Adress",
false,
TextInputType.emailAddress),
),
Container(
// height: 40.0,
child: _createTextFormField(loginPasswordController, Icons.lock,
"Password", true, TextInputType.text),
),
and then in your - TextFormField in InputDecoration, you can alter these value as per your liking.
contentPadding:
EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),
Above solutions did not work for me however I have figured out a very simple solution to avoid the above issue
TextFormField(
decoration: InputDecoration(
**errorStyle: const TextStyle(fontSize: 0.01),**
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(borderRadius),
borderSide: const BorderSide(
color: AppColor.neonRed,
width: LayoutConstants.dimen_1,
style: BorderStyle.solid,
),
),
);
Catch in the above solution is that we are setting the size of the error message to 0.01 so as a result it don't show up.
Additionally we can have custom border for the error.
Note : Setting the Text size to 0 is not working as it don't consider the text size and textFormField widget gets shrinked.
The problem is that we are not able to see your code so it might be challenging to assist you but I will do everything from scratch. You can firstly create the authentication class in one dart file
class AuthBloc{
StreamController _passController = new StreamController();
Stream get passStream => _passController.stream;
bool isValid(String pass){
_passController.sink.add("");
if(pass == null || pass.length < 6){
_passController.sink.addError("Password is too short");
return false;
}
else{
return true;
}
}
void dispose(){
_passController.close();
}
}
And then insert the following code in another dart file...
class LoginPage extends StatefulWidget{
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage>{
AuthBloc authBloc = new AuthBloc();
#override
void dispose(){
authBloc.dispose();
}
#override
Widget build(BuildContext context){
return Scaffold(
body: Container(
padding: EdgeInsets.fromLTRB(30, 0, 30, 0),
constraints: BoxConstraints.expand(),
children: <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(0, 40, 0, 20),
child: StreamBuilder(
stream: authBloc.passStream,
builder: (context, snapshot) => TextField(
controller: _passController,
style: TextStyle(fontSize: 18, color: Colors.black),
decoration: InputDecoration(
errorText: snapshot.hasError ? snapshot.error:null,
labelText: "Password",
prefixIcon: Container(
width: 50,
child: Icon(Icons.lock),
),
border: OutlineInputBorder(
borderSide: BorderSide(color: Color(0xffCED802), width: 1),
borderRadius: BorderRadius.all(Radius.circular(6))
)
),
),
)
),
Padding(
padding: const EdgeInsets.fromLTRB(0, 30, 0, 40),
child: SizedBox(
width: double.infinity,
height: 52,
child: RaisedButton(
onPressed: _onLoginClicked,
child: Text(
"Login",
style: TextStyle(fontSize: 18, color: Colors.white),
),
color: Color(0xff327708),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(6))
),
),
),
),
]
)
)
}
_onLoginClicked(){
var isValid = authBloc.isValid(_passController.text);
if(isValid){
//insert your action
}
}
}
I hope it works :)
Instead of using a fixed height container to wrap the textFormField, You can try to put a space in the helper text so it will keep the height of the field constant while only displaying when there is an error.
return TextFormField(
// ...
decoration: InputDecoration(
// ...
helperText: " ",
helperStyle: <Your errorStyle>,
)
According to Flutter Doc :
To create a field whose height is fixed regardless of whether or not an error is displayed, either wrap the TextFormField in a fixed height parent like SizedBox, or set the InputDecoration.helperText parameter to a space.
The problem with content padding is that you cant decrease the size of the field to UI requirement with an emphasize on decrease but how ever the second answer helped me come with a solution for my perticular problem, so am sharing that
StreamBuilder(
stream: viewModel.outEmailError,
builder: (context, snap) {
return Container(
width: MediaQuery.of(context).size.width*.7,
height: (snap.hasData)?55:35,
child: AccountTextFormField(
"E-mail",
textInputType: TextInputType.emailAddress,
focusNode: viewModel.emailFocus,
controller: viewModel.emailController,
errorText: snap.data,
textCapitalization: TextCapitalization.none,
onFieldSubmitted: (_) {
nextFocus(viewModel.emailFocus,
viewModel.passwordFocus, context);
},
),
);
}),