I try to understand how MobX observes chages. For example:
I have an object called User where I store user data. It is used in multiple places using GetIt singletons. I have general app header where i am observinf user.name by doing this:
Observer(builder: (_) => Text(userStore.user.name))
and I have page with form where I am editting user info. Changing name looks like:
Observer(
builder: (_) => TextFormField(
initialValue: _userStore.user.name,
decoration: InputDecoration(
labelText: 'Name'),
onChanged: (value) => _userStore.user.name = value,
))
So theoreticlly it should not observe shanges because reffrence of User did not change. Theoretically I suppose to use ObservableMap to do this. But this works... Why?
Also when I try to add reaction on user object or on user.name reactions do not run.
So maybe someone can exmplain me how to run actions on complex structures like maps inside of map?
So at the end of the day I was able to grasp information on how MobX works.
user.name on the header was refreshing because whole page was relbuilding
user object will trigger change if referenc eof object changes
user.name will change if it will be marked as #observable
Related
When saving data to the Firebase database I want every data to be saved according to its data type. But my all data is stored in String. How can I do it in flutter... like Amount will be int, Through will be String
[
here is my function
sandDataToDB() async {
CollectionReference _collectionReferance =
FirebaseFirestore.instance.collection("Use-of-fund");
return _collectionReferance
.doc()
.set({
"Details of Sector": _DetailsofSectorController.text,
"Through": _ThroughController.text,
"Amount": _AmountController.text,
"Date": _DateController.text,
})
.then((value) => dialog())
.catchError((error) => Fluttertoast.showToast(msg: "something wrong"));
}
here is all code
customAdminTextField(
"Details of Sector", _DetailsofSectorController),
customAdminTextField("Through", _ThroughController),
customAdminTextField("Amount", _AmountController),
customAdminTextField("Date", _DateController),
const SizedBox(
height: 10,
),
customSubmitButton("Submit ", () {
sandDataToDB();
})
You have to parse your data. An example for an integer:
...
"Amount": int.parse(_AmountController.text),
...
Here you can find the supported data types.
According to your question, you might be using the controller in TextFormField, you can get the value of the TextFormField.
This widget covers a TextField widget in a FormField for convenience.
It is not necessary to have a Form ancestor. The Form merely simplifies the process of saving, resetting, or validating numerous fields at the same time. To use without a Form, supply a GlobalKey to the constructor and save or reset the form field with GlobalKey.currentState.
TextEditingController.text defines the initialValue when a controller is defined. A controller should be given if this FormField is part of a scrolling container that generates its children lazily, such as a ListView or a CustomScrollView.
A stateful widget ancestor of the scrolling container should handle the controller's lifetime.
Example code:
TextFormField(
decoration: const InputDecoration(
icon: Icon(Icons.person),
hintText: 'What do people call you?',
labelText: 'Name *',
),
onSaved: (String? value) {
// This optional block of code can be used to run
// code when the user saves the form.
},
validator: (String? value) {
return (value != null && value.contains('#')) ? 'Do not use the # char.' : null;
},
)
You can also use the onSubmitted parameter in TextFormField. Like: onSubmitted: (String value)
Additionally, you can use something like this in the following code:
_formKey.currentState.save(); calls the onSaved() on each textFormField item, which gives all the fields a value and allows you to utilize them as needed.
I am using a package called responsive_builder to create a web responsive UI.
My issue is that this package rebuilds the widget that corresponds to the constraints after changing window size.
This causes the loss of user data if inserted in a TextField for example. Also this causes PageView to lose its state meaning losing the current page index if the page was change earlier before adjusting window size.
Any suggestions?
I have had a very similar issue happen with a closing/opening container with a Textfield.
You need to use Shared Preferences to store that data into the memory. Shared Preferences effectively keeps that typed data in a map - which is stored in memory and can be used anytime.
Also I highly recommend using Flutter Form Builder for your text fields as it allows you to have a initialValue in your TextField and has built in Validation.
Because there is no code to go off, your Textfield should look something like:
SharedPreferences prefs = await SharedPreferences.getInstance();
String firstName = '';
_persistantText(String value) async {
firstName = await prefs.setString(value);
}
FormBuilderTextField(
attribute: "firstName",
decoration: InputDecoration(labelText: "First Name"),
validators: [
FormBuilderValidators.required(),
FormBuilderValidators.max(70),
],
initialValue: firstName
onChanged: (value){
_persistantText(value);
}
),
I have a question about flutter lifecycle.
Something strange is happening since I updated from flutter 1.7 to 1.9 yesterday. My forms that were working perfectly started to act weird.
I have a stateful widget, which if given no object instantiates a new one in its constructor, so that the user can add a new object or use this widget to edit an existing object. something like this:
final MyObject object;
MyForm() : this.object = MyObject(name: "", origin: Origin.EARTH);
MyForm.edit(this.object);
Name is a simple string and origin is an enum.
In the form state I have a global key: final _formKey = GlobalKey<FormState>();
Later in the code I have a field name, and a dropdown with the different values of the origin.
TextFormField(
initialValue: widget.object.name,
onSaved: (value) {widget.object.name = value;),
),
DropdownButtonFormField(
decoration: InputDecoration(labelText: "Origin"),
items: _originList,
value: widget._object.origin,
onChanged: (value) {
setState(() {
widget._object.origin = value;
});
},
onSaved: (value) {
widget._object.type = value;
},
),
So for instance when I change my dropdown value, to something like mars, it indeed selects mars in my object and it shows mars on the dropdown. (confirmed by debugger)
Then when I click on the name textfield to change the name of the object, the dropdown goes back to earth.
Now the really strange thing is, if I save my object... it saves it correctly ! (with mars and the name I chose)
I found out that for some reason (that I do not understand) the constructor of the form (not the form state, but the form) is called when I click on other fields of the form, which recalls my constructor MyForm() which sets up a new MyObject with the value earth...
But then why does it saves it correctly boggles my mind... If it would be consistent with this behavior it should save the object with earth selected and not mars...
Any idea on how this is happening ? (also I make it very clear nothing wrong was happening before the update to flutter 1.9 so it may be a bug in that new version...)
Also I am testing this with an iPhone simulator if that is of any help.
Thank you for any feedback !
if anyone is having the same issue as me, you can read how to fix it here: https://github.com/flutter/flutter/issues/27821
It is a temporary fix until flutter team fixes this issue on their framework !
Cheers !
I wonder is it possible to declare a widget then after that edit the properties in the widget. See example below:
InputDecoration temp = new InputDecoration(
labelText: label,
labelStyle: TextStyle(color: Colors.white),
// ...
);
and then
temp.suffixIcon = IconButton(icon: Icons.sth);
I can't seems to get it working as it return suffixIcon is final. Any help much appreciated.
No. That is not possible (or should be avoided as much as possible).
The reason for this is, the same widget can be inserted in multiple locations of the widget tree.
For example, it's totally reasonable to do:
Widget foo = Something();
return Row(
children: [
foo,
foo,
]
);
What you're trying to do is imperative programming:
You have a program state and whenever it changes, you update all the necessary UI elements manually.
Flutter is a reactive UI framework. Semi-mathematically speaking, that means there is some state s and a build method f and when the state changes, the framework builds the new widget subtree by calling your function f(s).
More specifically, you don't want to change the concrete attributes of your child widgets—you don't even want to be able to do that because that goes against the reactive paradigm. Rather change the state of the program and tell the framework that the state changed. It will then re-render all the child widgets with the new attributes.
Don't worry about performance too much, Flutter is heavily optimized in that regard.
Widgets with changing state are StatefulWidgets in Flutter. Every StatefulWidget has a corresponding State, which can also contain mutable (non-final) attributes like these:
bool _useFirstIcon = true;
When you change the state, use the setState function in the State like this to notify the framework about the change:
void _changeTheState() {
setState(() => _useFirstIcon = false);
}
That tells the framework to re-render that subtree. Basically, it means your build method will be called when the next frame is drawn.
There, you should return different widget trees depending on the state (your _useFirstIcon attribute).
return SomeWidget(
decoration: new InputDecoration(
suffixIcon: IconButton(
icon: _useFirstIcon ? Icons.someIcon : Icons.someOtherIcon,
onPressed: _changeTheState,
),
),
);
Note that sometimes, you do want to access the state of a widget from the parent widget. That is—arguably somewhat inelegantly—achieved by providing some sort of controller to the child widget and the child widget modifies the state of the controller to make parts of its own state accessible to the parent.
This architecture is for example used in the TextField using the TextController.
I am new to Flutter (and Dart) and when trying to build a form to edit an object I searched online for examples and tutorials, and I saw both of these used.
What is the difference between the two? Which one should I use?
If you making a Form where you require save, reset, or validate
operations- use TextFormField. Else For Simple user input capture
TextField is sufficient.
TextFormField, which integrates with the Form widget.
This is a convenience widget that wraps a TextField widget in a FormField.
A Form ancestor is not required. The Form simply makes it easier to save, reset, or validate multiple fields at once.
To use without a Form, pass a GlobalKey to the constructor and use GlobalKey.currentState to save or reset the form field.
sample:
TextFormField(
autovalidateMode: AutovalidateMode.always
decoration: const InputDecoration(
icon: Icon(Icons.person),
hintText: 'What do people call you?',
labelText: 'Name *',
),
onSaved: (String value) {
// This optional block of code can be used to run
// code when the user saves the form.
},
validator: (String value) {
return value.contains('#') ? 'Do not use the # char.' : null;
},
)
TextField, which is the underlying text field without the Form integration.
The text field calls the onChanged callback whenever the user changes the text in the field. If the user indicates that they are done typing in the field (e.g., by pressing a button on the soft keyboard), the text field calls the onSubmitted callback.
Short answer
If you don't know what you need, then use a TextField. This is the most basic Flutter widget for getting text input from a user. It's the one you should master first.
TextField
Using a TextField is an easy way to allow user input.
TextField(
decoration: InputDecoration(
hintText: 'Name'
),
);
To get the text that the user entered, you can either get notified every time there is a change like this:
TextField(
decoration: InputDecoration(
hintText: 'Name'
),
onChanged: (text) {
// do something with text
},
),
Or you can use a TextEditingController, as described here. This will give you access to the text state.
TextFormField
If you find yourself needing to validate user text input before you save it, you might consider using a TextFormField. Imagine something like this:
There are lots of validation checks that you might want to do on a username and password.
Of course, you could still just use a couple TextFields, but TextFormField has extra builtin functionality that will make your life easier. Generally, you will only use a TextFormField when you are using it inside of a Form widget (though that isn't a strict requirement).
Here is a stripped down example from the documentation:
class MyCustomForm extends StatefulWidget {
#override
MyCustomFormState createState() {
return MyCustomFormState();
}
}
class MyCustomFormState extends State<MyCustomForm> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
validator: (value) {
// validation logic
},
),
RaisedButton(
child: Text('Submit'),
onPressed: () {
if (_formKey.currentState.validate()) {
// text in form is valid
}
},
),
],
),
);
}
}
See also
Realistic Forms in Flutter — Part 1
Forms with Flutter
TextField is a simple text field. (you don't care about user input)
TextFormField is a text field to be used in a form (you care about user input).
If you don't need to validate TextField.
If you need to validate user input, use TextFormField with validator.
TextFormField vs. TextField
TextFormField returns a TextField, but wraps the TextField with extra functionality you can use through a Form and also without (such as reset, validation, save, etc.).
Generally, you want to use TextFormField unless writing boiler-plate code is your thing.
How does TextFormField work? What can it do?
TextFormField extends FormField class, (a StatefulWidget).
FormField objects do a special thing when they are instantiated: they look up the widget tree for a Form and register themselves with that Form.
After registration, these FormField widgets can be changed by that parent Form.
Form is also a StatefulWidget. It has a FormState object.
FormState can get & set data on any/allFormFields registered to it.
For example to clear the entire form we can call reset() on FormState and FormState will iterate through all child FormFields registered, resetting each FormField to its initialValue (null by default).
Validation is another common use case for putting several FormField like TextFormField inside Form/FormState. This allows multiple fields to be validated with a single validate() call on the Form.
How? FormState has a validate() method that iterates through each registered FormField and calls that FormField's validate() method. Much more convenient calling validate() once on Form than you manually keeping track of all TextField and validating each one separately with custom code.
Details
How does a TextFormField register itself with a Form?
FormField (base class of TextFormField, etc.) make a call within their build() method:
Form.of(context)?._register(this);
In English this means:
Search up my context hierarchy until we find a Form widget (if any) and call that form's register method on myself.
The ? is in case there is no Form widget parent. The _register call will only be run if there is a Form & FormState somewhere above.
How does Form.of(context)?._register(this) work?
Form & FormState sneakily use InheritedWidget.
In FormState.build() you'll see this code:
return WillPopScope(
onWillPop: widget.onWillPop,
child: _FormScope( // ← sneaky
formState: this,
generation: _generation,
child: widget.child,
),
);
Looking at _FormScope we see:
class _FormScope extends InheritedWidget
When a parent widget is an InheritedWidget, any child can find that parent using a special "find me a parent of this exact Type" method.
Here's how that "find me" method is used/exposed inside Form as a static method we can call from anywhere:
static FormState of(BuildContext context) {
final _FormScope scope = context.dependOnInheritedWidgetOfExactType<_FormScope>();
return scope?._formState;
}
The naming of that method dependOnInheritedWidgetOfExactType is a bit tricky. It'd be more readable as findInheritedWidgetOfExactType, but it does more than just find. (We can trigger rebuilds of Form through children that have registered as dependOn this FormState).
Summary
TextFormField is the deluxe package, air-conditioned, Bluetooth-connected 8-speaker stereo version of TextField. It includes a lot of common functionality you would use when accepting user-entered information.
I think this might be the most concise and simple explanation about the differences between the two.
From the material library:
TextField: A material design text field.
TextFormField: A FormField that contains a TextField.
Similarly, you can wrap FormField around any cupertino input component such as CupertinoTextField
Below is an example about a custom CheckboxFormField, which is a FormField that wraps around the material design component Checkbox:
// A custom CheckboxFormField, which is similar to the built-in TextFormField
bool agreedToTerms = false;
FormField(
initialValue: false,
validator: (value) {
if (value == false) {
return 'You must agree to the terms of service.';
}
return null;
},
builder: (FormFieldState formFieldState) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Checkbox(
value: agreedToTerms,
onChanged: (value) {
// When the value of the checkbox changes,
// update the FormFieldState so the form is
// re-validated.
formFieldState.didChange(value);
setState(() {
agreedToTerms = value;
});
},
),
Text(
'I agree to the terms of service.',
style: Theme.of(context).textTheme.subtitle1,
),
],
),
if (!formFieldState.isValid)
Text(
formFieldState.errorText ?? "",
style: Theme.of(context)
.textTheme
.caption
.copyWith(color: Theme.of(context).errorColor),
),
],
);
},
),
Rule of thumb: If your box only have a single input field, just use the raw material input like TextField (FormField is a bit overkill in this case though). If your box has many input fields, you need to wrap each one of them in a FormField, and then integrate all of them to the Form widget to reap the benefits of validating and saving all form fields at once.
Extra tip: If u have a TextField wrapped in a FormField that doesn't allow the user to enter any text such as a CupertinoPickerFormField or a SimpleDialogFormField that offers the user a choice between several options (which is basically a material SimpleDialog widget wrapped in a FormField), just simply use the hintText param of InputDecoration without using TextEditingController to manipulate text. Make the hint text have the same color as the normal input text with hintStyle: const TextStyle(color: Color(0xdd000000)).
This video from Flutter Europe will help you master forms in Flutter in no time.