I have a collection of simple objets that sometimes changes. I am using a ListView to render those objects, basically text. When my collection changes the list is rebuild with the new objects, so if the list changes from 1 to 3 items, I see 3 items, but the first one keeps its previous value.
I've noticed that the method "createState" is not called in all cases when I create a new CustomTextField (in the example above, it is called only when new elements are added to the list).
How do I make sure my list is updated properly when my collection changes?
My parent widget builds a list of text fields:
...
#override
Widget build(BuildContext context) {
...
var list = <Widget>[];
collection.forEach((item) {
var widget = CustomTextField(
content: item,
);
list.add(widget);
...
return new ListView(
children: list,
);
});
...
My CustomTextField definition:
class CustomTextField extends StatefulWidget {
final MediaContent content;
CustomTextField({
Key key,
this.content,
}) : super(key: key);
#override
CustomTextFieldState createState() {
return CustomTextFieldState();
}
}
...
MediaContent is a very simple object containing some text:
class MediaContent {
String textData;
ContentType type;
MediaContent(
this.type,
);
}
You have to define unique Key for you collections items, take a look at here:
https://www.youtube.com/watch?v=kn0EOS-ZiIc
Related
I have a Page that contains a PageView and this PageView contains a GridView, and this GridView contains Text. Right now I want to update only one Text, not all the Text, how should I do without reloading all the PageView? For example, right now I only have a Text(), I want to make a widget out of this. How should I do that so I can update the specific Text and not reload all PageView and its children?
PageView.builder(itemBuilder: (context, index) {
return GridView.builder(gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, ), itemBuilder: (context, index) {
return Text('$index');
});
}, itemCount: 3,),
)
My answer for you: unnecessary. Because GridView.builder cache your widget. You can change your text comfortably and setState in current Page. GridView will build the widgets have changed.
It depends how you get your updated data.
You can customize the GridView Item with a customized Widget, and call setState in that widget.
But you need a way to know when the data been updated.
class YourGridViewCell extends StatefulWidget {
final String label;
const YourGridViewCell({this.label});
#override
State<StatefulWidget> createState() => _YourGridViewCellState();
}
class _YourGridViewCellState extends State<YourGridViewCell> {
String _label;
#override
void initState() {
super.initState();
_label = widget.label;
}
#override
Widget build(BuildContext context) {
return Text(widget.label);
}
// you can call this to update cell.
void _updateText(String newLabel) {
setState(() {
_label = newLabel;
});
}
}
I have the following variables
String _warningMessage;
bool _warningVisibility;
Which I want to update via a Class which implements an interface
class _UserSignupInterface extends _SignupSelectUsernamePageState
implements UserSignupInterface {
#override
void onSuccess() {
_hideWarning();
_navigateToUserPage();
}
#override
void onError(String message) {
_isSignupClickable = true;
if(message != null) {
_displayWarning(message);
}
}
}
with the _displayWarning code (which is inside the _SignupSelectUsernamePageState)
void _displayWarning(String message) {
if (message != null) {
setState(() {
widget._warningMessage = message;
widget._warningVisibility = true;
});
}
}
However, whenever I call the _displayWarning(message) from outside the _SignupSelectUsernamePageState. I get an error saying
Unhandled Exception: setState() called in constructor
Is there a proper way of updating these variable states outside their class? Which in my case, I'm calling the _displayWarning(message) from another class that implements an interface
You have to decide whether this is a value that is changed internally within the widget, or if that's a value that changes externally to it.
If it's internal, the common thing is to place them in the State class with the _ on them, they could start with a value for instance set on initState and every time they change you call setState to indicate that.
However, if they change outside the widget, then you place them on the StatefulWidget class (as you seem to have done), you leave them without the _ as they are actually public and you even make them final and place them in the constructor to allow them to be set.
In this last case, if in the State class you must be aware of a change in the widget, you can implement didUpdateWidget, but that's not mandatory.
Of course you can mix both things, having a _warningMessage in the State, so you can update it with setState, but with an initial value defined in initState that comes from the widget.
Again, if the widget changes externally, you can again update the value of the _warningMessage with the new widgets value.
Something like that: (I didn't test this code)
class YourWidget extends StatefulWidget {
YourWidget({this.warningMessage});
final String warningMessage;
#override
State<YourWidget> createState() => new _YourWidgetState();
}
class _YourWidgetState extends State<YourWidget> {
String _warningMessage;
#override
void initState() {
super.initState();
_warningMessage = widget.warningMessage;
}
#override
didUpdateWidget(ReorderableListSimple oldWidget) {
super.didUpdateWidget(oldWidget);
_warningMessage = widget.warningMessage;
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text(_warningMessage),
RaisedButton(
child: Text("Change Message"),
onPressed: () {
setState(() {
_warningMessage = "new message from within the State class";
});
}
)
],
);
}
}
So in this example you can change the warningMessage externally, like in the parent Widget you are able to pass a different message. However, if you need, you can also set it internally using setState, as it's happening in the button's onPressed.
What you might check is wether you actually need that property exposed in the Widget, maybe you don't! Then, the example would look like that:
class YourWidget extends StatefulWidget {
#override
State<YourWidget> createState() => new _YourWidgetState();
}
class _YourWidgetState extends State<YourWidget> {
String _warningMessage;
#override
void initState() {
super.initState();
_warningMessage = "default message, no need for widget";
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text(_warningMessage),
RaisedButton(
child: Text("Change Message"),
onPressed: () {
setState(() {
_warningMessage = "new message from within the State class";
});
}
)
],
);
}
}
Just create a static value in the state of your widget class, then when you build the widget, set it's value to the widget. So whenever you want to call it to setState(), just call the static value.
The Flutter documentation for InheritedWidget says
Base class for widgets that efficiently propagate information down the tree.
To obtain the nearest instance of a particular type of inherited widget from > a build context, use BuildContext.inheritFromWidgetOfExactType.
Inherited widgets, when referenced in this way, will cause the consumer
to rebuild when the inherited widget itself changes state.
Given that widgets in Flutter are immutable, and in the example code..
class FrogColor extends InheritedWidget {
const FrogColor({
Key key,
#required this.color,
#required Widget child,
}) : assert(color != null),
assert(child != null),
super(key: key, child: child);
final Color color;
static FrogColor of(BuildContext context) {
return context.inheritFromWidgetOfExactType(FrogColor);
}
#override
bool updateShouldNotify(FrogColor old) => color != old.color;
}
the color property is final so cannot be reassigned. Assuming this widget is right at the top of the tree, as in most examples, when will this ever be useful. For the widget to be replaced, a new instance will have to be created.
Presumably where this is done, a new instance of whatever is passed as child will be created too, causing that child's descendants to also rebuild, creating new instances of its childresn etc..
Ending up with the whole tree rebuilt anyway. So the selective updating applied by using inheritFromWidgetOfExactType is pointless, when the data of an instance of InheritedWidget will never change for that instance?
Edit:
This is the simplest example of what I don't understand that I can put together.
In this example, the only way to "change" the InheritedWidget/FrogColor which is near the root of the application is to have its parent (MyApp) rebuild. This causes it to rebuild its children and create a new instance of FrogColor and which gets passed a new child instance. I don't see any other way that the InheritedWidget/FrogColor
would change its state as in the documentation
... will cause the consumer to rebuild when the inherited widget itself changes state.
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(MyApp());
}
class FrogColor extends InheritedWidget {
const FrogColor({
Key key,
#required this.color,
#required Widget child,
}) : assert(color != null),
assert(child != null),
super(key: key, child: child);
final Color color;
static FrogColor of(BuildContext context) {
return context.inheritFromWidgetOfExactType(FrogColor);
}
#override
bool updateShouldNotify(FrogColor old) => color != old.color;
}
class MyApp extends StatefulWidget {
// This widget is the root of your application.
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp>
{
#override
Widget build(BuildContext context) {
var random = Random(DateTime.now().millisecondsSinceEpoch);
return FrogColor(
color : Color.fromARGB(255,random.nextInt(255),random.nextInt(255),random.nextInt(255)),
child:MaterialApp(
title: 'Flutter Demo',
home: Column (
children: <Widget>[
WidgetA(),
Widget1(),
FlatButton(
child:Text("set state",style:TextStyle(color:Colors.white)),
onPressed:() => this.setState((){})
)
]
)
)
);
}
}
class WidgetA extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Ran Build ${this.runtimeType.toString()}");
return WidgetB();
}
}
class WidgetB extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Ran Build ${this.runtimeType.toString()}");
return Text("SomeText",style:TextStyle(color:FrogColor.of(context).color));
}
}
class Widget1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Ran Build ${this.runtimeType.toString()}");
return Widget2();
}
}
class Widget2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Ran Build ${this.runtimeType.toString()}");
return Text("SomeText",style:TextStyle(color:FrogColor.of(context).color));
}
}
Further, the output of this is
I/flutter (24881): Ran Build WidgetA
I/flutter (24881): Ran Build WidgetB
I/flutter (24881): Ran Build Widget1
I/flutter (24881): Ran Build Widget2
So all the child widgets are always rebuilt. Making the registration done in inheritFromWidgetOfExactType pointless also.
Edit2:
In response to #RĂ©miRousselet answer in the comments, modifying the above example, something like
class MyAppState extends State<MyApp>
{
Widget child;
MyAppState()
{
child = MaterialApp(
title: 'Flutter Demo',
home: Column (
children: <Widget>[
WidgetA(),
Widget1(),
FlatButton(
child:Text("set state",style:TextStyle(color:Colors.white)),
onPressed:() => this.setState((){})
)
]
)
);
}
#override
Widget build(BuildContext context) {
var random = Random(DateTime.now().millisecondsSinceEpoch);
return FrogColor(
color : Color.fromARGB(255,random.nextInt(255),random.nextInt(255),random.nextInt(255)),
child: child
);
}
}
works by storing the tree that shouldn't be modified outside of the build function so that the same child tree is passed to the InhertedWidget on each rebuild. This does work only causing the rebuild of the widgets that have registered with inheritFromWidgetOfExactType to get rebuilt, but not the others.
Although #RĂ©miRousselet says it is incorrect to store the subtree as part of the state, I do not believe there is any reason that this is not ok, and infact they do this in some google tutorial videos. Here She has a subtree created and held as part of the state. In her case 2 StatelessColorfulTile() widgets.
Presumably where this is done, a new instance of whatever is passed as a child will be created too, causing that child's descendants to also rebuild, creating new instances of its children etc..
Ending up with the whole tree rebuilt anyway.
That's where your confusion comes from
A widget rebuilding doesn't force its descendants to rebuild.
When a parent rebuild, the framework internally check if newChild == oldChild, in which case the child is not rebuilt.
As such, if the instance of a widget didn't change, or if it overrides
operator== then it is possible for a widget to not rebuild when its parent is updated.
This is also one of the reasons why AnimatedBuilder offer a child property:
AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Container(child: child,);
},
child: Text('Hello world'),
);
This ensures that when for the whole duration of the animation, child is preserved and therefore not rebuilt. Leading to a much more optimized UI.
I'm trying to create a tile for my listview. While the tile is working when placed within the listview file, I'm wondering how would I go about encapsulating the tile widget into its own class file.
Specifically, if the tile object does not take an argument, I can simply extend a stateless widget and call upon the build method to return a new tile object.
But if the tile object is to be created with arguments (i.e. custom text), how do I pass this information along? Or would it be better to leave the widget in the listview class itself?
Example:
class Tile extends StatelessWidget {
#override
Widget build(BuildContext context){
return _tile(); //Error, How do i pass the arguments?
}
Widget _tile(String text, String time) {
return new Align(
child: new Container(
// padding: EdgeInsets.all(5.0),
...
I think you can simply create a constructor and use it
import 'package:flutter/material.dart';
class Tile extends StatelessWidget {
final String text;
final String time;
/// Here is your constructor
Tile({Key key, this.text, this.time});
#override
Widget build(BuildContext context) {
return _buildTitle(this.text, this.time);
}
Widget _buildTitle(String text, String time) {
return new Align(
child: new Container(
// padding: EdgeInsets.all(5.0),
));
}
}
Generally when creating a widget constructor you also add a Key and call super. Variables should also be marked final since widgets are immutable.
class Tile extends StatelessWidget {
// make these final
final String text;
final String time;
// constructor
const Tile({Key key, this.text, this.time}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListTile(
// ...
);
}
}
And call it like this:
Tile(text: 'hello', time: '5:30');
It is so common to create custom constructors that there is even a shortcut for it in Android Studio.
Write the names of your final variables.
Put your cursor on them and press Option+Retern (or Alt+Enter).
Image source here.
Despite flutter calling build (and printing the correct information as below), it doesn't seem to build new TaskWidgets (the print in TaskWidgetState's constructor is not called). This is creating some unusual behaviour in my application (for example, the persistence of deleted ListView items).
I have the following code:
class TaskWidget extends StatefulWidget {
TaskWidget({this.task, this.callToSave, this.callToDelete});
final Task task;
final Function callToSave;
final Function callToDelete;
#override
State<StatefulWidget> createState() {
return new TaskWidgetState(task, callToSave, callToDelete);
}
}
class TaskWidgetState extends State<TaskWidget>{
Task task;
Function toCallOnChange;
Function callToDelete;
TaskWidgetState(Task task, Function callToSave, Function callToDelete){
print("I'm a task widget for " + task.serialise().toString());
this.task = task;
toCallOnChange = callToSave;
this.callToDelete = callToDelete;
}
}
and
class ToDoListWidget extends State<ToDoList>{
List<Task> _toDo = new List<Task>();
...
#override
Widget build(BuildContext context) {
print("building");
return new Scaffold(
body: new ListView(
children: <Widget> [
generateCard(),
...
]
),
);
}
Widget generateCard() {
return new Card(
child: new Column (
children: generateWidgets()
),
...
);
}
List<Widget> generateWidgets() {
print("generating Widgets");
List<Task> tasks = getTasks();
List<Widget> widgets = new List<Widget>();
print("I have " + tasks.length.toString() + " widgets to build");
for(Task t in tasks) {
print(t.title);
TaskWidget widget = new TaskWidget(task: t, callToSave: saveList, callToDelete: deleteTask,);
widgets.add(widget);
}
return widgets;
}
}
Prints out:
building
I/flutter (28783): Returning for Daily
I/flutter (28783): // correct, undeleted task
but onscreen state doesn't reflect this
You're not using State and Stateful Widget properly.
How it works in flutter is that the Widget can be created many times, but there will most likely only be one instance of a State to go along with it.
It's a bit of an anti-pattern to have a constructor for a state.
Instead you should be doing something like this:
class TaskWidget extends StatefulWidget {
TaskWidget({this.task, this.callToSave, this.callToDelete});
final Task task;
final Function callToSave;
final Function callToDelete;
#override
State<StatefulWidget> createState() => new TaskWidgetState();
}
class TaskWidgetState extends State<TaskWidget>{
Widget build(Context context) {
// you can just use the widget.task, this is to illustrate.
var task = widget.task;
var callToSave = widget.callToSave;
var callToDelete = widget.calltoDelete;
}
}
This way, when the widget changes, your state will be re-built and will use whatever the updated values are that were passed into the widget.