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
CodePudding user response:
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.
