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.
Related
I'm trying to implement dark mode in my flutter app using provider, but I'm having some issues:
I have a home screen that it has a scaffold widget. The body of the scaffold is a Stack with two different classes, as you can see here:
#override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: Constant.menuOn
? BorderRadius.circular(60.0)
: BorderRadius.circular(0.0),
child: Scaffold(
body: Stack(
children: const [
Menu(),
HomeScreen(),
],
),
),
);
}
The home screen class has another Scaffold widget, where hos body has another class called body.
It is from the body where I'm able to change the theme of my app using a switch where I can set state my BODY class with the provider as you can see here:
lsetState(() {
final provider = Provider.of<ThemeProvider>(
context,
listen: false);
rovider.toggleTheme(Constant.isDarkMode);
});
My body class change the theme however the class that is under the body class (Remember that my class "MENU" is under the class BODY because the first stake), does not change theme until restart my app (I'm using the same parameters of the body in order to know when has to change the class)...
I want to know how I can set the theme state of my menu class, or in general how I can change any other state from any class.
Note: Both of my class are StatefulWidget.
In general, setState is a very local intra-widget operation. Local data has changed, so the local view needs updating. Wanting to call the setState of another widget is a bad code smell. If you need other views depending on data you've updated, consider one of the state management solutions. (I would, for example, use a Riverpod Provider to hold the data, then watch that in every dependent view.)
I have a widget with a list and a button with a tree dot icon in every row that shows and hides a panel in its own row. I only want one panel open in the list. When I click on a row button, I'd like to close the panels of the other rows list. All the buttons in the list are siblings. I'd like to send an event to the other rows' code to close the panels. Which is the correct manner of flutter?
I have tried NotificationListener but it does not work because the components to be notified are not their parents.
The question is if the correct thing to do is to use the event_listener library or to use streams. I'm new to flutter/dart and streams seem too complex to me. It's a very simple use case and in this entry
Flutter: Stream<Null> is allowed?
they say
*
Some peoples use streams as a flux of events instead of a value
changing over time, but the class isn't designed with this in mind.
They typically try to represent the following method as a stream:
So with simple events with 0 or 1 argument. event_listener or Streams?
This is the screen I'm working on. I want that when one yellow button panel opens the other one closes.
Your question is broad and it seems to be a design question, i.e. it doesn't have a right answer.
However, I don't think you should use Streams or EventListeners at all in this case, because you should not make components in the same layer communicate with each other. Components should only communicate with their parents and children, otherwise your code will increase in complexity really fast. That's even documented in flutter_bloc.
Other than that, if you don't lift state up, i.e. move the responsibility of triggering the removal of the other rows to a parent Widget, than you're fighting against Flutter instead of letting it help you.
It's easy to create a parent Widget, just wrap one Widget around it. What you want to do is hard, so why would try to communicate with sibling widgets instead of using what's Flutter designed to do?
This is a suggestion:
class _NewsSectionState extends State<NewsSection> {
Widget build(BuildContext context) {
return ListView.builder(
itemCount: newsInSection.length;
itemBuilder: (_, int index) => NewsTile(
title: Text('${newsInSection[index].title}')
onDismiss: () => onDismiss(index),
// I don't know how you set this up,
// but () => onDismiss(Index)
// should animate the dismiss of the Row with said index
),
);
}
}
class NewsRow extends StatefulWidget {
final void Function() onDismiss;
#override
State<NewsRow> _createState => _NewsRowState();
}
class _NewsRowState extends State<NewsRow> {
Widget build(BuildContext context) {
return Row(
children: [
// title
// home button
// fav button
// remove button
IconButton(
Icons.close,
onPressed: widget.onDismiss,
),
],
);
}
}
I'm using this reorderables package. This package works by having a list of children widgets that are each wrapped with a Draggable and put inside a DragTarget. Before that the childs key is assigned to a GlobalObjectKey.
After the dragTarget is created, it is assigned(or rebuild?) to a KeyedSubTree:
dragTarget = KeyedSubtree(key: keyIndexGlobalKey, child: dragTarget);
According to the comments in the package source code, this should preserve the child widgets state (toWrap) when being dragged:
// We pass the toWrapWithGlobalKey into the Draggable so that when a list
// item gets dragged, the accessibility framework can preserve the selected
// state of the dragging item.
final GlobalObjectKey keyIndexGlobalKey = GlobalObjectKey(toWrap.key);
The reordering itself happens not with the DragTarget accepting the Draggable dragged into it, but rather by using the DragTarget around each child to get index of the current position the Draggable is hovering over. When the Draggable is let go, a reorder function will get called, which removes the widget (that was being dragged) from the list and inserting it into the new position.
Now comes my problem: The state of the widget is not being preserved. I made a simple TestWidget to test this:
class TestWidget extends StatefulWidget{
#override
_TestWidgetState createState() => _TestWidgetState();
}
class _TestWidgetState extends State<TestWidget> {
Color boxColor;
#override
void initState() {
super.initState();
boxColor= Colors.blue;
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Container(
decoration: BoxDecoration(color: boxColor),
child: Text("Test"),
),
FlatButton(
onPressed: (){
setState(() {
boxColor = Colors.red;
});
},
padding: EdgeInsets.all(8.0),
child: Text("Change to Red"),
color: Colors.grey,
)
],
);
}
}
This widget has a Container with a initial blue background (boxColor) and a button. When the button is pressed, it will change the boxColor to red. The moment the dragging on the widget is initiated, it is rebuild and defaults to the initial state (at least the Draggable feedback is). After the reordering that doesn't change and the widget is still in it's default state.
My plan here is to have a list of different custom widgets, where the User can modify their content and if they are not happy with the order, they can drag those widgets around and rearrange them.
My question is: How do I preserve the state of my widgets?
I'm thinking of creating a class for each widget with all state relevant variables and use that to build my widgets but that seems very bloated and not really in the mind of flutter. Isn't that supposed to be the role of the state of the StatefulWidget?
EDIT:
So I solved my problem by creating an additional class for my widget state with ChangeNotifier and then moving all my variables that I want to keep track of into this class. So I basically now have two lists, one for my widgets in the reorderable list and one for their states. I still think that this is kinda scuffed. If a widget in my list has additional children of its own, I would need to create separate state classes for each of them that need it and save them somewhere. This can get very messy, very quickly.
Let's say, I have a chat screen that looks like this.
Now, when the user clicks the "Press when ready" button, the method fetchNewQuestion() is called.
My intention is that this will make a HTTP request, and display the result using
_buildUsersReply(httpResponse);
But, the problem is that this return must be made inside the current scaffold's widget as a child under the existing children, so that it is built at the bottom with the previous ones still there. The result would be like this:
You can find my complete code here.
Is this possible to be done pro-grammatically? Or do I have to change the concept of how I do this?
[Update, I now understand that my approach above is wrong and I have to use a listview builder. CurrentStatus below shows my progress towards achieving that goal.]
Current status:
I have built a list of Widgets:
List<Widget> chatScreenWidgets = [];
And on setState, I am updating that with a new Widget using this:
setState(() { chatScreenWidgets.add(_buildUsersReply("I think there were 35 humans and one horse.")); });
Now at this point, I am not sure how to pass the widget inside the scaffold. I have written some code that does not work. For instance, I tried this:
Code in the image below and in the gist here:
Just for future reference, here is what I really needed to do:
1. Create a list of widgets
List<Widget> chatScreenWidgets = [];
2. Inside my method, I needed to use a setState in order to add elements to that list. Every widget I add to this will be displayed on ths Scaffold.
`setState(() {
chatScreenWidgets.add(_buildUsersReply("Some Text"));
});`
3. And then, load that inside my Scaffold, I used an itemBuilder in order to return a list of widgets to my ListView. I already had that ListView (where I was manually adding children). Now this just returns them through the setState method inside my business logic method (in this case, fetchNewQuestion()).
body: Stack(
children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: 0),
child: new ListView.builder(
physics: BouncingScrollPhysics(),
padding: EdgeInsets.symmetric(horizontal: 25),
itemCount: chatScreenWidgets.length,
itemBuilder: (BuildContext context, int itemCount) {
return chatScreenWidgets[itemCount];
}
),
),
],
),
);`
I hope this helps future flutter engineers!
forget about the scaffold the idea is about what you really want to change, lets say it is
a list and your getting the data from an array if you update the array, then the list will update,if it is another type widgets then you can handle it in a different way i will edit this answer if you clarify what each part does in your widget as i cant read your full code.
first you have to create an object with two attributes one is the type of the row(if it is a user replay or other messages) and the second attribute is the string or the text.
now create a global list in the listview class from the above object, so you get the data from the user or even as a news and you create a new object from the above class and add your data to it and add it to the list.
item builder returns a widget so according to the the widget that you return the row will be set , so according to the data in the object call one of your functions that return the views like _buildUsersReply(string text).
if you have any other question you can ask :) if this what you need please mark it as the answer.
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.