I am using a text field in my app to write comments, when the user type a text and press a button the text should be written to the database. However, I have a problem which is when I write the text then press done or return from the keyboard, the text disappear then there is no comment to be added. Is there any idea to save the value of the text in the text field even after pressing done or return?
Not sure why your input is disappearing but you can use a TextEditingController and pass that controller to a TextField.
Then access the value of the TextField using controller.text .
Here is a little example
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
String databaseText;
TextEditingController controller = TextEditingController();
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: TextField(
controller: controller,
onEditingComplete: () {
databaseText = controller.text;
print(databaseText);
},
),
),
),
);
}
}
this issue happen because the TextEditingController is rebuilt after you add your text. for me I used static controller to solve this problem.
static TextEditingController textController = TextEditingController();
and I highly recommend reading this
String _text;
final formKey = GlobalKey<FormState>();
Form(
key: formKey,
child: Column(children: <Widget>[
TextFormField(
autocorrect: false,
decoration: InputDecoration(labelText: "Text:"),
onSaved: (str) => _text = str,
)
]));
Then just pass your _text variable inside your paramater as your key value.
Try modifing contentPadding in InputDecoration. For example:
contentPadding: EdgeInsets.symmetric(horizontal: 2, vertical: 0)
The corresponding Flutter bug report is #29542: "Text getting cleared when using TextEditingController AND StreamBuilder".
There, user bizz84 commented, asking:
So the reason that this works with StatefulWidget is that the state is retained even across rebuilds, so I can use it to hold my TextEditingControllers.
Is this correct?
And rrousselGit replied:
Exactly :-)
Generally speaking, don't create anything but primitives in a StatelessWidget/InheritedWIdget. For everything else, you'll need a State
So, this appears to be the official answer: if you are creating anything other than primitives, you will need a State.
Related
I am facing a problem with TextField, since I am working hand in hand with TextEditingController()..text and onChanged, but when entering a new data, it is not reflected in the TextField. I made a print inside the onChanged this same one recognizes a new entry but the value to initiate continues without being updated. Inside the onChanged, I have a function which is in charge of validating what is entered and returning an error if necessary. When I comment the instruction before mentioned the TextField already allows to enter and to update what the user enters.
I hope you can help me, a feedback, tutorial, etc.
I would appreciate it.
TextField Code:
CustomTextField(
controller: TextEditingController()..text = datumAdministrative.name,
placeholder: Constants.selectDate,
helperText: Constants.requiredData,
keyboardType: TextInputType.text,
enable: true,
errorText: validationForm.name.error,
textInputAction: TextInputAction.next,
textCapitalization: TextCapitalization.sentences,
onChanged: (String value) {
validationForm.changeName(value);
},
);
ValidationForm Code:
void changeName(String value) {
String pattern = r'(^[a-zA-Z ]*$)';
RegExp regExp = new RegExp(pattern);
regExp.hasMatch(value)
? _name = ValidationItem(value, null)
: _name = ValidationItem(null, Constants.nameAdministrativeMessage);
notifyListeners();
}
Try Using : TextController(text: "<Required Text>")
Also does the validation have to be every single time the user enter any word ?
If not, you can try validation everything at the end.
If you are trying to use reactive validation, make sure your CustomTextField is wrapped with the widget which is responsible for rebuilding the UI.. something like Consumer() when using provider package
The problem is probably caused because the TextEditingController object is getting discarded by the rebuilds made by Flutter because you are instantiating the TextEditingController inside a build method. You should save the instance of your controller elsewhere, like in a state object as shown by the official docs or in your case, you can create it and get it from your validationForm.
This is the example in the docs:
/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
/// save the [TextEditingController] instance
final TextEditingController _controller = TextEditingController();
...
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(6),
child: TextFormField(
controller: _controller, // User your instance
decoration: const InputDecoration(border: OutlineInputBorder()),
),
),
);
}
}
Possible solution in your case:
CustomTextField(
controller: validationForm.myTextController,
placeholder: Constants.selectDate,
...
I have a TextField and an IconButton in a row like so.
I would like the IconButton to be enabled only when there is text in the TextField. I am using the provider package for state management.
Here is the ChangeNotifier implementation.
class ChatMessagesProvider with ChangeNotifier{
List<ChatMessage> chatMessages = <ChatMessage>[];
bool messageTyped = false;
ChatMessagesProvider(this.chatMessages);
void newMessage(String textMessage){
ChatMessage message = ChatMessage(textMessage);
this.chatMessages.add(message);
notifyListeners();
}
int messageCount() => chatMessages.length;
void updateMessageTyped(bool typed){
this.messageTyped = typed;
// notifyListeners(); Un-comennting this makes the Text disappear everytime I type something on the text field
}
}
Here is the actual widget:
class TextCompose extends StatelessWidget {
final TextEditingController _composeTextEditingController = new TextEditingController();
TextCompose(this.chatMessagesProvider);
final ChatMessagesProvider chatMessagesProvider;
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
Flexible(
child: new TextField(
controller: _composeTextEditingController,
onSubmitted: (String text) {
_onMessageSubmitted(text, context);
},
onChanged: (String text){
if(text.length > 0){
chatMessagesProvider.updateMessageTyped(true);
print(text);
}
else{
chatMessagesProvider.updateMessageTyped(false);
print("No text typed");
}
},
decoration: new InputDecoration.collapsed(
hintText: "Enter message"
),
),
),
new Container(
margin: new EdgeInsets.all(8.0),
child: new IconButton(
color: Theme.of(context).accentColor,
icon: new Icon(Icons.send),
disabledColor: Colors.grey,
onPressed:chatMessagesProvider.messageTyped // This dosen't work
? () => _onMessageSubmitted(_composeTextEditingController.text, context)
: null,
),
)
],
),
);
}
void _onMessageSubmitted(String text, BuildContext context){
if(chatMessagesProvider.messageTyped) { // This works fine.
// clear the message compose text box
_composeTextEditingController.clear();
// add the message to provider.
chatMessagesProvider.newMessage(text);
// set the message typed to false
chatMessagesProvider.messageTyped = false;
}
I am using messageTyped from ChatMessageProvider to check to see if there is any text in the TextField. It seems to work fine when I check it in the _onMessageSubmitted method but not when I check its value in the onPressed property for the IconButton.
I know this because I can see the IconButton remains disabled(colour doesn't change from grey) when I type text, whereas I can hit the submit button on the virtual keyboard and the text is cleared from the TextField(as per call to _composeTextEditingController.clear())
Question:
why does chatMessagesProvider.messageTyped return the right value when called from the _onMessageSubmitted but not when it is called from the onPrssed attribute in the IconButton?
How would I debug something like this in Flutter, I would really like to drop a breakpoint in onPressedAttribute and see the value for chatMessagesProvider.messageTyped
Let me know if you need to see any more of my code.
onPressed:chatMessagesProvider.messageTyped this line is being executed during widget build time so it is always default value and it will never get refreshed until unless you rebuild the widget using notify listener or stateful widget.
Store the currently being typed message in your provider and make your send button enable/disable depends on whether currently being typed message is empty or not.
You say you are using 'provider_package' but you actually have no Provider in your layout. Instead you have a custom built ChangeNotifier with no listeners - you are indeed calling notifyListeners() but there are actually no listeners, so no rebuild is being triggered. A rebuild is needed in order for the button to change its onPressed function reference and implicitly its color.
As for debugging, you can set a breakpoint on the line with onPressed, but it will only be hit during a rebuild.
The most important thing to understand is that the function reference you give to onPressed will be invoked correctly, but a rebuild is needed for the widget to change visually.
Although your current ChangeNotifier implementation does not make much sense, simply wrapping your calls to updateMessageTyped within setState should solve the visual problem - and your breakpoint will also be hit after each typed/deleted character.
The simplest solution you can, first of all, make your widget StatefulWidget.
You need a boolean inside State class:
bool hasText = false;
Then create initState:
#override
void initState() {
_composeTextEditingController.addListener(() {
if (_composeTextEditingController.text.isNotEmpty) {
setState(() {
hasText = true;
});
} else {
setState(() {
hasText = false;
});
}
});
super.initState();
}
Also don't forget to dispose:
#override
void dispose() {
_composeTextEditingController.dispose();
super.dispose();
}
And finally your build method:
Row(
children: <Widget>[
Expanded(
child: TextField(
controller: _composeTextEditingController,
)),
if (hasText) IconButton(icon: Icon(Icons.send), onPressed: () {})
],
),
I have 3 TextFormFields for inputting of telephone numbers on a Flutter Form (daytime, evening and mobile).
Validation for each TextFormField (in the validator:) allows a blank string to be input.
But I don't want the form to be saved unless there is at least one phone number entered.
formKey.currentState.validate will obviously validate all individual fields as being valid.
So is there a simple way in the framework to cross validate all the TextFormFields and display an error without having to write individial validators for each TextFormField and include references to specific fieldnames (which I regard as being a bit of a dirty hack) e.g.
String _validatePhoneNumber(String value) {
// dirty bit - means I have to write a separate validator for each TextFormField rather than use a generic validator
if (value.isEmpty && this.eveningNumber.isEmpty && this.mobileNumber.isEmpty)
return 'At least one number must be included';
if (value.isEmpty) return null;
if (_invalidNumber(value))
return 'Enter a valid phone number';
return null;
}
You should add a TextEditingController to each TextFormField, that way you can check the value of each field when you submit your form.
For example:
class MyForm extends StatefulWidget {
#override
_MyFormState createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
final _formKey = GlobalKey<FormState>();
TextEditingController first = TextEditingController();
TextEditingController second = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Form(
key: _formKey,
child: ListView(
children: <Widget> [
TextFormField(
controller: firstController
// rest of your stuff
)
// rest of your text fields using subsequent controllers
// secondController, thirdControler...
]
)
)
)
}
}
Then whenever you want to check it's value you just call firstController.text or better if you want to know if it's empty you just call firstController.text.isEmpty
I am looking for a better explanation on the benefit of TextEditingController over OnChanged event for a TextField.
My understanding is that onChanged's setState notifies all widgets of the change in state variable value. This way any widget (e.g. Text) can simply use the state variable and it will be notified of its changes.
My false hopes were TextEditingController would make it even simpler that I won't even need a state variable. Something like below:
import "package:flutter/material.dart";
class TestForm extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return TestFormState();
}
}
class TestFormState extends State<TestForm> {
//string myStateVariable = "";
final ctrl = TextEditingController();
#override
Widget build(BuildContext context) {
var tf = TextField(
controller: ctrl,
);
var t = Text("Current value: " + ctrl.text); // <<<<<<<<<<< false hope! doesnt work!
var x = Column(children: <Widget>[tf,t],);
return MaterialApp(home: Material(child: Scaffold(
appBar: AppBar(title: Text("Test Form"),),
body: x,
)));
}
}
Can anyone explain why TextEditingController or something similar cannot manage the state itself and notifies all consumers of change in state?
Thanks.
You are just not setting state synchronously that's all. What onChanged does is exactly possible with this approach:
class _TestFormState extends State<TestForm> {
late TextEditingController controller;
#override
void initState() {
controller = TextEditingController()
..addListener(() {
setState(() {});
});
super.initState();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Text('Current Value: ${controller.text}'),
TextField(
controller: controller,
),
],
);
}
}
As you see, we have listener that setting state every time state of the controller changes. This is exactly what onChanged does.
So, about benefits, you can achieve everything with both approach, it's a subjective way.
About benefits:
If you need to hold field values within Stream, onChanged is what you need. In other cases you may use controller.
Actually you won't need both in most of time in my opinion because TextFormField + Form within StatefulWidget is quite complete way to implement form pages. Checkout cookbook: https://flutter.dev/docs/cookbook/forms/validation
TextEditingController actually is managing his own state, that's why you can see the input on the screen once you change it.
You have 2 problems here, the first is that you are not adding any listener to the TextEditingController, you are just asking "give me the current value" only when you build the widget, not "give me the value any time it changes". To achieve this you need to add a listener to the text controller and it will be called every time that the value change.
Try this :
#override
void initState() {
super.initState();
// Start listening to changes.
ctrl.addListener(_printValue);
}
_printValue() {
print("Value: ${ctrl.text}");
}
This will work because print doesn't need to render anything on the screen but if you change it to return a widget it will not work either. That is the second problem, as you pointed out, your parent widget is not been rebuild when the value change, in this case you cannot avoid the setState (or other way to tell flutter that needs to rebuild the widget) when the value change because you need to rebuild the widget to view the change.
Another thing that ill like to point out is that TextEditingController is much powerful and it can be used for more things that just add notifiers to changes. For example if you want a button on other part of the screen that clear the text on a TextField you will need a TextEditingController binded to that field.
Hope it helps!
In the flutter_flux example when we commit a new message, the _currentMessage is emptied but the TextField does not reflect that changes.
This is the code in the store:
triggerOnAction(commitCurrentMessageAction, (ChatUser me) {
final ChatMessage message =
new ChatMessage(sender: me, text: _currentMessage);
_messages.add(message);
_currentMessage = '';
});
The view uses a TextEditingController as a controller for the TextField Widget so I understand why it is not updated.
How can we empty the TextField from the Store with flutter_flux?
EDIT: The flutter_flux example has been updated since I posted this answer, and it now correctly discards message in the TextField but in a better way. You should check it out.
I think the correct way would be to move the TextEditingController to the ChatMessageStore, instead of simply keeping the currentMessage in that store. Then you would be able to empty the text field by calling clear() on the TextEditingController.
Generally speaking, the state values which would normally be kept in FooState in vanilla flutter would go into a Store when using flutter_flux. Since you would normally create and keep a TextEditingController in a State, I think it's more natural to keep it in a Store anyways.
The updated ChatMessageStore would look something like this:
class ChatMessageStore extends Store {
ChatMessageStore() {
triggerOnAction(commitCurrentMessageAction, (ChatUser me) {
final ChatMessage message =
new ChatMessage(sender: me, text: currentMessage);
_messages.add(message);
_msgController.clear();
});
}
final List<ChatMessage> _messages = <ChatMessage>[];
final TextEditingController _msgController = new TextEditingController();
List<ChatMessage> get messages =>
new List<ChatMessage>.unmodifiable(_messages);
TextEditingController get msgController => _msgController;
String get currentMessage => _msgController.text;
bool get isComposing => currentMessage.isNotEmpty;
}
Note that we no longer need the setCurrentMessageAction, as the TextEditingController would take care of the text value change itself.
Then, the msgController defined in ChatScreen widget could be removed and the _buildTextComposer could be updated accordingly.
Widget _buildTextComposer(BuildContext context, ChatMessageStore messageStore,
ChatUserStore userStore) {
final ValueChanged<String> commitMessage = (String _) {
commitCurrentMessageAction(userStore.me);
};
ThemeData themeData = Theme.of(context);
return new Row(children: <Widget>[
new Flexible(
child: new TextField(
key: const Key("msgField"),
controller: messageStore.msgController,
decoration: const InputDecoration(hintText: 'Enter message'),
onSubmitted: commitMessage)),
new Container(
margin: new EdgeInsets.symmetric(horizontal: 4.0),
child: new IconButton(
icon: new Icon(Icons.send),
onPressed:
messageStore.isComposing ? () => commitMessage(null) : null,
color: messageStore.isComposing
? themeData.accentColor
: themeData.disabledColor))
]);
}
Hope this helps.