Automatically Switch Textfields - flutter

I wanted to implement an age verification process but to get the age I need Textfields where the birthdate can be typed in. This is the code for the Textfield I have right now:
class TextFieldAgeInput extends StatelessWidget {
TextFieldAgeInput({
Key? key,
required this.textController,
required this.leftPadding,
required this.hintText,
}) : super(key: key);
TextEditingController textController;
double leftPadding;
String hintText;
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(
left: leftPadding,
top: 5,
),
child: Container(
height: 40,
width: 30,
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(10),
border: Border.all(
width: 1,
color: primaryColor,
),
),
child: Padding(
padding: const EdgeInsets.only(
bottom: 5,
left: 1,
),
child: TextField(
keyboardType: TextInputType.number,
style: GoogleFonts.poppins(
textStyle: const TextStyle(
color: mainTextColor,
fontSize: 15,
fontWeight: FontWeight.w600,
),
),
textAlign: TextAlign.center,
controller: textController,
decoration: InputDecoration(
hintText: hintText,
hintStyle: GoogleFonts.poppins(
textStyle: const TextStyle(
color: primaryColor,
fontSize: 11,
fontWeight: FontWeight.w600,
),
),
border: InputBorder.none,
),
onChanged: (textController) {
TextInputAction.next;
},
),
),
),
);
}
}
In the code of the screen I call the widget several times with the padding and so on. Now how can I make the textfield switch to the next one when 1 number was given (Keyboard = numberkeyboard)?
thanks for the help in advance

There are fundamental mistakes in your code like here:
onChanged: (textController) {
TextInputAction.next;
},
The onChanged property is a call back that gives you the updated text. It doesn't give you textController. Inside the body, you're using a definition that does nothing TextInputAction.next;. This should be assigned to the TextField as keyboardType: TextInputType.number.
Anyway, below is a fully runnable example that I wrote a while back and which tackle the same problem you're trying to solve. The example was for verification code but it applies to birth year as well (since both are four fields).
In the example, you'll see that each field has its own TextEditingController and FocusNode. We use the controller for setting/retrieving the value while the focus node is used to move the focus from one field to another.
The example also uses a workaround to detect when the user clicks backspace (see note at the bottom) hence you'll see zero width space character added to the controller but removed when we added to code (would be age in your example). There are notes about this within the code.
You can see the code running on DartPad here: Multiple Text Fields Example or you can copy it to your editor and run it:
// ignore_for_file: avoid_function_literals_in_foreach_calls, avoid_print
import 'package:flutter/material.dart';
void main() => runApp(MultipleTextFieldsExampleApp());
class MultipleTextFieldsExampleApp extends StatelessWidget {
const MultipleTextFieldsExampleApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
const MyHomePage({Key? key, required this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: const Center(child: CodeField()),
);
}
}
/// zero-width space character
///
/// this character can be added to a string to detect backspace.
/// The value, from its name, has a zero-width so it's not rendered
/// in the screen but it'll be present in the String.
///
/// The main reason this value is used because in Flutter mobile,
/// backspace is not detected when there's nothing to delete.
const zwsp = '\u200b';
// the selection is at offset 1 so any character is inserted after it.
const zwspEditingValue = TextEditingValue(text: zwsp, selection: TextSelection(baseOffset: 1, extentOffset: 1));
class CodeField extends StatefulWidget {
const CodeField({Key? key}) : super(key: key);
#override
_CodeFieldState createState() => _CodeFieldState();
}
class _CodeFieldState extends State<CodeField> {
List<String> code = ['', '', '', ''];
late List<TextEditingController> controllers;
late List<FocusNode> focusNodes;
#override
void initState() {
super.initState();
focusNodes = List.generate(4, (index) => FocusNode());
controllers = List.generate(4, (index) {
final ctrl = TextEditingController();
ctrl.value = zwspEditingValue;
return ctrl;
});
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
// give the focus to the first node.
focusNodes[0].requestFocus();
});
}
void printValues() {
print(code);
}
#override
void dispose() {
super.dispose();
focusNodes.forEach((focusNode) {
focusNode.dispose();
});
controllers.forEach((controller) {
controller.dispose();
});
}
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
4,
(index) {
return Container(
width: 20,
height: 20,
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
),
child: TextField(
controller: controllers[index],
focusNode: focusNodes[index],
maxLength: 2,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
counterText: "",
),
onChanged: (value) {
if (value.length > 1) {
// this is a new character event
if (index + 1 == focusNodes.length) {
// do something after the last character was inserted
FocusScope.of(context).unfocus();
} else {
// move to the next field
focusNodes[index + 1].requestFocus();
}
} else {
// this is backspace event
// reset the controller
controllers[index].value = zwspEditingValue;
if (index == 0) {
// do something if backspace was pressed at the first field
} else {
// go back to previous field
controllers[index - 1].value = zwspEditingValue;
focusNodes[index - 1].requestFocus();
}
}
// make sure to remove the zwsp character
code[index] = value.replaceAll(zwsp, '');
print('current code = $code');
},
),
);
},
),
);
}
}
In Flutter, backspace does not trigger onChanged when the TextField is empty, so that's why the workaround exists.

Related

Comparing a String to a list of strings and then if there is a match, switching a bool

I had a TextFormField widget, a grey container to set the state and a container that will change color according to the bool assosiated with it displayed on my page.
I also have a list of strings (not displayed).
import 'package:flutter/material.dart';
class Test extends StatefulWidget {
const Test({Key? key}) : super(key: key);
#override
State<Test> createState() => _TestState();
}
class _TestState extends State<Test> {
bool activated = false;
TextEditingController textController = TextEditingController();
List<String> listOfStrings = [
'String01',
'String02',
'String03',
'String04',
'String05',
'String06',
'String07',
'String08',
'String09',
'String10',
];
#override
void dispose() {
textController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
SizedBox(
height: 20,
),
Container(
padding: EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.center,
child: TextFormField(
autofocus: false,
controller: textController,
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
textInputAction: TextInputAction.done,
),
),
SizedBox(
height: 20,
),
InkWell(
onTap: () {
if (textController == listOfStrings) {
activated = true;
}
},
child: Container(
height: 20,
width: 60,
color: Colors.blueGrey,
)),
SizedBox(
height: 60,
),
Container(
width: 20,
height: 20,
color: activated ? Colors.green : Colors.red,
),
],
),
);
}
}
what I'm trying to do is that when a string is entered into the textformfield, I want it to compare the answer to the list and if there is a match, switch the bool to true and delete the string from the list.
I know I have to use an if statement in a setsate which is the grey contaner. (is it possible to setstate with enter on the keyboard instead?) but im not sure what that if statement should be to compare to the list of strings
thanks so much and any help would be greatly appreciated
Here is the answer of your first question
if (listOfStrings.contains(textController.text)) {
activated = true;
}
For your second question
Yes, you can change bool value when you press enter on TextFormField
You just need to check
onFieldSubmitted: (String s) {
//Here you can write whatever you want to do after entered
}
Remove the particular value from list
listOfStrings.removeWhere((element) => element == textController.text)

passing a widget that has setState to another page without stateful/stateless widget

Is Any Way How to pass a widget function to another page that is without any stateless/stateful? The file only includes widgets such as textfields, buttons and etc. I am trying not to cluster every fields in one page. Any helps/ideas would be appreciated!
Main.dart
class MainPage extends StatefulWidget {
const MainPage({super.key});
#override
State<Main Page> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
// bool for toggling password
bool isSecuredPasswordField = true;
#override
Widget build(BuildContext context) {
return Container();
}
// widget function that I need to pass on widget_fields.dart
Widget togglePassword() {
return IconButton(
onPressed: () {
setState(() {
isSecuredPasswordField = !isSecuredPasswordField;
});
},
icon: isSecuredPasswordField
? const Icon(Icons.visibility)
: const Icon(Icons.visibility_off),
);
}
}
widget_fields.dart
Widget userPasswordField(_passwordUserCtrlr) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 25.0),
child: TextFormField(
obscureText: true,
controller: _passwordUserCtrlr,
keyboardType: TextInputType.visiblePassword,
decoration: InputDecoration(
isDense: true,
suffixIcon: togglePassword(), //<-- I wanna call that function here
prefixIcon: const Icon(Icons.lock),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Color(0xFFCECECE)),
borderRadius: BorderRadius.circular(12),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFFCECECE)),
),
hintText: 'Password',
hintStyle: const TextStyle(
fontFamily: 'Poppins',
fontSize: 14,
),
fillColor: const Color(0xFFFEFEFE),
filled: true,
),
validator: (value) {
if (value!.isEmpty) {
return "Please enter your password.";
} else if (value.length < 8) {
return "Password should be min. 8 characters.";
} else {
return null;
}
},
),
);
}
You can pass functions like any other variable. I made a full working example that's different than yours to show a more minimal example but you can apply the same logic for your code
main.dart
import 'package:flutter/material.dart';
import 'column.dart';
void main() {
runApp(const MaterialApp(home: MyApp()));
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
Widget returnSomeText() {
return const Text("test");
}
#override
Widget build(BuildContext context) {
return Scaffold(body: createColumn(returnSomeText));
}
}
column.dart
import 'package:flutter/material.dart';
Widget createColumn(Function widgetFunction) {
return Column(
children: [widgetFunction(), widgetFunction()],
);
}
As you can see the togglePassword from your code corresponds to returnSomeText in mine. and userPasswordField is like createColumn. But it must be said that it's not recommended to use helper functions like createColumn here but to turn it into a StatelessWidget, like this for example:
import 'package:flutter/material.dart';
class CreateColumn extends StatelessWidget {
final Function widgetFunction;
const CreateColumn({Key? key, required this.widgetFunction}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
children: [widgetFunction(), widgetFunction()],
);
}
}
And then in main.dart:
return Scaffold(body: CreateColumn(widgetFunction: returnSomeText));
See also this YouTube video: Widgets vs helper methods
This is Example that how you call Widget in another class:
class MainApge extends StatefulWidget {
const MainApge({Key? key}) : super(key: key);
#override
State<MainApge> createState() => _MainApgeState();
}
class _MainApgeState extends State<MainApge> {
#override
Widget build(BuildContext context) {
return Column(
children: [
ContainerTextFields.customsTextField(
"User Name",
'enter name',
userNameController,
),
],
);
}
}
This is Custom Widget Class:
class ContainerTextFields {
static Widget customsTextField(
String label, String cusHintText, TextEditingController _controller) {
return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Padding(
padding: EdgeInsets.only(
left: SizeConfig.screenHeight! * 0.05,
top: SizeConfig.screenHeight! * 0.03),
child: Text(
label,
style: AppStyle.kUnSyncedDialogeText.copyWith(
color: AppColors.kTextFieldLabelColorGrey,
fontWeight: FontWeight.bold),
)),
Padding(
padding: EdgeInsets.only(
top: SizeConfig.screenHeight! * 0.02,
left: SizeConfig.screenHeight! * 0.042,
right: SizeConfig.screenWidth! * 0.042),
child: SingleChildScrollView(
child: Container(
height: SizeConfig.screenHeight! * 0.065,
child: TextFormField(
controller: _controller,
decoration: InputDecoration(
hintText: cusHintText,
hintStyle: TextStyle(
color: AppColors.kLoginPopUpColor,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
),
),
),
)
]);
}
}
You can pass the widget as parameter to child widget:
class MyTextField extends StatelessWidget {
const MyTextField({Key? key,
this.togglePassword,
this.passwordUserCtrlr
})
: super(key: key);
final Widget? togglePassword;
final TextEditingController? passwordUserCtrlr;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 25.0),
child: TextFormField(
obscureText: true,
controller: passwordUserCtrlr,
keyboardType: TextInputType.visiblePassword,
decoration: InputDecoration(
isDense: true,
suffixIcon: togglePassword, //<-- I wanna call that function here
prefixIcon: const Icon(Icons.lock),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Color(0xFFCECECE)),
borderRadius: BorderRadius.circular(12),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFFCECECE)),
),
hintText: 'Password',
hintStyle: const TextStyle(
fontFamily: 'Poppins',
fontSize: 14,
),
fillColor: const Color(0xFFFEFEFE),
filled: true,
),
validator: (value) {
if (value!.isEmpty) {
return "Please enter your password.";
} else if (value.length < 8) {
return "Password should be min. 8 characters.";
} else {
return null;
}
},
),
);
}
}
And can easily call from main widget:
class MainPage extends StatefulWidget {
const MainPage({super.key});
#override
State<MainPage> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
// bool for toggling password
bool isSecuredPasswordField = true;
TextEditingController? passwordUserCtrlr = TextEditingController();
#override
Widget build(BuildContext context) {
return MyTextField(
togglePassword: togglePassword(),
passwordUserCtrlr: passwordUserCtrlr,
);
}
// widget function that I need to pass on widget_fields.dart
Widget togglePassword() {
return IconButton(
onPressed: () {
setState(() {
isSecuredPasswordField = !isSecuredPasswordField;
});
},
icon: isSecuredPasswordField
? const Icon(Icons.visibility)
: const Icon(Icons.visibility_off),
);
}
}
You can create class like GlobalWidget for example, like this:
class GlobalWidget {
// widget function that I need to pass on widget_fields.dart
Widget togglePassword(Function()? onPressed, bool value) {
return IconButton(
onPressed: onPressed,
icon: value
? const Icon(Icons.visibility)
: const Icon(Icons.visibility_off),
);
}
}
And You can call the Widget like that :
GlobalWidget().togglePassword(() => setState(() {
isSecuredPasswordField = !isSecuredPasswordField;
}), isSecuredPasswordField)
What you are trying to do is impossible in the Flutter framework. You cannot call methods belonging to other widgets
Also, it is discouraged to use function to return widgets as this impacts the framework's ability to optimize the build process.
One possible solution is to package your complete password entry in a set of custom (statefull) widgets. You can collect those into a single source file if you like. Be sure to create a class for every widget.

Make some parts of TextField Editable

So I have this unique requirement as shown in the picture below, where the blue texts are user inputs. I have been doing my own research regarding this and have found a few viable solutions, out of which a custom TextEditingController seems most promising. I haven't started implementation but out of the top of my head, I think I might run into some problems with the touch and cursor controls later on.
Now my question is, Is there a better way that I might have overlooked? Is there a way to gracefully handle which areas are touchable, handle the cursor when deleting, and moving to the next focus?
As suggested by #Andrey Gordeev, we can use a Wrap Widget with a children defined as follows:
Wrap(
alignment: WrapAlignment.center,
runAlignment: WrapAlignment.center,
children: [
const Text('Hala, we\'re '),
SizedBox(
width: 50,
height: 20,
child: TextField(
controller: TextEditingController.fromValue(
const TextEditingValue(text: 'aryab'),
),
),
),
const Text(' You? I\'m '),
SizedBox(
width: 150,
height: 20,
child: TextField(
controller: TextEditingController.fromValue(
const TextEditingValue(text: 'Someone Awesome'),
),
),
),
],
)
This renders the result of the screenshot attached:
A full example snippet can be found below:
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 const MaterialApp(
title: 'Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child:
Wrap(
alignment: WrapAlignment.center,
runAlignment: WrapAlignment.center,
children: [
const Text('Hala, we\'re '),
SizedBox(
width: 50,
height: 20,
child: TextField(
controller: TextEditingController.fromValue(
const TextEditingValue(text: 'aryab'),
),
),
),
const Text(' You? I\'m '),
SizedBox(
width: 150,
height: 20,
child: TextField(
controller: TextEditingController.fromValue(
const TextEditingValue(text: 'Someone Awesome'),
),
),
),
],
),
),
);
}
}
While there are two types of widgets, <plainText,inputText> can be placed here, and we need to concern about retrieving full text from the form/text.
Creating a map for the widgets will be a good choice, I think.
final Map<String, String?> _textsInForm = {
"Hala, we're": null,
"aryab": "groupName",
". You? I'm": null,
"Someone Awysome": "myself",
",a": null,
"Driver": "job",
};
As you can see, I'm using key as main value and value as nullable string. If we find null-string, it will be simply Text widget, else TextFiled.
I'm using RichText to handle this situation.
We will create list List<InlineSpan> for RichText and List<TextEditingController> to handle input text.
I'm using StatelessWidget, while having stateFullWidget handle creation on initState.
There is one issue, it is taking minimum hint text width. You can try with passing text on TextEditingController instead.
import 'package:flutter/material.dart';
class SomeTextEditable extends StatelessWidget {
SomeTextEditable({Key? key}) : super(key: key);
final Map<String, String?> _textsInForm = {
"Hala, we're": null,
"aryab": "groupName",
". You? I'm": null,
"Someone Awysome": "myself",
",a": null,
"Driver": "job",
};
final TextStyle _hintTextStyle = const TextStyle(
color: Colors.grey,
);
final TextStyle _textFiledStyle = const TextStyle(
color: Colors.blue,
);
WidgetSpan _textFiled(TextEditingController controller, String hint) =>
WidgetSpan(
alignment: PlaceholderAlignment.middle, // set middle according to texts
child: IntrinsicWidth(
//flexiable size
child: TextField(
style: _textFiledStyle,
controller: controller,
decoration: InputDecoration(
hintStyle: _hintTextStyle,
hintText: hint,
border: InputBorder.none,
),
),
),
);
#override
Widget build(BuildContext context) {
List<TextEditingController> controllers = [];
List<InlineSpan> textSpans = [];
_textsInForm.forEach((key, value) {
if (value != null) {
TextEditingController controller = TextEditingController();
controllers.add(controller);
textSpans.add(_textFiled(controller, key));
} else {
textSpans.add(TextSpan(text: "$key "));
}
});
return Scaffold(
body: Column(
children: [
RichText(
text: TextSpan(children: textSpans),
),
ElevatedButton(
onPressed: () {
String result = "";
int i = 0;
_textsInForm.forEach((key, value) {
if (value != null) {
final textFromIcontroller = controllers[i++].text;
result += "$textFromIcontroller ";
} else {
result += "$key ";
}
});
print(result);
},
child: Text("Get Text"),
),
],
),
);
}
}

how to display validation error message out from the textfield in flutter

how to display "this field is required" message out from the box. this message will display on button click.
here is the textfield code
-------------------EDITED QUESTION-------------------
Here is your code and modified it by adding one more textformfield
import 'package:flutter/material.dart';
class ExperimentApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
scaffoldBackgroundColor: Colors.white,
),
home: ExperimentHome(),
);
}
}
class ExperimentHome extends StatelessWidget {
final GlobalKey<FormFieldState> formFieldKey = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: RoundedInputField(
formFieldKey: formFieldKey,
icon: Icons.edit,
labelText: 'Label',
validate: (value) {
if (value == null || value.isEmpty) {
return "This field is required";
}
return null;
},
),
),
),
//this is one more TextFormField
RoundedInputField(
formFieldKey: formFieldKey,
icon: Icons.edit,
labelText: 'Label1',
validate: (value) {
if (value == null || value.isEmpty) {
return "This field is required";
}
return null;
},
),
IconButton(
icon: Icon(Icons.check),
onPressed: () {
// you need to call `.validate` to actually validate the field.
formFieldKey.currentState.validate();
},
)
],
),
),
);
}
}
class RoundedInputField extends StatelessWidget {
final IconData icon;
final FormFieldValidator<String> validate;
final String labelText;
final GlobalKey<FormFieldState> formFieldKey;
// (before flutter 2.0) drop `required`
const RoundedInputField({
Key key,
#required this.formFieldKey,
#required this.labelText,
#required this.icon,
#required this.validate,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return TextFormField(
key: formFieldKey,
validator: validate,
decoration: InputDecoration(
icon: Icon(
icon,
color: Colors.blue,
),
labelText: labelText,
),
);
}
}
this is error
════════ Exception caught by rendering library ═════════════════════════════════
RenderBox was not laid out: RenderTransform#3842d NEEDS-LAYOUT NEEDS-PAINT
'package:flutter/src/rendering/box.dart':
Failed assertion: line 1940 pos 12: 'hasSize'
The relevant error-causing widget was
TextFormField-[LabeledGlobalKey<FormFieldState<dynamic>>#a4b32]
lib\abc.dart:87
════════════════════════════════════════════════════════════════════════════════
════════ Exception caught by widgets library ═══════════════════════════════════
Multiple widgets used the same GlobalKey.
════════════════════════════════════════════════════════════════════════════════
════════ Exception caught by widgets library ═══════════════════════════════════
Multiple widgets used the same GlobalKey.
════════════════════════════════════════════════════════════════════════════════
and it shows a plain white screen as output
You need to use a GlobalKey<FormFieldState> and actually call .validate on the field to validate the field.
When you call .validate, the TextFormField will validate the field and show the error message if the validate method returns a String.
More on TextFormField: https://api.flutter.dev/flutter/material/TextFormField-class.html
Code Sample (there are some syntatic differences as you seem to be using an older version of dart):
import 'package:flutter/material.dart';
void main() async {
runApp(ExperimentApp());
}
class ExperimentApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
scaffoldBackgroundColor: Colors.white,
),
home: ExperimentHome(),
);
}
}
class ExperimentHome extends StatelessWidget {
final GlobalKey<FormFieldState> formFieldKey = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: RoundedInputField(
formFieldKey: formFieldKey,
icon: Icons.edit,
labelText: 'Label',
validate: (value) {
if (value == null || value.isEmpty) {
return "This field is required";
}
return null;
},
),
),
),
IconButton(
icon: Icon(Icons.check),
onPressed: () {
// you need to call `.validate` to actually validate the field.
formFieldKey.currentState!.validate();
},
)
],
),
),
);
}
}
class RoundedInputField extends StatelessWidget {
final IconData icon;
final FormFieldValidator<String> validate;
final String labelText;
final GlobalKey<FormFieldState> formFieldKey;
// (before flutter 2.0) drop `required`
const RoundedInputField({
Key? key,
required this.formFieldKey,
required this.labelText,
required this.icon,
required this.validate,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return TextFormField(
key: formFieldKey,
validator: validate,
decoration: InputDecoration(
icon: Icon(
icon,
color: Colors.blue,
),
labelText: labelText,
),
);
}
}
This will work for you.
decoration: InputDecoration(
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.anyColor,
width: 2),
),
focusedErrorBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.anyColor,
width: 2),
),
errorBorder:
(value.isEmpty)
? UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.anyColor),
)
: InputBorder.none,
errorText:
(value.isEmpty)
? "Minimum 3 characters required"
: null,
errorStyle: anyTextStyle(),
hintText: "Name",
hintStyle:
anyTextStyle()),
Finally I got the solution! Here it works perfectly.
TextFormField widget:
import 'package:attendance_system_app/text_field_container.dart';
import 'package:flutter/material.dart';
class RoundedInputField extends StatelessWidget {
final String hintText;
final IconData icon;
final ValueChanged<String> onChanged;
final TextEditingController controller;
final double fontsize;
final FormFieldValidator<String> validate;
final String errortext;
final String labelText;
final GlobalKey<FormFieldState> formFieldKey;
//final onsaved;
const RoundedInputField(
{Key key,
this.labelText,
this.formFieldKey,
this.errortext,
this.hintText,
this.icon,
this.validate,
this.onChanged,
this.controller,
this.fontsize})
: super(key: key);
#override
Widget build(BuildContext context) {
return TextFormField(
decoration: InputDecoration(
labelText: labelText,
fillColor: Colors.blue[50],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(29.0),
),
),
validator: validate,
controller: controller,
maxLength: 5,
);
}
}
And here you can call the widget.
class JafPersonals extends StatefulWidget {
#override
_JafPersonalState createState() => _JafPersonalState();
}
class _JafPersonalState extends State<JafPersonals> {
TextEditingController _applicantName = new TextEditingController();
TextEditingController _fatherName = new TextEditingController();
final GlobalKey<FormState> _formkey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return new Scaffold(
drawer: new AdminDrawerCode(),
appBar: AppBar(
title: Image.asset(
"assets/image/company_logo.png",
height: 140,
width: 280,
),
//automaticallyImplyLeading: false,
backgroundColor: Colors.white,
iconTheme: IconThemeData(color: Colors.blue, size: 20),
//leading: new Icon(Icons.menu,color: Colors.blue,),
actions: <Widget>[
IconButton(
icon: Icon(Icons.notifications, color: Colors.blue, size: 26),
onPressed: () {
// do something
},
)
],
),
body: Form(
key: _formkey,
child: ListView(
padding: EdgeInsets.all(16),
children: [
Text(" Employee Bio Data",
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Colors.blue[900])),
SizedBox(
height: 20,
),
Text(" Personal data",
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
color: Colors.blue[900])),
SizedBox(
height: 20,
),
RoundedInputField(
labelText: 'Applicant name',
controller: _applicantName,
validate: (value) {
if (value.length < 4) {
return 'Enter at least 4 characters';
} else {
return null;
}
},
),
SizedBox(height: 10),
RoundedInputField(
labelText: 'Father name',
controller: _applicantName,
validate: (value) {
if (value.length < 4) {
return 'Enter at least 4 characters';
} else {
return null;
}
},
),
RoundedButton(
text: 'Submit',
press: () {
final isvalid = _formkey.currentState.validate();
if (isvalid) {
_formkey.currentState.save();
Navigator.push(context,
MaterialPageRoute(builder: (context) => JafFamily()));
}
},
),
],
),
),
);
}
}

How to update Pin on keyboard press?

How can I update the PinInput Boxes with input from the on-screen keyboard? From what I understand, whenever there's a state change, the widget will be rebuild. Hence, from below, what I did was updating the text whenever the on-screen keyboard detects tap. Then since the state is changed, I assumed it will rebuild all the widget which include the PinInput widget, and this is true since I tested the text whenever there's changes. I then did _pinPutController.text = text; to change the input of PinInput, however it is not working.
When I hardcode _pinPutController.text = '123', it works. So the problem is that it is not rebuilding. Am I understanding this correctly? How can I achieve what I wanted?
import 'package:flutter/material.dart';
import 'package:numeric_keyboard/numeric_keyboard.dart';
import 'package:pinput/pin_put/pin_put.dart';
import '../../../../constants.dart';
import '../../../../size_config.dart';
class InputForm extends StatefulWidget {
#override
_InputFormState createState() => _InputFormState();
}
class _InputFormState extends State<InputForm> {
String text = '';
#override
Widget build(BuildContext context) {
return Column(
children: [
PinInput(text: text),
NumericKeyboard(
onKeyboardTap: (value) {
setState(() {
text += value;
});
},
textColor: Colors.red,
rightButtonFn: () {
setState(() {
text = text.substring(0, text.length - 1);
});
},
rightIcon: Icon(
Icons.backspace,
color: Colors.red,
),
mainAxisAlignment: MainAxisAlignment.spaceEvenly),
],
);
}
}
class PinInput extends StatelessWidget {
const PinInput({
Key key,
this.text,
}) : super(key: key);
final String text;
#override
Widget build(BuildContext context) {
final size = getProportionateScreenHeight(60);
final TextEditingController _pinPutController = TextEditingController();
final FocusNode _pinPutFocusNode = FocusNode();
_pinPutFocusNode.unfocus();
print(text);
_pinPutController.text = text;
return PinPut(
fieldsCount: 4,
onSubmit: (String pin) => {},
focusNode: _pinPutFocusNode,
controller: _pinPutController,
preFilledWidget: Align(
alignment: Alignment.bottomCenter,
child: Divider(
color: kPrimaryColor,
thickness: 2.5,
indent: 7.5,
endIndent: 7.5,
),
),
textStyle: TextStyle(
fontSize: getProportionateScreenHeight(24),
),
eachFieldPadding: EdgeInsets.all(
getProportionateScreenHeight(10),
),
eachFieldMargin: EdgeInsets.all(
getProportionateScreenWidth(5),
),
eachFieldHeight: size,
eachFieldWidth: size,
submittedFieldDecoration: boxDecoration(),
selectedFieldDecoration: boxDecoration(),
followingFieldDecoration: boxDecoration(),
inputDecoration: InputDecoration(
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
counterText: '',
),
withCursor: true,
pinAnimationType: PinAnimationType.scale,
animationDuration: kAnimationDuration,
);
}
BoxDecoration boxDecoration() {
return BoxDecoration(
color: Colors.white,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(
getProportionateScreenWidth(10),
),
);
}
}
The problem is that you recreate a new TextEditingController at each rebuild of your PinInput Widget. However, if you check the PinPutState of the pinput package, it keeps a reference to the first TextEditingController you provide in its initState method:
#override
void initState() {
_controller = widget.controller ?? TextEditingController();
[...]
}
So, you have to keep the same TextEditingController all the way.
The easiest way to fix this would be to raise the TextEditingController to the State of InputForm. Instead of a String text, just use a Controller:
class InputForm extends StatefulWidget {
#override
_InputFormState createState() => _InputFormState();
}
class _InputFormState extends State<InputForm> {
TextEditingController _controller = TextEditingController(text: '');
#override
Widget build(BuildContext context) {
return Column(
children: [
PinInput(controller: _controller),
NumericKeyboard(
onKeyboardTap: (value) => _controller.text += value,
textColor: Colors.red,
rightButtonFn: () => _controller.text =
_controller.text.substring(0, _controller.text.length - 1),
rightIcon: Icon(Icons.backspace, color: Colors.red),
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
),
],
);
}
}
Note: Since you use a TextEditingController instead of a String, you can get rid of all your setState methods.
Full source code:
import 'package:flutter/material.dart';
import 'package:numeric_keyboard/numeric_keyboard.dart';
import 'package:pinput/pin_put/pin_put.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
);
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: InputForm()),
);
}
}
class InputForm extends StatefulWidget {
#override
_InputFormState createState() => _InputFormState();
}
class _InputFormState extends State<InputForm> {
TextEditingController _controller = TextEditingController(text: '');
#override
Widget build(BuildContext context) {
return Column(
children: [
PinInput(controller: _controller),
NumericKeyboard(
onKeyboardTap: (value) => _controller.text += value,
textColor: Colors.red,
rightButtonFn: () => _controller.text =
_controller.text.substring(0, _controller.text.length - 1),
rightIcon: Icon(Icons.backspace, color: Colors.red),
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
),
],
);
}
}
class PinInput extends StatelessWidget {
const PinInput({
Key key,
this.controller,
}) : super(key: key);
final TextEditingController controller;
#override
Widget build(BuildContext context) {
final size = 60.0;
final FocusNode _pinPutFocusNode = FocusNode();
_pinPutFocusNode.unfocus();
return PinPut(
fieldsCount: 4,
onSubmit: (String pin) => {},
focusNode: _pinPutFocusNode,
controller: controller,
preFilledWidget: Align(
alignment: Alignment.bottomCenter,
child: Divider(
color: Theme.of(context).primaryColor,
thickness: 2.5,
indent: 7.5,
endIndent: 7.5,
),
),
textStyle: TextStyle(
fontSize: 24,
),
eachFieldPadding: EdgeInsets.all(
10,
),
eachFieldMargin: EdgeInsets.all(
5,
),
eachFieldHeight: size,
eachFieldWidth: size,
submittedFieldDecoration: boxDecoration(),
selectedFieldDecoration: boxDecoration(),
followingFieldDecoration: boxDecoration(),
inputDecoration: InputDecoration(
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
counterText: '',
),
withCursor: true,
pinAnimationType: PinAnimationType.scale,
animationDuration: Duration(milliseconds: 500),
);
}
BoxDecoration boxDecoration() {
return BoxDecoration(
color: Colors.white,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(
10,
),
);
}
}
UPDATE: How to hide the Keyboard...
...while keeping the focus blinking cursor
1. disable the focus on your PinPut fields:
For this, I used a class described here:
class AlwaysDisabledFocusNode extends FocusNode {
#override
bool get hasFocus => false;
}
...as the focusNode of the PinPut:
class PinInput extends StatelessWidget {
[...]
#override
Widget build(BuildContext context) {
final size = 60.0;
final FocusNode _pinPutFocusNode = AlwaysDisabledFocusNode();
// _pinPutFocusNode.unfocus();
return PinPut(
[...]
focusNode: _pinPutFocusNode,
[...]
);
}
}
So, now, the PinPut never gets the focus. The Soft Keyboard is not shown but, hey!, we lost the blinking cursor!
No worries, we'll keep it always on programmatically.
2. Keep the blinking cursor always on
For this, though, we'll have to change the code of the pinput package.
Currently, in PinPutState, the blinking cursor is shown on the next field if withCursor is set to true and the PinPut has the focus. Instead, we will always show the blinking cursor if withCursoris set to true:
if (widget.withCursor /* && _focusNode!.hasFocus */ && index == pin.length) {
return _buildCursor();
}
Voilà! Does it work for you?
This has been mentioned on PinPut GitHub Issue Tracker [ref] about disabling the device keyboard when using a custom keyboard.