Get values from separate TextFormField widget in flutter - flutter

I have separated my TextFormField Widget from the editing page to clear the clutter.
This is inside my CustomFormField:
class CustomFormField extends StatefulWidget {
String? val;
bool? isNumberPadRequired;
CustomFormField({
Key? key,
this.val = '',
this.isNumberPadRequired = false,
}) : super(key: key);
#override
_CustomFormFieldState createState() => _CustomFormFieldState();
}
class _CustomFormFieldState extends State<CustomFormField> {
#override
Widget build(BuildContext context) {
return Container(
height: 40,
child: Center(
child: TextFormField(
initialValue: widget.val!,
onSaved: (val) {
print(val);
if (val!.isNotEmpty) {
setState(() {
widget.val = val;
});
}
},
keyboardType: widget.isNumberPadRequired!
? TextInputType.number
: TextInputType.text,
textAlign: TextAlign.left,
decoration: InputDecoration(
isDense: true,
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(
vertical: 25.0,
horizontal: 10.0,
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: CustomColors.secondary,
width: 2,
),
),
),
),
),
);
}
}
And inside my EditProfilePage I have some string values like name, email and number with the current values:
String email = user.email; // john#john.com
String name = user.name; // John Doe
And inside the Form:
return Form(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildName(),
....
],
),
);
Widget _buildName() {
return FormFieldWrapper(
label: "Name",
child: CustomFormField(
val: firstName, // newly typed name: Jane Monroe
),
);
}
But when I try to call the _handleUpdate():
_updateProfile() async {
(_formKey.currentState as FormState).save();
print(name);
}
I am getting the old values i.e John Doe.

You should add the String Function argument to your custom textfield. When call saves method you should call this argument, for the update name or whatever variable.
Example;
class CustomFormField extends StatefulWidget {
String? val;
bool? isNumberPadRequired;
final Fuction(String savedValue) onSaved;
CustomFormField({
Key? key,
this.val = '',
required this.onSaved,
this.isNumberPadRequired = false,
}) : super(key: key);
#override
_CustomFormFieldState createState() => _CustomFormFieldState();
}
class _CustomFormFieldState extends State<CustomFormField> {
#override
Widget build(BuildContext context) {
return Container(
height: 40,
child: Center(
child: TextFormField(
initialValue: widget.val!,
onSaved: (val) {
print(val);
if (val!.isNotEmpty) {
widget.onSaved(val);
}
},
keyboardType: widget.isNumberPadRequired!
? TextInputType.number
: TextInputType.text,
textAlign: TextAlign.left,
decoration: InputDecoration(
isDense: true,
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(
vertical: 25.0,
horizontal: 10.0,
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: CustomColors.secondary,
width: 2,
),
),
),
),
),
);
}
}
and should implement inside Form widget;
return Form(
key: _formKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildName(),
....
],
),
);
Widget _buildName() {
return FormFieldWrapper(
label: "Name",
child: CustomFormField(
val: firstName, // newly typed name: Jane Monroe
onSaved: (String savedValue){
name = savedValue;
},
),
);
}

Related

Save input values between widget rebuilds with Bloc Flutter

I have a form builded with Bloc package.
There are two options with textfields in it.
Switching between option i've made also with bloc and Visibility widget.
When I choose an option widget rebuilds, name and price values deletes.
What is the best way to save this values between choosing options?
Here is my Bloc code
class FormBloc extends Bloc<FormEvent, MyFormState> {
FormBloc() : super(MyFormState()) {
on<RadioButtonFormEvent>(_setRadioButtonState);
}
void _setRadioButtonState(
RadioButtonFormEvent event, Emitter<MyFormState> emit) async {
emit(RadioButtonFormState(
buttonIndex: event.buttonIndex,
buttonName: event.buttonName,
));
}
}
class MyFormState {}
class RadioButtonFormState extends MyFormState {
final int buttonIndex;
final String buttonName;
RadioButtonFormState({
required this.buttonIndex,
required this.buttonName,
});
}
abstract class FormEvent extends Equatable {}
class RadioButtonFormEvent extends FormEvent {
final int buttonIndex;
final String buttonName;
RadioButtonFormEvent({
required this.buttonIndex,
required this.buttonName,
});
#override
List<Object?> get props => [buttonIndex, buttonName,];
}
Here is Form code
class FormInput extends StatelessWidget {
const FormInput({super.key});
#override
Widget build(BuildContext context) {
final formBlocWatcher = context.watch<FormBloc>().state;
final nameController = TextEditingController();
final priceController = TextEditingController();
final formOneController = TextEditingController();
final formTwoController = TextEditingController();
final formThreeController = TextEditingController();
String formOptionController = '';
bool optionOneIsActive = true;
bool optionTwoIsActive = false;
if (formBlocWatcher is RadioButtonFormState) {
switch (formBlocWatcher.buttonIndex) {
case 0:
formOptionController = formBlocWatcher.buttonName;
break;
case 1:
optionTwoIsActive = true;
optionOneIsActive = false;
formOptionController = formBlocWatcher.buttonName;
break;
}
}
return Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
top: 15,
left: 15,
right: 15),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: nameController,
decoration: const InputDecoration(hintText: 'Name'),
),
const SizedBox(height: 10),
TextField(
controller: priceController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(hintText: 'Price'),
),
const SizedBox(height: 15),
OptionsWidget(),
Visibility(
visible: optionOneIsActive,
child: TextField(
controller: formOneController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(hintText: 'Form one'),
)),
Visibility(
visible: optionTwoIsActive,
child: Column(
children: [
TextField(
controller: formTwoController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(hintText: 'Form two'),
),
TextField(
controller: formThreeController,
keyboardType: TextInputType.number,
decoration: const InputDecoration(hintText: 'Form three'),
),
],
)),
const SizedBox(height: 10),
ElevatedButton(
onPressed: () {
BlocProvider.of<ProductsListBloc>(context).add(
AddProductEvent(
productName: nameController.text,
productPrice: int.parse(priceController.text),
productDescOne: formOneController.text,
productDescTwo: formTwoController.text,
productDescThree: formThreeController.text,
formOption: formOptionController,
),
);
},
child: Text('Create New'),
),
],
),
);
}
}
class OptionsWidget extends StatelessWidget {
OptionsWidget({super.key});
int value = 0;
Widget CustomRadioButton(String text, int index, BuildContext context) {
final formBloc = BlocProvider.of<FormBloc>(context);
final blocWatch = context.watch<FormBloc>().state;
if (blocWatch is RadioButtonFormState) {
value = blocWatch.buttonIndex;
}
return OutlinedButton(
onPressed: () {
formBloc.add(RadioButtonFormEvent(
buttonIndex: index,
buttonName: text,
));
},
style: OutlinedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
side: BorderSide(color: (value == index) ? Colors.blue : Colors.grey),
),
child: Text(
text,
style: TextStyle(
color: (value == index) ? Colors.blue : Colors.grey,
),
));
}
#override
Widget build(BuildContext context) {
return Row(
children: [
CustomRadioButton("option one", 0, context),
const SizedBox(width: 15),
CustomRadioButton("option two", 1, context),
],
);
}
}
Your FormInput class should be extends from StatefulWidget, not StatelessWidget.
After this change, you should remove all TextEditingController assignments from the build() method and move them into initState().

How To Make Dynamic if-else Condition Inside Flutter TextFormField Validator

So I have a registration form with some fields and 1 submit button.
I created the UI of the form by separating it into a widget named custom_text_form_field.dart because the structure is all the same, only some widgets are dynamic to change, so I can use it multiple times
Following the original tutorial straight from Flutter, the validation works fine as it should, but the problem is that the implemented logic is applied to all the fields in the form - which I don't want that.
I want, for example like this
validator: (value) {
if (forms == "name") {
if (max > 32 && value.isEmpty) {
return 'Enter valid value on Name';
}
} else if (forms == "email") {
if (value == null || value.isEmpty) {
return 'Enter valid value on Email';
}
} else if (forms == "phone") {
if (max > 12 || value.isEmpty) {
return 'Enter valid value on Phone';
}
}
}
How to pass dynamic if-else logic into Widgets parameter?
Is this possible?
Or should I not use a separate form widget?
Here's my code:
custom_text_form_field.dart
part of 'widgets.dart';
class CustomTextFormField extends StatelessWidget {
final String title;
final String hintText;
final TextInputType type;
final bool obscureText;
final TextEditingController controller;
const CustomTextFormField({
Key? key,
required this.title,
required this.type,
required this.hintText,
this.obscureText = false,
required this.controller,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(bottom: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: blackTextStyle.copyWith(
fontSize: 16,
fontWeight: semiBold,
),
),
SizedBox(height: 8),
TextFormField(
cursorColor: kBlackColor,
keyboardType: type,
obscureText: obscureText,
controller: controller,
decoration: InputDecoration(
hintText: hintText,
hintStyle: greyTextStyle,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(
defaultRadius,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(
defaultRadius,
),
borderSide: BorderSide(
color: kPrimaryColor,
),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
],
),
);
}
}
signup_page.dart
part of 'pages.dart';
class SignUp extends StatefulWidget {
SignUp({super.key});
#override
State<SignUp> createState() => _SignUpState();
}
class _SignUpState extends State<SignUp> {
final TextEditingController nameController = TextEditingController(text: '');
final TextEditingController emailController = TextEditingController(text: '');
final TextEditingController phoneController = TextEditingController(text: '');
final TextEditingController passwordController =
TextEditingController(text: '');
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
AppBar header() {
return AppBar(
backgroundColor: kPrimaryColor,
centerTitle: true,
title: Text(
'Sign Up',
style: whiteTextStyle,
),
);
}
Widget body() {
return SafeArea(
child: ListView(
padding: EdgeInsets.all(margin16),
children: [
CustomTextFormField(
title: "Name",
type: TextInputType.name,
hintText: 'Your full name',
controller: phoneController,
),
CustomTextFormField(
title: "E-Mail",
type: TextInputType.emailAddress,
hintText: 'Your e-mail address',
controller: emailController,
),
CustomTextFormField(
title: "Mobile Number",
type: TextInputType.phone,
hintText: 'Your mobile number',
controller: phoneController,
),
CustomButton(
title: 'Sign Up',
margin: EdgeInsets.only(top: 32),
onPressed: () {
if (_formKey.currentState!.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('True')),
);
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("False")));
}
},
),
],
),
);
}
return Form(
key: _formKey,
child: Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: kBackgroundColor,
appBar: header(),
body: body(),
),
);
}
}
Thank you, please help or advice..
define one more property in customtextformfield as FormFieldValidator<String>? validator and pass dynamic validator to all the textfields.
modify your custom_text_form_field.dart like this.
part of 'widgets.dart';
class CustomTextFormField extends StatelessWidget {
final String title;
final String hintText;
final TextInputType type;
final bool obscureText;
final TextEditingController controller;
FormFieldValidator<String>? validator;
const CustomTextFormField({
Key? key,
required this.title,
required this.type,
required this.hintText,
this.obscureText = false,
required this.controller,
this.validator,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(bottom: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: blackTextStyle.copyWith(
fontSize: 16,
fontWeight: semiBold,
),
),
SizedBox(height: 8),
TextFormField(
cursorColor: kBlackColor,
keyboardType: type,
obscureText: obscureText,
controller: controller,
validator:validator,
decoration: InputDecoration(
hintText: hintText,
hintStyle: greyTextStyle,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(
defaultRadius,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(
defaultRadius,
),
borderSide: BorderSide(
color: kPrimaryColor,
),
),
),
validator: validator,
),
],
),
);
}
}
and then pass validators for every textfield individually like this.
CustomTextFormField(
title: "Mobile Number",
type: TextInputType.phone,
hintText: 'Your mobile number',
controller: phoneController,
validator: (value) {
if (forms == "name") {
if (max > 32 && value.isEmpty) {
return 'Enter valid value on Name';
}
} else if (forms == "email") {
if (value == null || value.isEmpty) {
return 'Enter valid value on Email';
}
} else if (forms == "phone") {
if (max > 12 || value.isEmpty) {
return 'Enter valid value on Phone';
}
}
}
),

How do I validate dynamically created forms in flutter?

I'm currently dynamically creating a custom form widget (Row) and am wondering what the best way to validate each form was. By attempting to use a global FormState key, nothing works as the key is shared by each form instance (I assume).
This creates issues such as the keyboard immediately dropping out upon focusing on a textfield.
Was wondering if anyone has a solution to this or could point me in the right direction. Thanks!
Root Widget:
class ExerciseTable extends ConsumerWidget {
final Exercise exercise;
ExerciseTable({#required this.exercise});
#override
Widget build(BuildContext context, ScopedReader watch) {
final _exerciseTableController = watch(exerciseTableControllerProvider);
/*
* Logic of where I build the form rows dynamically
*/
List<Widget> _buildFormRows() {
List<Widget> rows = [];
int sets = int.parse(exercise.sets);
for (int i = 1; i < sets; i++) {
rows.add(
_BuildExerciseRow(
set: i.toString(),
),
);
}
return rows;
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
exercise.exerciseName,
style: Theme.of(context).textTheme.headline6,
),
const SizedBox(height: 8),
YoutubePlayerTile(
url: exercise.exerciseURL,
),
const SizedBox(height: 8),
_BuildRowHeader(),
Column(children: _buildFormRows())
],
);
}
}
Individual Form Row Widget:
/**
* * Form Rows
*/
class _BuildExerciseRow extends StatelessWidget {
final String set;
final _formKey = GlobalKey<FormState>();
final _kgTextEditingController = TextEditingController();
final _repsTextEditingController = TextEditingController();
_BuildExerciseRow({this.set});
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 5),
child: Form(
key: _formKey,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
Text(set),
const SizedBox(width: 15),
Text("-"),
const SizedBox(width: 15),
_BuildInputTextField(
controller: _kgTextEditingController,
validator: (value) {
if (value.isEmpty) {
return "Please enter some text";
} else
return "";
}),
// const SizedBox(width: 15),
_BuildInputTextField(
controller: _repsTextEditingController,
),
TickBox(onTap: () => _formKey.currentState.validate())
],
),
),
);
}
}
class _BuildInputTextField extends StatelessWidget {
// final int keyValue;
final String Function(String) validator;
final TextEditingController controller;
_BuildInputTextField({this.validator, #required this.controller});
#override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 2),
height: 39,
width: 80,
child: TextFormField(
validator: validator,
inputFormatters: [
LengthLimitingTextInputFormatter(6),
],
controller: controller,
keyboardType: TextInputType.number,
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(bottom: 5, left: 10),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(width: 1),
),
),
onChanged: (value) => {}),
);
}
}
Custom TextFormField Widget:
class _BuildInputTextField extends StatelessWidget {
// final int keyValue;
final String Function(String) validator;
final TextEditingController controller;
_BuildInputTextField({this.validator, #required this.controller});
#override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 2),
height: 39,
width: 80,
child: TextFormField(
validator: validator,
inputFormatters: [
LengthLimitingTextInputFormatter(6),
],
controller: controller,
keyboardType: TextInputType.number,
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(bottom: 5, left: 10),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(width: 1),
),
),
onChanged: (value) => {}),
);
}
}
try changing your validator callback as
validator: (value) {
if (value.isEmpty) {
return "Please enter some text";
} else
return null;
}),
Fixed!
It was super simple, I forgot to convert _BuildExerciseRow to a Stateful Widget. (In my case, a Consumer Widget from Riverpod which extends StatefulWidget).

How to shift focus to next custom textfield in Flutter?

As per: How to shift focus to next textfield in flutter?, I used FocusScope.of(context).nextFocus() to shift focus. But this doesn't work when you use a reusable textfield class. It only works when you directly use TextField class inside Column.
import 'package:flutter/material.dart';
void main() {
return runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final focus = FocusScope.of(context);
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
CustomTextField(
textInputAction: TextInputAction.next,
onEditingComplete: () => focus.nextFocus(),
),
const SizedBox(height: 10),
CustomTextField(
textInputAction: TextInputAction.done,
onEditingComplete: () => focus.unfocus(),
),
],
),
),
),
);
}
}
class CustomTextField extends StatelessWidget {
final TextInputAction textInputAction;
final VoidCallback onEditingComplete;
const CustomTextField({
this.textInputAction = TextInputAction.done,
this.onEditingComplete = _onEditingComplete,
});
static _onEditingComplete() {}
#override
Widget build(BuildContext context) {
return TextField(
textInputAction: textInputAction,
onEditingComplete: onEditingComplete,
);
}
}
In this code, if I click next in keyboard it will not shift focus to next textfield. Please help me with this.
That's because the context doesn't have anything it could grab the focus from. Replace your code with this:
void main() => runApp(MaterialApp(home: MyApp()));
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final focus = FocusScope.of(context);
return Scaffold(
appBar: AppBar(),
body: Column(
children: <Widget>[
CustomTextField(
textInputAction: TextInputAction.next,
onEditingComplete: () => focus.nextFocus(),
),
SizedBox(height: 10),
CustomTextField(
textInputAction: TextInputAction.done,
onEditingComplete: () => focus.unfocus(),
),
],
),
);
}
}
You need to wrap your fields in a form widget with a form key and use a TextFormField instead of textField widget. Set the action to TextInputAction.next and it should work! You can also use TextInput.done to trigger the validation.
Here a fully working exemple:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class LogInPage extends StatefulWidget {
LogInPage({Key key}) : super(key: key);
#override
_LogInPageState createState() => _LogInPageState();
}
class _LogInPageState extends State<LogInPage> {
final _formKey = new GlobalKey<FormState>();
bool isLoading = false;
String firstName;
String lastName;
String password;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
backgroundColor: Colors.black,
body: body(),
);
}
Widget body() {
return Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
showInput(
firstName,
TextInputType.name,
Icons.drive_file_rename_outline,
"FirstName",
TextInputAction.next,
onSaved: (value) => firstName = value.trim()),
showInput(lastName, TextInputType.name,
Icons.drive_file_rename_outline, "LastName", TextInputAction.next,
onSaved: (value) => lastName = value.trim()),
showInput(null, TextInputType.text, Icons.drive_file_rename_outline,
"Password", TextInputAction.done,
isPassword: true, onSaved: (value) => password = value),
Padding(
padding: EdgeInsets.symmetric(vertical: 10),
),
showSaveButton(),
],
),
);
}
Widget showInput(String initialValue, TextInputType textInputType,
IconData icon, String label, TextInputAction textInputAction,
{#required Function onSaved, bool isPassword = false}) {
return Padding(
padding: EdgeInsets.fromLTRB(16.0, 20.0, 16.0, 0.0),
child: new TextFormField(
style: TextStyle(color: Theme.of(context).primaryColorLight),
maxLines: 1,
initialValue: initialValue,
keyboardType: textInputType,
textInputAction: textInputAction,
autofocus: false,
obscureText: isPassword,
enableSuggestions: !isPassword,
autocorrect: !isPassword,
decoration: new InputDecoration(
fillColor: Theme.of(context).primaryColor,
hintText: label,
hintStyle: TextStyle(color: Theme.of(context).primaryColorDark),
filled: true,
contentPadding: new EdgeInsets.fromLTRB(10.0, 10.0, 10.0, 10.0),
border: new OutlineInputBorder(
borderRadius: new BorderRadius.circular(12.0),
),
icon: new Icon(
icon,
color: Theme.of(context).primaryColorLight,
)),
validator: (value) {
return value.isEmpty && !isPassword
? "You didn't filled this field."
: null;
},
onSaved: onSaved,
onFieldSubmitted:
textInputAction == TextInputAction.done ? (value) => save() : null,
),
);
}
Widget showSaveButton() {
return RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(100))),
color: Theme.of(context).primaryColor,
padding: EdgeInsets.symmetric(vertical: 12, horizontal: 25),
child: isLoading
? SizedBox(height: 17, width: 17, child: CircularProgressIndicator())
: Text(
"Sauvegarder",
style: TextStyle(color: Theme.of(context).primaryColorLight),
),
onPressed: save,
);
}
void save() async {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
//TODO
}
}
}
FocusNode textSecondFocusNode = new FocusNode();
TextFormField textFirst = new TextFormField(
onFieldSubmitted: (String value) {
FocusScope.of(context).requestFocus(textSecondFocusNode);
},
);
TextFormField textSecond = new TextFormField(
focusNode: textSecondFocusNode,
);
// render textFirst and textSecond where you want

Implementation Outlined text field Input with lable text in flutter application

i want to have a textFieldInput with border that has label inside the border like the image below. Thankyou in advance
TextField(
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
labelText: "Label",
hintText: "Input Text",
contentPadding: EdgeInsets.fromLTRB(32, 16, 32, 16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
Result:
I think you want to achieve something like this.
Inactive
Active
Validation
You can achieve this design by using this widget.
class OutlineBorderTextFormField extends StatefulWidget {
FocusNode myFocusNode;
TextEditingController tempTextEditingController;
String labelText;
TextInputType keyboardType;
bool autofocus = false;
TextInputAction textInputAction;
List<TextInputFormatter> inputFormatters;
Function validation;
bool checkOfErrorOnFocusChange = false;//If true validation is checked when evre focus is changed
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _OutlineBorderTextFormField();
}
OutlineBorderTextFormField(
{#required this.labelText,
#required this.autofocus,
#required this.tempTextEditingController,
#required this.myFocusNode,
#required this.inputFormatters,
#required this.keyboardType,
#required this.textInputAction,
#required this.validation,
#required this.checkOfErrorOnFocusChange});
}
class _OutlineBorderTextFormField extends State<OutlineBorderTextFormField> {
bool isError = false;
String errorString = "";
getLabelTextStyle(color) {
return TextStyle(
fontSize: 12.0, color: color
);
} //label text style
getTextFieldStyle() {
return TextStyle(
fontSize: 12.0,
color: Colors.black,
);
} //textfield style
getErrorTextFieldStyle() {
return TextStyle(
fontSize: 10.0,
color: Colors.red,
);
}// Error text style
getBorderColor(isfous) {
return isfous
? Colors.deepPurple
: Colors.black54;
}//Border colors according to focus
#override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.only(left: 16.0, top: 15.0, right: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
FocusScope(
child: Focus(
onFocusChange: (focus) {
//Called when ever focus changes
print("focus: $focus");
setState(() {
getBorderColor(focus);
if (widget.checkOfErrorOnFocusChange &&
widget
.validation(widget.tempTextEditingController.text)
.toString()
.isNotEmpty) {
isError = true;
errorString = widget
.validation(widget.tempTextEditingController.text);
} else {
isError = false;
errorString = widget
.validation(widget.tempTextEditingController.text);
}
});
},
child: Container(
padding: EdgeInsets.all(2.0),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.all(Radius.circular(
6.0) // <--- border radius here
),
border: Border.all(
width: 1,
style: BorderStyle.solid,
color: isError
? Colors.red
: getBorderColor(widget.myFocusNode.hasFocus),
)),
child: TextFormField(
focusNode: widget.myFocusNode,
controller: widget.tempTextEditingController,
style: getTextFieldStyle(),
autofocus: widget.autofocus,
keyboardType: widget.keyboardType,
textInputAction: widget.textInputAction,
inputFormatters: widget.inputFormatters,
validator: (string) {
if (widget
.validation(widget.tempTextEditingController.text)
.toString()
.isNotEmpty) {
setState(() {
isError = true;
errorString = widget
.validation(widget.tempTextEditingController.text);
});
return "";
} else {
setState(() {
isError = false;
errorString = widget
.validation(widget.tempTextEditingController.text);
});
}
return null;
},
decoration: InputDecoration(
labelText: widget.labelText,
labelStyle: isError
? getLabelTextStyle(
Colors.red)
: getLabelTextStyle(Colors.deepPurple),
contentPadding:
EdgeInsets.symmetric(vertical: 7, horizontal: 16),
fillColor: Colors.grey[200],
filled: true,
enabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
border: InputBorder.none,
errorStyle: TextStyle(height: 0),
focusedErrorBorder: InputBorder.none,
disabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
hasFloatingPlaceholder: true),
),
),
),
),
Visibility(
visible: isError ? true : false,
child: Container(
padding: EdgeInsets.only(left: 15.0, top: 2.0),
child: Text(
errorString,
style: getErrorTextFieldStyle(),
))),
],
),
);
;
}
}
Example for calling this widget
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
FocusNode myFocusNode = new FocusNode();
TextEditingController tempTextEditingController = TextEditingController();
FocusNode myFocusNode1 = new FocusNode();
TextEditingController tempTextEditingController1 = TextEditingController();
void validateAndSave() {
final FormState form = _formKey.currentState;
if (form.validate()) {
print('Form is valid');
} else {
print('Form is invalid');
}
}
String getTempIFSCValidation(String text) {
return text.length > 5 ? "* Please enter valid IFSC Code" : "";
}
String getTempAccountValidation(String text) {
return text.length > 8 ? "* Please enter valid Account Number" : "";
}
#override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Form(
key: _formKey,
child: Column(
children: <Widget>[
OutlineBorderTextFormField(labelText: "Account Number*",myFocusNode: myFocusNode,tempTextEditingController: tempTextEditingController,
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
autofocus: false,
checkOfErrorOnFocusChange: true,
inputFormatters: [
LengthLimitingTextInputFormatter(18),
WhitelistingTextInputFormatter.digitsOnly
],
validation: (textToValidate){
return getTempAccountValidation(textToValidate);
},),
OutlineBorderTextFormField(labelText: "Re- Enter Account Number*",myFocusNode: myFocusNode1,tempTextEditingController: tempTextEditingController1,
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
autofocus: false,
checkOfErrorOnFocusChange: true,
inputFormatters: [
LengthLimitingTextInputFormatter(18),
WhitelistingTextInputFormatter.digitsOnly
],
validation: (textToValidate){
print("Value Validated");
return getTempIFSCValidation(textToValidate);
},),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: validateAndSave,//call the validation method
tooltip: 'Validate',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}