How to add clear button to TextField Widget - flutter

Is there a proper way to add a clear button to the TextField?
Just like this picture from Material design guidelines:
What I found is to set a clear IconButton in the InputDecoration's suffixIcon. Is this the right way?

Output:
Create a variable
var _controller = TextEditingController();
And your TextField:
TextField(
controller: _controller,
decoration: InputDecoration(
hintText: 'Enter a message',
suffixIcon: IconButton(
onPressed: _controller.clear,
icon: Icon(Icons.clear),
),
),
)

Container(
margin: EdgeInsets.only(left: 16.0),
child: TextFormField(
controller: _username,
decoration: InputDecoration(
hintText: '请输入工号',
filled: true,
prefixIcon: Icon(
Icons.account_box,
size: 28.0,
),
suffixIcon: IconButton(
icon: Icon(Icons.remove),
onPressed: () {
debugPrint('222');
})),
),
),
use iconButton

Try this -
final TextEditingController _controller = new TextEditingController();
new Stack(
alignment: const Alignment(1.0, 1.0),
children: <Widget>[
new TextField(controller: _controller,),
new FlatButton(
onPressed: () {
_controller.clear();
},
child: new Icon(Icons.clear))
]
)

Here’s another answer expanding a bit on #Vilokan Lab’s answer, which wasn’t really doing it for me since FlatButton has a minimum width of 88.0, and thus the clear button was not appearing right-aligned with the TextField at all.
So I went ahead and made my own button class, and applied that using a Stack, here is my process:
Button class:
class CircleIconButton extends StatelessWidget {
final double size;
final Function onPressed;
final IconData icon;
CircleIconButton({this.size = 30.0, this.icon = Icons.clear, this.onPressed});
#override
Widget build(BuildContext context) {
return InkWell(
onTap: this.onPressed,
child: SizedBox(
width: size,
height: size,
child: Stack(
alignment: Alignment(0.0, 0.0), // all centered
children: <Widget>[
Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: BoxShape.circle, color: Colors.grey[300]),
),
Icon(
icon,
size: size * 0.6, // 60% width for icon
)
],
)));
}
}
Then apply like so as InputDecoration to your TextField:
var myTextField = TextField(
controller: _textController,
decoration: InputDecoration(
hintText: "Caption",
suffixIcon: CircleIconButton(
onPressed: () {
this.setState(() {
_textController.clear();
});
},
)),
},
);
To get this:
Unhighlighted state
Highlighted / selected state.
Note this colouring comes free when you use suffixIcon.
Note you can also Stack it in your TextField like this, but you won't get the auto-colouring you get when you use suffixIcon:
var myTextFieldView = Stack(
alignment: Alignment(1.0,0.0), // right & center
children: <Widget>[
TextField(
controller: _textController,
decoration: InputDecoration(hintText: "Caption"),
),
Positioned(
child: CircleIconButton(
onPressed: () {
this.setState(() {
_textController.clear();
});
},
),
),
],
);

Search TextField with icon and clear button
import 'package:flutter/material.dart';
class SearchTextField extends StatefulWidget{
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return new SearchTextFieldState();
}
}
class SearchTextFieldState extends State<SearchTextField>{
final TextEditingController _textController = new TextEditingController();
#override
Widget build(BuildContext context) {
// TODO: implement build
return new Row(children: <Widget>[
new Icon(Icons.search, color: _textController.text.length>0?Colors.lightBlueAccent:Colors.grey,),
new SizedBox(width: 10.0,),
new Expanded(child: new Stack(
alignment: const Alignment(1.0, 1.0),
children: <Widget>[
new TextField(
decoration: InputDecoration(hintText: 'Search'),
onChanged: (text){
setState(() {
print(text);
});
},
controller: _textController,),
_textController.text.length>0?new IconButton(icon: new Icon(Icons.clear), onPressed: () {
setState(() {
_textController.clear();
});
}):new Container(height: 0.0,)
]
),),
],);
}
}

TextFormField(
controller:_controller
decoration: InputDecoration(
suffixIcon: IconButton(
onPressed: (){
_controller.clear();
},
icon: Icon(
Icons.keyboard,
color: Colors.blue,
),
),
),
)

TextEditingController is used to check the current state of Text, where we can decide whether we can show the cancel icon or not, depend on the availability of Text.
var _usernameController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: TextField(
controller: _usernameController,
onChanged: (text) {
setState(() {});
},
decoration: InputDecoration(
labelText: 'Username',
suffixIcon: _usernameController.text.length > 0
? IconButton(
onPressed: () {
_usernameController.clear();
setState(() {});
},
icon: Icon(Icons.cancel, color: Colors.grey))
: null),
),
),
),
);
}
Output:

Here's a snippet of my code that works.
What it does: only show clear button if text field value is not empty
class _MyTextFieldState extends State<MyTextField> {
TextEditingController _textController;
bool _wasEmpty;
#override
void initState() {
super.initState();
_textController = TextEditingController(text: widget.initialValue);
_wasEmpty = _textController.text.isEmpty;
_textController.addListener(() {
if (_wasEmpty != _textController.text.isEmpty) {
setState(() => {_wasEmpty = _textController.text.isEmpty});
}
});
}
#override
void dispose() {
_textController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return TextFormField(
controller: _textController,
decoration: InputDecoration(
labelText: widget.label,
suffixIcon: _textController.text.isNotEmpty
? Padding(
padding: const EdgeInsetsDirectional.only(start: 12.0),
child: IconButton(
iconSize: 16.0,
icon: Icon(Icons.cancel, color: Colors.grey,),
onPressed: () {
setState(() {
_textController.clear();
});
},
),
)
: null,
),);
...

TextField(
decoration: InputDecoration(
suffixIcon: IconButton(
icon: Icon(
Icons.cancel,
),
onPressed: () {
_controllerx.text = '';
}
),
)
)

To add icon inside the textfield. You must have to use suffixIcon or prefixIcon inside the Input decoration.
TextFormField(
autofocus: false,
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
suffixIcon: Icon(
Icons.clear,
size: 20.0,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(0.0)),
),
hintText: 'Enter Password',
contentPadding: EdgeInsets.all(10.0),
),
);

Didn't want to go the StatefulWidget route. Here's an example using TextEditingController and StatelessWidget (with Providers pushing updates).
I keep the controller in the static field.
class _SearchBar extends StatelessWidget {
static var _controller = TextEditingController();
#override
Widget build(BuildContext context) {
var dictionary = Provider.of<Dictionary>(context);
return TextField(
controller: _controller,
autofocus: true,
onChanged: (text) {
dictionary.lookupWord = text;
},
style: TextStyle(fontSize: 20.0),
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Search',
suffix: GestureDetector(
onTap: () {
dictionary.lookupWord = '';
_controller.clear();
},
child: Text('x'),
)));
}
}

If you want a ready-to-use Widget, which you can just put in a file and then have a reusable element you can use everywhere by inserting ClearableTextField(), use this piece of code:
import 'package:flutter/material.dart';
class ClearableTexfield extends StatefulWidget {
ClearableTexfield({
Key key,
this.controller,
this.hintText = 'Enter text'
}) : super(key: key);
final TextEditingController controller;
final String hintText;
#override
State<StatefulWidget> createState() {
return _ClearableTextfieldState();
}
}
class _ClearableTextfieldState extends State<ClearableTexfield> {
bool _showClearButton = false;
#override
void initState() {
super.initState();
widget.controller.addListener(() {
setState(() {
_showClearButton = widget.controller.text.length > 0;
});
});
}
#override
Widget build(BuildContext context) {
return TextField(
controller: widget.controller,
decoration: InputDecoration(
hintText: widget.hintText,
suffixIcon: _getClearButton(),
),
);
}
Widget _getClearButton() {
if (!_showClearButton) {
return null;
}
return IconButton(
onPressed: () => widget.controller.clear(),
icon: Icon(Icons.clear),
);
}
}
Further explanations can be found on this page:
https://www.flutterclutter.dev/flutter/tutorials/text-field-with-clear-button/2020/104/
It also builds upon IconButton, but has the advantage of only displaying the clear button when there is text inside the textfield.
Looks like this:

You can also use TextFormField.
First create Form Key. final _formKeylogin = GlobalKey<FormState>();
Form(key: _formKeylogin,
child: Column(
children: [
Container(
margin: EdgeInsets.all(20.0),
child: TextFormField(
style: TextStyle(color: WHITE),
textInputAction: TextInputAction.next,
onChanged: (companyName) {
},
validator: (companyName) {
if (companyName.isEmpty) {
return 'Please enter company Name';
} else {
return null;
}
},
),
And in OnTap or onPress Method.
_formKeylogin.currentState.reset();

To clear a button when the input it is not empty and with focus.
Create a controller variable:
//Clear inputs
final _nameInputcontroller = TextEditingController();
Create a FocusNode:
late FocusNode focusNodeNameInput;
#override
void initState() {
super.initState();
//FocusNode for all inputs
focusNodeNameInput = FocusNode();
focusNodeNameInput.addListener(() {
setState(() {});
});
}
#override
void dispose() {
// Clean up the focus node when the Form is disposed.
focusNodeNameInput.dispose();
super.dispose();
}
And the TextFormField:
TextFormField(
controller: _nameInputcontroller,
focusNode: focusNodeNameInput,
decoration: InputDecoration(
labelText: LocaleKeys.name.tr(),
labelStyle: TextStyle(
color: focusNodeNameInput.hasFocus ? AppColors.MAIN_COLOR : null,
),
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: AppColors.MAIN_COLOR,
),
),
suffixIcon: _nameInputcontroller.text.isNotEmpty || focusNodeNameInput.hasFocus
? IconButton(
onPressed: _nameInputcontroller.clear,
icon: const Icon(
Icons.clear,
color: AppColors.MAIN_COLOR,
),
) : null,
),
),

Robust solution based on the code written by the Flutter team
Here is a fully reusuable ClearableTextFormField with maximum configuration, most of the code for this clearable text field here is from the commits on Apr 1, 2021 of the Flutter team for the built-in TextFormField. The ClearableTextFormField accepts the same params as and works similarly to the built-in TextFormField.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// A [TextFormField] with a clear button
class ClearableTextFormField extends FormField<String> {
/// Creates a [FormField] that contains a [TextField].
///
/// When a [controller] is specified, [initialValue] must be null (the
/// default). If [controller] is null, then a [TextEditingController]
/// will be constructed automatically and its `text` will be initialized
/// to [initialValue] or the empty string.
///
/// For documentation about the various parameters, see the [TextField] class
/// and [new TextField], the constructor.
ClearableTextFormField({
Key? key,
this.controller,
String? initialValue,
FocusNode? focusNode,
InputDecoration decoration = const InputDecoration(),
TextInputType? keyboardType,
TextCapitalization textCapitalization = TextCapitalization.none,
TextInputAction? textInputAction,
TextStyle? style,
StrutStyle? strutStyle,
TextDirection? textDirection,
TextAlign textAlign = TextAlign.start,
TextAlignVertical? textAlignVertical,
bool autofocus = false,
bool readOnly = false,
ToolbarOptions? toolbarOptions,
bool? showCursor,
String obscuringCharacter = '•',
bool obscureText = false,
bool autocorrect = true,
SmartDashesType? smartDashesType,
SmartQuotesType? smartQuotesType,
bool enableSuggestions = true,
MaxLengthEnforcement? maxLengthEnforcement,
int? maxLines = 1,
int? minLines,
bool expands = false,
int? maxLength,
ValueChanged<String>? onChanged,
GestureTapCallback? onTap,
VoidCallback? onEditingComplete,
ValueChanged<String>? onFieldSubmitted,
FormFieldSetter<String>? onSaved,
FormFieldValidator<String>? validator,
List<TextInputFormatter>? inputFormatters,
bool? enabled,
double cursorWidth = 2.0,
double? cursorHeight,
Radius? cursorRadius,
Color? cursorColor,
Brightness? keyboardAppearance,
EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
bool enableInteractiveSelection = true,
TextSelectionControls? selectionControls,
InputCounterWidgetBuilder? buildCounter,
ScrollPhysics? scrollPhysics,
Iterable<String>? autofillHints,
AutovalidateMode? autovalidateMode,
ScrollController? scrollController,
// Features
this.resetIcon = const Icon(Icons.close),
}) : assert(initialValue == null || controller == null),
assert(obscuringCharacter.length == 1),
assert(
maxLengthEnforcement == null,
'maxLengthEnforced is deprecated, use only maxLengthEnforcement',
),
assert(maxLines == null || maxLines > 0),
assert(minLines == null || minLines > 0),
assert(
(maxLines == null) || (minLines == null) || (maxLines >= minLines),
"minLines can't be greater than maxLines",
),
assert(
!expands || (maxLines == null && minLines == null),
'minLines and maxLines must be null when expands is true.',
),
assert(!obscureText || maxLines == 1,
'Obscured fields cannot be multiline.'),
assert(maxLength == null || maxLength > 0),
super(
key: key,
initialValue:
controller != null ? controller.text : (initialValue ?? ''),
onSaved: onSaved,
validator: validator,
enabled: enabled ?? true,
autovalidateMode: autovalidateMode ?? AutovalidateMode.disabled,
builder: (FormFieldState<String> field) {
final _ClearableTextFormFieldState state =
field as _ClearableTextFormFieldState;
final InputDecoration effectiveDecoration = decoration
.applyDefaults(Theme.of(field.context).inputDecorationTheme);
void onChangedHandler(String value) {
field.didChange(value);
if (onChanged != null) onChanged(value);
}
return Focus(
onFocusChange: (hasFocus) => state.setHasFocus(hasFocus),
child: TextField(
controller: state._effectiveController,
focusNode: focusNode,
decoration: effectiveDecoration.copyWith(
errorText: field.errorText,
suffixIcon:
((field.value?.length ?? -1) > 0 && state.hasFocus)
? IconButton(
icon: resetIcon,
onPressed: () => state.clear(),
color: Theme.of(state.context).hintColor,
)
: null,
),
keyboardType: keyboardType,
textInputAction: textInputAction,
style: style,
strutStyle: strutStyle,
textAlign: textAlign,
textAlignVertical: textAlignVertical,
textDirection: textDirection,
textCapitalization: textCapitalization,
autofocus: autofocus,
toolbarOptions: toolbarOptions,
readOnly: readOnly,
showCursor: showCursor,
obscuringCharacter: obscuringCharacter,
obscureText: obscureText,
autocorrect: autocorrect,
smartDashesType: smartDashesType ??
(obscureText
? SmartDashesType.disabled
: SmartDashesType.enabled),
smartQuotesType: smartQuotesType ??
(obscureText
? SmartQuotesType.disabled
: SmartQuotesType.enabled),
enableSuggestions: enableSuggestions,
maxLengthEnforcement: maxLengthEnforcement,
maxLines: maxLines,
minLines: minLines,
expands: expands,
maxLength: maxLength,
onChanged: onChangedHandler,
onTap: onTap,
onEditingComplete: onEditingComplete,
onSubmitted: onFieldSubmitted,
inputFormatters: inputFormatters,
enabled: enabled ?? true,
cursorWidth: cursorWidth,
cursorHeight: cursorHeight,
cursorRadius: cursorRadius,
cursorColor: cursorColor,
scrollPadding: scrollPadding,
scrollPhysics: scrollPhysics,
keyboardAppearance: keyboardAppearance,
enableInteractiveSelection: enableInteractiveSelection,
selectionControls: selectionControls,
buildCounter: buildCounter,
autofillHints: autofillHints,
scrollController: scrollController,
),
);
},
);
/// Controls the text being edited.
///
/// If null, this widget will create its own [TextEditingController] and
/// initialize its [TextEditingController.text] with [initialValue].
final TextEditingController? controller;
final Icon resetIcon;
#override
_ClearableTextFormFieldState createState() => _ClearableTextFormFieldState();
}
class _ClearableTextFormFieldState extends FormFieldState<String> {
TextEditingController? _controller;
bool hasFocus = false;
TextEditingController get _effectiveController =>
widget.controller ?? _controller!;
#override
ClearableTextFormField get widget => super.widget as ClearableTextFormField;
#override
void initState() {
super.initState();
if (widget.controller == null)
_controller = TextEditingController(text: widget.initialValue);
else
widget.controller!.addListener(_handleControllerChanged);
}
#override
void didUpdateWidget(ClearableTextFormField oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.controller != oldWidget.controller) {
oldWidget.controller?.removeListener(_handleControllerChanged);
widget.controller?.addListener(_handleControllerChanged);
if (oldWidget.controller != null && widget.controller == null)
_controller =
TextEditingController.fromValue(oldWidget.controller!.value);
if (widget.controller != null) {
setValue(widget.controller!.text);
if (oldWidget.controller == null) _controller = null;
}
}
}
#override
void dispose() {
widget.controller?.removeListener(_handleControllerChanged);
super.dispose();
}
#override
void didChange(String? value) {
super.didChange(value);
if (_effectiveController.text != value)
_effectiveController.text = value ?? '';
}
#override
void reset() {
// setState will be called in the superclass, so even though state is being
// manipulated, no setState call is needed here.
_effectiveController.text = widget.initialValue ?? '';
super.reset();
}
void setHasFocus(bool b) => setState(() => hasFocus = b);
void _handleControllerChanged() {
// Suppress changes that originated from within this class.
//
// In the case where a controller has been passed in to this widget, we
// register this change listener. In these cases, we'll also receive change
// notifications for changes originating from within this class -- for
// example, the reset() method. In such cases, the FormField value will
// already have been set.
if (_effectiveController.text != value)
didChange(_effectiveController.text);
}
/// Invoked by the clear suffix icon to clear everything in the [FormField]
void clear() {
WidgetsBinding.instance!.addPostFrameCallback((_) {
_effectiveController.clear();
didChange(null);
});
}
}
Read more about the assert statement in the Dart language doc.
Below are the explanations for the additional code added to the built-in TextFormField
Optional resetIcon param for the ClearableTextFormField
🚀 Additional code inside _ClearableTextFormFieldState:
/// Invoked by the clear suffix icon to clear everything in the [FormField]
void clear() {
WidgetsBinding.instance!.addPostFrameCallback((_) {
_effectiveController.clear();
didChange(null);
});
}
addPostFrameCallback() ensures the passed-in function is only called on Widget build complete (when the TextFormField has been fully built and rendered on the screen). U can read more about it in this thread.
The following state and setState allows u to keep track of the focus change in TextField (when the field is focused or unfocused):
bool hasFocus = false;
void setHasFocus(bool b) => setState(() => hasFocus = b);
🚀 Additional code inside the builder method of the extended FormField<String> (the super() parts):
Hide the clear button when the field's value is empty or null:
suffixIcon:
((field.value?.length ?? -1) > 0 && state.hasFocus)
? IconButton(
icon: resetIcon,
onPressed: () => state.clear(),
color: Theme.of(state.context).hintColor,
)
: null,
Rebuild the TextField on focus and unfocus to show and hide the clear button:
Focus(
onFocusChange: (hasFocus) => state.setHasFocus(hasFocus),
child: TextField(// more code here...),
)

IconButton(
icon: Icon(Icons.clear_all),
tooltip: 'Close',
onPressed: () {
},
)

Related

Value change but UI don't

I want to show a button only if the new value is different from the old one. But its not working, the button isn't showing
class ViewPatientPage extends StatefulWidget {
final int status;
final String name;
const ViewPatientPage({required this.status, required this.name, super.key});
#override
State<ViewPatientPage> createState() => _ViewPatientPageState();
}
class _ViewPatientPageState extends State<ViewPatientPage> {
String name = '';
#override
void initState() {
super.initState();
name = widget.name;
}
SizedBox space() {
return const SizedBox(height: 15);
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
FocusManager.instance.primaryFocus?.unfocus();
},
child: Scaffold(
appBar: AppBar(
actions: [
(name != widget.name) ? TextButton(
onPressed: () {},
child: const Text(
'Editar',
style: TextStyle(color: Colors.white),
)): const SizedBox.shrink()
],
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Column(
children: [
space(),
TextFormField(
initialValue: widget.name,
// keyboardType: keyboardType,
validator: (val) {
if (val.toString().isEmpty || val == null || val == '') {
return 'Fill field';
}
return null;
},
decoration: InputDecoration(
label: const Text('Name'),
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
border: OutlineInputBorder(
borderSide: const BorderSide(color: Colors.black),
borderRadius: BorderRadius.circular(20))),
onChanged: (value) {
name = value.trim();
print(name);
print('widget ${widget.name}');
},
// inputFormatters: inputFormatters,
),
space(),
],
),
),
),
);
}
}
#random text to satisfy the site's rules flutter code vscode button textButton
random text to satisfy the site's rules flutter code vscode button textButton
random text to satisfy the site's rules flutter code vscode button textButton
random text to satisfy the site's rules flutter code vscode button textButton
random text to satisfy the site's rules flutter code vscode button textButton
random text to satisfy the site's rules flutter code vscode button textButton
You need to call setState() in onChanged.
Something like:
onChanged: (value) {
setState(() {
name = value.trim();
});
print(name);
print('widget ${widget.name}');
},
Call setState inside onChanged function
setState(() { name = value.trim(); });
To update the UI, you have to a call a setState() in the onChanged: (value) {}
setState(()
{
...
});

Flutter Custom Input widget listen for focus change

I have a Widget called "CustomInput" that will be massively used through all my app and I'd like to each input to listen to his own FocusNode & apply some conditions whenever the input is focused / unfocused.
Sample example:
class CustomInput extends StatefulWidget {
final String labelText;
final FocusNode focusNode;
final IconData icon;
const CustomInput({String labelText, FocusNode focusNode, IconData icon})
: this.labelText = labelText,
this.focusNode = focusNode,
this.icon = icon;
#override
_CustomInputState createState() => _CustomInputState();
}
class _CustomInputState extends State<CustomInput> {
#override
Widget build(BuildContext context) {
return input();
}
TextFormField input() {
return TextFormField(
focusNode: widget.focusNode,
decoration: InputDecoration(
labelText: widget.labelText,
labelStyle: TextStyle(color: Colors.black54),
fillColor: Colors.white,
filled: true,
prefixIcon: prefixIcon(),
),
);
}
Padding prefixIcon() {
return Padding(
padding: EdgeInsets.only(left: 5),
child: Icon(
widget.icon,
color: widget.focusNode.hasFocus /// <------------- here
? Colors.red
: Colors.black,
size: 20,
),
);
}
}
The goal is to whenever the input is focused, the prefixIcon should assume the red color and when is not focused it should assume the black color. I call the Widget this way:
Column(children: [
CustomInput(
focusNode: emailFocusNode,
icon: Icons.email_outlined,
),
CustomInput(
focusNode: passwordFocusNode,
icon: Icons.lock,
),
/// etc
]);
The problem is that the condition widget.focusNode.hasFocus is not triggering / always valuates to false. I have seen this post How to listen focus change in flutter? and also tried:
bool isFocused = false;
#override
void initState() {
super.initState();
widget.focusNode.addListener(() {
isFocused = widget.focusNode.hasFocus;
});
}
/// and then switch the prefixIcon function to
Padding prefixIcon() {
return Padding(
padding: EdgeInsets.only(left: 5),
child: Icon(
widget.icon,
color: this.isFocused
? Colors.red
: Colors.black,
size: 20,
),
);
}
But the problem persists, it seems the focus listener doesn't get triggered.
Solved. I forgot about the setState 😑
Instead of:
#override
void initState() {
super.initState();
widget.focusNode.addListener(() {
isFocused = widget.focusNode.hasFocus;
});
}
Should be:
#override
void initState() {
super.initState();
widget.focusNode.addListener(() {
setState(() {
isFocused = widget.focusNode.hasFocus
});
});
}

How can I change the position of the Textformfield validation text ? Flutter

I made a custom form and when I want to use the validation. My issue is that the error message is not appearing as I wanted. Here's the screenshot of it.
So I want to change the position of the error message to be below my container. Does anyone have any idea how to do it ?
Here's the code of the form:
class AuthForm extends StatefulWidget {
final bool isPassword;
final IconData prefixIcon;
final String hintText;
late bool isPasswordVisible = isPassword;
final bool isCalendar;
final TextEditingController controller;
final bool isDropDown;
final bool isPhone;
final String? Function(String?)? validator;
AuthForm({Key? key, this.isPassword = false, required this.prefixIcon, required this.hintText,
this.isCalendar = false, required this.controller, this.isDropDown = false, this.isPhone = false, required this.validator}) : super(key: key);
#override
State<AuthForm> createState() => _AuthFormState();
}
class _AuthFormState extends State<AuthForm> {
#override
void initState() {
super.initState();
if (widget.isPhone){
getCountryCode();
}
}
start () async {
await CountryCodes.init();
}
Locale? getCountryCode () {
start();
final Locale? deviceLocale = CountryCodes.getDeviceLocale();
final CountryDetails details = CountryCodes.detailsForLocale();
return deviceLocale;
}
DateTime selectedDate = DateTime(2000,1);
Future<void> _selectDate(BuildContext context) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(1950, 1),
lastDate: DateTime.now());
if (picked != null && picked != selectedDate) {
setState(() {
selectedDate = picked;
});
}
}
#override
Widget build(BuildContext context) {
return Container(
width: 70.w,
height: 5.h,
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 0.13.w,
color: Theme.of(context).splashColor,
),
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(
widget.prefixIcon,
color: Theme.of(context).primaryColor,
size: 6.w,
),
widget.isDropDown ? const DropDownBar() :
Expanded(
child: TextFormField(
//autovalidateMode: AutovalidateMode.onUserInteraction,
validator: widget.validator,
keyboardType: widget.isPhone ? TextInputType.phone : TextInputType.text,
inputFormatters: [DialCodeFormatter()],
controller: widget.controller,
textAlign: TextAlign.center,
obscureText: widget.isPasswordVisible,
style: Theme.of(context).textTheme.bodyText2,
decoration: InputDecoration(
hintText : widget.hintText,
hintStyle: Theme.of(context).textTheme.bodyText1,
border: InputBorder.none,
),
onTap: () async {
if (widget.isCalendar){
//Dismiss the keyboard
FocusScope.of(context).requestFocus(FocusNode());
//Call the calendar
await _selectDate(context);
widget.controller.text = DateFormat('dd-MM-yyyy').format(selectedDate);
}
}
),
),
//Show Icon only if the form is for a Password
Visibility(
visible: widget.isPassword,
//Maintain the space where the widget is even if it is hid
maintainAnimation: true,
maintainState: true,
maintainSize: true,
child: InkWell(
highlightColor : Colors.transparent,
splashColor: Colors.transparent,
child: Icon(
widget.isPasswordVisible ? Icons.visibility : Icons.visibility_off,
color: Theme.of(context).primaryColor,
size: 6.w,
),
onTap: () {
setState(() {
widget.isPasswordVisible = !widget.isPasswordVisible;
});
},
),
),
],
),
);
}
}
Thanks for your suggestion,
Chris
You should try below piece of code for TextFormField
TextFormField(
textInputAction: TextInputAction.next,
style: TextStyle(fontSize: 16, height: 1),
inputFormatters: <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(10)],
keyboardType: TextInputType.phone,
validator: (val) {
if (val!.isEmpty) {
return 'Please enter your mobile number';
} else if (val.length < 10) {
return 'Mobile number should be 10 digits only';
}
},
controller: bloc.mobileNumberLoginController,
decoration: InputDecoration(
filled: true,
border: OutlineInputBorder(
borderSide: BorderSide(
color: code != null ? HexColor(code!) : Colors.pink,
)),
contentPadding: EdgeInsets.only(top: 5, bottom: 5, left: 10, right: 10),
),
),

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.
);
}
}