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.
Related
I want to know how to make a validation in flutter where IF a TextFormField is filled then when you hit "send" then it doesn't let you go to the next section until all other textformfields must be filled, BUT if one of the TextFormFields is NOT filled when you hit send then it lets you pass to the next section. This is for a job form where a section is NOT mandatory, but only if one field has been filled then it becomes mandatory.
If you have a Form widget that contains all your FormFields (not only text-ones, but also dropdowns and such), the validation occurs on all your fields at once if you write your submit code this way:
final _formKey = GlobalKey<FormState>();
var tecUser = TextEditingController();
var tecPwd = TextEditingController();
[...]
//inside your widget tree...
Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: tecUser,
validator: (value) {
//your validation code: return null when value is right
//or a string if there's some error
},
decoration: InputDecoration(hintText: "username".tr()),
),
const SizedBox(height: 10),
TextFormField(
controller: tecPwd,
validator: (value) {
//your validation code: return null when value is right
//or a string if there's some error
},
obscureText: true,
),
const SizedBox(height: 10),
OutlinedButton(child: const Icon(Icons.login), onPressed: () => _submit()),
[...]
void _submit() async {
if (_formKey.currentState!.validate()) {
//all validators returned null, so you can proceed with your logic
} else {
//this happens when at least one of the validators returned a string
//by default, the error string returned by the validators will be displayed
//near each field, so you won't have to worry about handling the error cases and the else here won't even be necessary
}
}
This is an excerpt from an actual login form.
EDIT:
Ok, now I understand what you want to do. You have a group of fields that aren't mandatory, but they instead are mandatory if at least one of them has some value.
You need to assign a different TextEditingController to each of this fields: then, you need to assign a validator to each FormField that does something like this:
//insert all the TextEditingController in a list
var tecList = <TextEditingController>[tec1, tec2...]
//then, set a validator like this
(value) {
bool notMandatory = true;
for (var tec in tecList){
notMandatory = notMandatory && tec.text.isEmpty;
}
if (!notMandatory) return "Fill all fields";
//some other validation here
}
If you use a TextEditingController you can use the .text.isNotEmpty statement an write yourself a litte if function to check everything.
TextEditingController controller = TextEditingController();
if (controller.text.isNotEmpty) {
print("have fun with your new job")
}
i am creating very easy app - user will get 4 texfields and he will put there numbers. Then i want to do some math on that numbers.
How is it possible to save that inputs in variables which i could use wherever i want and in relevant moment?
For now i only created possibility to display it on this class where they were created:
my TextFields look like this (i have 4 textfields: e, f, g and h:
var e = '';
TextField(
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onChanged: (newVal) {
e = newVal;},),
And this is button - when i click it i can see inputs
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('You wrote $d $e $f $g'),
);
},
);
But how to save that inputs to variables outside this class?
You can save the value from onChanged to a state management solution like StatefulWidget or Providers and so on.
TextField(
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onChanged: (newVal) {
if(newVal != null){
context.read<SignUpBloc>().add(SignUpEvent.mobileChanged(mobile: newVal));
}
},),
This is how we do it in Bloc. You can do the same in any other state management solution or even store it locally in SharedPreferences as well.
First you need to create a TextEditingController inside you class for each field you want to change:
class _MyClassWithDataState extends State<MyClassWithData> {
TextEditingController textController1 = TextEditingController();
TextEditingController textController2 = TextEditingController();
TextEditingController textController3 = TextEditingController();
TextEditingController textController4 = TextEditingController();
}
and define on the other class the data you will need:
class ClassWithReceivedData extends StatefulWidget {
const ClassWithReceivedData({
Key? key,
required this.text1,
required this.text2,
required this.text3,
required this.text4,
}) : super(key: key);
final TextEditingController text1;
final TextEditingController text2;
final TextEditingController text3;
final TextEditingController text4;
#override
State<ClassWithReceivedData> createState() =>
_ClassWithReceivedDataState();
}
then when you navigate to other page you simple pass:
Navigator.push(context, new MaterialPageRoute(builder: (context) => new
ClassWithReceivedData(text1: textController1, text2: textController2,
text3: textController3, text4: textController4);
but if you really need to retrieve data on multiple classes i suggest to create a Controller or a Store class for the data you need an then use an state managment like Provider, Get, Mobx, etc..., to retrieve data whatever you want.
I'm building an application for the company I'm working for in Flutter. We are using the MVVM (Model, View, ViewModel) architecture with the other developer I'm working with.
I would like to display user data from my ViewModel to my edit form (those data are fetched through our API).
The problem is: the data won't display to my view. I have access to it and I can print it (see screenshots below)...
What I tried so far :
I used initialValue primarily and called, for instance, my 'lastName' variable (but it doesn't show anything)
I tried using a controller for each field. With this method, it shows my user data but I then have a weird keyboard issue where each time I want to type some content, the cursor just goes to the start and deletes the word.
Also, I noticed that my variable can be displayed in a Text() widget.
I'm pretty clueless and I would really love to get an answer on this bug.
class MyAccountViewModel with ChangeNotifier {
String _lastName;
MyAccountViewModel() {
// this._lastName = 'Hardcoded text';
ApiHelper api = new ApiHelper();
api.getUserData().then((Map<String, dynamic>response) {
print(response);
this._lastName = response['last_name'];
});
notifyListeners();
}
String get lastName => this._lastName;
set lastName(String value) {
this._lastName = value;
notifyListeners();
}
Widget editProfileForm(model, BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
initialValue: model.lastName,
),
],
),
);
}
current view
response after the API call
Thanks to the answer I received in this post, I managed to find a working solution.
As advised in the previous comments, I needed to instantiate a controller and bind for instance "lastName" from my api response to controller.text.
Here is a sample code using the MVVM architecture :
class MyAccountViewModel with ChangeNotifier {
TextEditingController _lastNameController;
MyAccountViewModel() {
_lastNameController = new TextEditingController();
ApiHelper api = new ApiHelper();
api.getUserData().then((Map<String, dynamic> response) {
this._lastNameController.text = response['last_name'];
notifyListeners();
});
}
TextEditingController get lastName => this._lastNameController;
set lastName(TextEditingController value) {
this._lastNameController = value;
notifyListeners();
}
}
Widget editProfileForm(model, BuildContext context) {
return Form(
key: _formKey,
child: TextFormField(
controller: model.lastName,
),
);
}
#Metr0me, have you tried using controller to update the value? It could look something like this,
Initialize the controller as final lastNameController = TextEditingController();
Assign text value to the controller when you have your model instance as,
MyAccountViewModel model = new MyAccount.....
lastNameController.text = model.lastName;
setState(() {}); //Refresh if you need to
Assign lastNameController to your form field as,
child: Column(
children: <Widget>[
TextFormField(
controller: lastNameController,
),
],
)
Set the data to the text field
setState (() {
lastNameController.text = model.lastName ;
});
Assign Controller to your textform field
TextFormField(
controller: lastNameController,
),
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 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.