I have a AppTextField in flutter app as follow:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart' as intl;
class AppTextField extends StatefulWidget {
final int maxLines;
final String? title;
final TextInputType? keyboardType;
final bool autoFocus;
final TextInputAction inputAction;
final bool isSuffixIcon;
AppTextField(
{this.title,
this.maxLines: 1,
this.keyboardType,
this.autoFocus: false,
this.inputAction: TextInputAction.next,
this.isSuffixIcon: false});
#override
State<StatefulWidget> createState() => AppTextFieldSate();
}
class AppTextFieldSate extends State<AppTextField> {
String? text = '';
bool isRTL(String text) {
return intl.Bidi.detectRtlDirectionality(text);
}
#override
Widget build(BuildContext context) => Container(
child: TextField(
textDirection: isRTL(text!) ? TextDirection.rtl : TextDirection.ltr,
textInputAction: widget.inputAction,
keyboardType: widget.keyboardType,
autofocus: widget.autoFocus,
style: Theme.of(context).textTheme.bodyText1,
maxLines: widget.maxLines,
decoration: InputDecoration(
labelText: widget.title,
suffixIcon: widget.isSuffixIcon
? Icon(Icons.check_circle, color: Theme.of(context).hintColor)
: Container(),
),
onChanged: (value) {
setState(() {
text = value;
});
}));
}
When I use maxLines in AppTextField, there is a problem!
AppTextField(maxLines: 5, keyboardType: TextInputType.multiline)
Only one character is entered in a line as follow picture:
My question is:
Why occur this problem and I how to resolve it?
I resolved it :)
I must use null instead of Container in suffixIcon widget.
Container widget make to problem.
suffixIcon: widget.isSuffixIcon
? Icon(Icons.check_circle, color: Theme.of(context).hintColor)
: null,
Related
class UserInputArea extends StatefulWidget {
#override
State<UserInputArea> createState() => _UserInputAreaState();
}
class _UserInputAreaState extends State<UserInputArea> {
#override
Widget build(BuildContext context) {
String convertedText='';
setState(() {
convertedText = Provider.of<UserText>(context, listen: true).convertedText;
print('convertedText :: $convertedText');
});
return Card(
elevation: 10,
child: Container(
padding: EdgeInsets.all(10),
child: TextField(
decoration: InputDecoration(hintText: convertedText.isNotEmpty ? convertedText : 'Enter text'),
keyboardType: TextInputType.multiline,
maxLines: 5,
onChanged: (value){
Provider.of<UserText>(context, listen: false).updateText(value);
},
),
),
);
}
}
Need to update hintText field whenever convertedText gets updated.
This update is happening only if screen refreshed somehow (In Appbar, if click on home-button-icon the data get updated in TextField), Using Provider package that should listen the changes and update the required feild, didnot work. So converted page to Stateful widget and addedd setState() & moved convertedText variable inside it. But still its not working, and not able to figure it out, what is exactly missing here? Anyhelp appreciated. Thanks in advance
Please use TextEditingController class
your code will be somthing like this
class UserInputArea extends StatefulWidget {
#override
State<UserInputArea> createState() => _UserInputAreaState();
}
class _UserInputAreaState extends State<UserInputArea> {
final TextEditingController nameController = TextEditingController();
#override
void initState() {
nameController.text = "test";
super.initState();
//Here you should write your func to change the controller value
Future.delayed(const Duration(seconds: 2), () {
nameController.text = 'test after chabging';
});
}
#override
Widget build(BuildContext context) {
return Card(
elevation: 10,
child: Container(
padding: EdgeInsets.all(10),
child: TextField(
controller: nameController,
decoration: InputDecoration(hintText: convertedText.isNotEmpty ? convertedText : 'Enter text'),
keyboardType: TextInputType.multiline,
maxLines: 5,
),
),
);
}
}
in the write it code above when you will enter the page the hint text will be test after 2 seconds the value will be "test after chabging" without any problem you do not need setState(() {}) I tired it and it works
I think that putting SetState() into the method and calling the method from onChanged could solve the issue. And moving it from Widget build. Something like this:
class UserInputArea extends StatefulWidget {
#override
State<UserInputArea> createState() => _UserInputAreaState();
}
class _UserInputAreaState extends State<UserInputArea> {
String convertedText='';
void _updateField() {
setState(() {
convertedText = Provider.of<UserText>(context, listen: true).convertedText;
print('convertedText :: $convertedText');
});
#override
Widget build(BuildContext context) {
return Card(
elevation: 10,
child: Container(
padding: EdgeInsets.all(10),
child: TextField(
decoration: InputDecoration(hintText: convertedText.isNotEmpty ? convertedText : 'Enter text'),
keyboardType: TextInputType.multiline,
maxLines: 5,
onChanged: (value){
Provider.of<UserText>(context, listen: false).updateText(value);
_updateField();
},
),
),
);
}
}
Can we refactor TextField/TextFormField in Flutter without rebuilding the Widget?
I tried to refactor the TextField Widget for a form that I have created to collect some data. But, when I dismiss my keyboard, the data is losing because the widget is rebuilding. Is there any way to fix it? Pls, Let me know...
See the code below of the Refactored TextField
import 'package:flutter/material.dart';
class ContentInputWidget extends StatelessWidget {
const ContentInputWidget({
Key? key,
required this.text,
required this.controller,
this.keyboardType = TextInputType.text,
}) : super(key: key);
final String text;
final TextInputType keyboardType;
final TextEditingController controller;
#override
Widget build(BuildContext context) {
print('Content Input Widget Rebuild');
return TextField(
decoration: InputDecoration(
labelText: text,
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(15),
),
),
),
controller: controller,
keyboardType: keyboardType,
maxLines: null,
);
}
}
I've used Provider and Consumer to get the data from the text field. And I got it by using a Providers Model Class
See below
class ContentUpdater extends ChangeNotifier {
String description = 'Description comes here';
String name = 'Name';
String prayerRequest = 'Request Comes here';
String postDate = '01-01-2022';
void updatePoster(
String nameText,
String descriptionText,
String prayerReqText,
String postDtText,
) {
name = nameText;
description = descriptionText;
prayerRequest = prayerReqText;
postDate = postDtText;
notifyListeners();
}
}
Called a Function to Update Content using Provider
() { Provider.of<ContentUpdater>(context, listen: false)
.updatePoster(
nameController.text.toString(),
descriptionController.text.toString(),
requestController.text.toString(),
dateController.text.toString(),
);
}
This all works well. But the problem comes if we dismiss the keyboard by clicking the back button, the content disappears from the TextField ...
Is there any way to do without using a StateFulWidget
You can set up your TextFieldController using flutter_hooks (useTextEditingController). This will make it so the state of the TextEditingController isn't lost on rebuilds.
TextEditingController controller = useTextEditingController();
I had a similar problem a long time ago. I don't remember exactly how I made it work, but you could try adding a onChanged function to your TextField and save the data before the re-build clears the text.
final String myText;
void _onChanged() {
if (controller.text != null && controller.text != "") {
myText = controller.text;
}
}
#override
Widget build(BuildContext context) {
print('Content Input Widget Rebuild');
return TextField(
onChanged: _onChanged,
decoration: InputDecoration(
labelText: text,
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(15),
),
),
),
controller: controller,
keyboardType: keyboardType,
maxLines: null,
);
}
The problem is probably there because the controller is inside the stateless widget. When the state is changed, controller dies and gets rebuilt. Try wrapping widget inside a stateful widget and maybe it'll solve your issue.
import 'package:flutter/material.dart';
class ContentInputWidget extends StatefulWidget {
const ContentInputWidget({
Key? key,
required this.text,
required this.controller,
this.keyboardType = TextInputType.text,
}) : super(key: key);
final String text;
final TextInputType keyboardType;
final TextEditingController controller;
#override
State<ContentInputWidget> createState() => _ContentInputWidgetState();
}
class _ContentInputWidgetState extends State<ContentInputWidget> {
#override
Widget build(BuildContext context) {
print('Content Input Widget Rebuild');
return TextField(
decoration: InputDecoration(
labelText: widget.text,
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(15),
),
),
),
controller: widget.controller,
keyboardType: widget.keyboardType,
maxLines: null,
);
}
}
I composed a custom text field that check the validator when it lost focus, with some UI such as error text and green checked icon. They are working fine until I need to perform some auto-filling according to other TextField's data.
When user filled up the postal code, the API will be called from ChangeNotifierProvider view model and update the state according
user.prefectureName = data.prefectureName;
user.city = data.city;
user.address = data.address;
notifyListeners();
In the HookConsumerWidget page, I passed the state into the custom text field like this
OCTextField(
text: user.prefectureName,
labelText: l10n.accountProfileEditPrefectureName,
errorText: "都道府県は必須項目です",
onChanged: (value) => user.prefectureName = value,
validator: (value) => value.isNotEmpty),
OCTextField(
text: user.city,
labelText: l10n.accountProfileEditCity,
errorText: "市区町村は必須項目です",
onChanged: (value) => user.city = value,
validator: (value) => value.isNotEmpty),
OCTextField(
text: user.address,
labelText: l10n.accountProfileEditAddress,
errorText: "丁目・番地は必須項目です",
onChanged: (value) => user.address = value,
validator: (value) => value.isNotEmpty),
However, when the state change (user.prefectureName, user.city and user.address), my custom text field won't be able to reflect the changes accordingly.
Here is part of my custom text field code
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import '../util/ext/string_ext.dart';
typedef Validator = bool Function(String value);
class OCTextField extends HookWidget {
const OCTextField(
{Key? key,
this.text = "",
this.labelText,
this.hintText,
this.errorText,
this.maxLines = 1,
this.maxLength,
this.sanitise = true,
this.onChanged,
this.validator})
: super(key: key);
final String text;
final String? labelText;
final String? hintText;
final String? errorText;
final int? maxLines;
final int? maxLength;
final bool sanitise;
final ValueChanged<String>? onChanged;
final Validator? validator;
#override
Widget build(BuildContext context) {
final _showError = useState(false);
final _controller = useTextEditingController(text: text);
final _checkIcon;
final unchecked =
const Icon(Icons.check_circle_outline, color: Colors.black26);
final checked = const Icon(Icons.check_circle, color: Colors.green);
if (validator != null) {
_checkIcon = useState(validator!(_controller.text) ? checked : unchecked);
} else {
_checkIcon = useState(checked);
}
return Focus(
child: TextField(
controller: _controller,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(1.0),
),
suffixIcon: _checkIcon.value,
labelText: labelText,
hintText: hintText,
errorText: _showError.value ? errorText : null,
),
maxLines: maxLines,
maxLength: maxLength,
onChanged: onChanged),
onFocusChange: (isFocused) {
if (isFocused) {
_showError.value = false;
_checkIcon.value = unchecked;
} else {
if (sanitise) {
_controller.text = _controller.text.sanitise();
onChanged!(_controller.text.sanitise());
}
if (validator != null) {
_showError.value = !validator!(_controller.text);
}
_checkIcon.value = _showError.value ? unchecked : checked;
}
});
}
}
I actually read the documentation of useTextEditingController(text: text) which it does not react to the text changes. I tried to replace useTextEditingController to TextEditingController and it somehow works, with some strange behaviour - Even I erased the reflected pre-filled text and move the another field, the text will come back again; The cursor is acting weird, when I click on the field, it starts from the beginning of the pre-fill text.
It works perfectly without strange issue when I inherited TextField class and initialise the TextControllerEditor directly into TextField instance.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class OutlinedTextField extends TextField {
OutlinedTextField(
{String? labelText,
String? text,
String? errorText,
String? hintText,
TextInputType keyboardType = TextInputType.text,
int maxLines = 1,
Icon? icon,
List<TextInputFormatter>? inputFormatters,
ValueChanged<String>? onChanged,
isDense = false})
: super(
controller: text == null ? null : TextEditingController(text: text),
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(1.0),
),
labelText: labelText,
hintText: hintText,
errorText: errorText,
prefixIcon: icon,
isDense: isDense),
keyboardType: keyboardType,
maxLines: maxLines,
inputFormatters: inputFormatters,
onChanged: onChanged,
);
}
With this approach, I am not able to obtain the controller. The reason I am refactor the Custom Text Field with composition instead of inheritance is to access the controller.text for validation UI. Am I doing some serious mistake or misunderstanding the concept here? I am not able to figure out this strange behaviour.
you have to add reusable controller in your custom textfield
Note: You have to always create a seperate controller for each textfield
class CustomTextField extends StatelessWidget {
// created custom controller
TextEditingController controller;
String text;
CustomTextField({
required this.controller,
required this.text
});
#override
Widget build(BuildContext context) {
return TextField();
}
}
then create a textEditingController for each text Filed and initilize it in oninit function.
late TextEditingController text1 ;
late TextEditingController text2 ;
use the controllers in your custom text fields
CustomTextField(controller: text1 ,text: "text1",),
CustomTextField(controller: text2 ,text: "text2",)
In the below code widget.hintText is giving the error, I am trying to make the datepicker as the seperate component and dynamically pass the hinttext value whenever calling it from the other file.
import 'package:date_field/date_field.dart';
import 'package:flutter/material.dart';
class DatePicker extends StatefulWidget {
final String hintText;
DatePicker({
this.hintText,
Key key,
}): super(key: key);
#override
_DatePickerState createState() => _DatePickerState();
}
class _DatePickerState extends State<DatePicker> {
#override
Widget build(BuildContext context) {
return DateTimeFormField(
decoration: const InputDecoration(
hintText: widget.hintText,
hintStyle: TextStyle(color: Colors.black54,fontSize: 16),
errorStyle: TextStyle(color: Colors.redAccent),
suffixIcon: Icon(Icons.event_note),
),
mode: DateTimeFieldPickerMode.date,
autovalidateMode: AutovalidateMode.always,
// validator: (e) => (e?.day ?? 0) == 1 ? 'Please not the first day' : null,
onDateSelected: (DateTime value) {
},
);
}
}
The error comes from the fact of using a variable widget.hint inside of const object InputDecoration
I can't find anywhere in the date_field code where it forces you to use a constant decoration
So you might just remove the const keyword in front of InputDecoration
See this answer for details about the difference between const and final
Try removing the const for the InputDecoration()
You can try removing the final keyword from the string
I've been trying to implement a small form in Flutter and found that the onChanged and onSaved events are not available together on either of the 2 TextInput widgets.
onChanged is defined in TextField widget and onSaved is defined in TextFormField widget. One workaround is to use the TextEditingController to watch for changes but that adds a bunch of additional lines of code to add listeners, remove listeners and dispose. Is there a better solution to address this issue?
You can create your own widget to support that method, like this :
import 'package:flutter/material.dart';
class MyTextField extends StatefulWidget {
final Key key;
final String initialValue;
final FocusNode focusNode;
final InputDecoration decoration;
final TextInputType keyboardType;
final TextInputAction textInputAction;
final TextStyle style;
final TextAlign textAlign;
final bool autofocus;
final bool obscureText;
final bool autocorrect;
final bool autovalidate;
final bool maxLengthEnforced;
final int maxLines;
final int maxLength;
final VoidCallback onEditingComplete;
final ValueChanged<String> onFieldSubmitted;
final FormFieldSetter<String> onSaved;
final FormFieldValidator<String> validator;
final bool enabled;
final Brightness keyboardAppearance;
final EdgeInsets scrollPadding;
final ValueChanged<String> onChanged;
MyTextField(
{this.key,
this.initialValue,
this.focusNode,
this.decoration = const InputDecoration(),
this.keyboardType = TextInputType.text,
this.textInputAction = TextInputAction.done,
this.style,
this.textAlign = TextAlign.start,
this.autofocus = false,
this.obscureText = false,
this.autocorrect = true,
this.autovalidate = false,
this.maxLengthEnforced = true,
this.maxLines = 1,
this.maxLength,
this.onEditingComplete,
this.onFieldSubmitted,
this.onSaved,
this.validator,
this.enabled,
this.keyboardAppearance,
this.scrollPadding = const EdgeInsets.all(20.0),
this.onChanged});
#override
_MyTextFieldState createState() => _MyTextFieldState();
}
class _MyTextFieldState extends State<MyTextField> {
final TextEditingController _controller = new TextEditingController();
_onChangedValue() {
if (widget.onChanged != null) {
widget.onChanged(_controller.text);
}
}
#override
void initState() {
_controller.addListener(_onChangedValue);
super.initState();
}
#override
void dispose() {
_controller.removeListener(_onChangedValue);
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return TextFormField(
key: widget.key,
controller: _controller,
initialValue: widget.initialValue,
focusNode: widget.focusNode,
decoration: widget.decoration,
keyboardType: widget.keyboardType,
textInputAction: widget.textInputAction,
style: widget.style,
textAlign: widget.textAlign,
autofocus: widget.autofocus,
obscureText: widget.obscureText,
autocorrect: widget.autocorrect,
autovalidate: widget.autovalidate,
maxLengthEnforced: widget.maxLengthEnforced,
maxLines: widget.maxLines,
onEditingComplete: widget.onEditingComplete,
onFieldSubmitted: widget.onFieldSubmitted,
onSaved: widget.onSaved,
validator: widget.validator,
enabled: widget.enabled,
keyboardAppearance: widget.keyboardAppearance,
scrollPadding: widget.scrollPadding,
);
}
}
And include it in your page:
Padding(
padding: EdgeInsets.all(20.0),
child: Center(child: MyTextField(
onChanged: (value) {
print("testing onchanged $value");
},
)),
)