I am making this app, but am unsure of a good way to organize it. With the picture to demonstrate below:
Where (currently):
Blue: Stateless Widget => ListView, Rows Widgets
Red: Stateful Widget => Expanded Widgets
Green: Stateless Widget => Expanded Widgets
What the app needs to do: The digits need to update themselves whenever they change (I am not worried about fancy animations right now, just transitioning).
Problem: I found that the only way to update these digits is through Timer.periodic every minute. However, I am finding it hard to route this information. I currently have my periodic timer under each Red Expanded Widget, but there is a unique DateTime used in all of them. Redundant information is the problem with this approach. I could place a periodic timer in the Blue ListView, Rows Widget. However, every update would require a reload of all containing widgets, including the Green Expanded Widgets.
If there are any suggestions or different ways, anything would be appreciated.
Flutter's design means that it doesn't matter that you reload the green Widgets; the way it works makes that really inexpensive. So, drag your state up to a StatefulWidget containing all your text/columns/etc (which can now be stateless).
This example shows one way of pushing down a fragment of state (into the constructor of DurationPartWidget). I've shown that you could do the date mathematics in the build method. Equally, you could do it in setState and make years, months, etc instance variables of _AnniversaryWidgetState.
class AnniversaryWidget extends StatefulWidget {
final DateTime firstDate;
AnniversaryWidget(this.firstDate);
#override
State createState() {
return new _AnniversaryWidgetState();
}
}
class _AnniversaryWidgetState extends State<AnniversaryWidget> {
Timer _timer;
#override
void initState() {
super.initState();
_timer = new Timer.periodic(new Duration(seconds: 1), (Timer t) {
setState(() {});
});
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
DateTime now = new DateTime.now();
// todo - calculate these from now minus firstDate
int years = 0;
int months = 4;
int days = 13;
int hours = 21;
int minutes = now.difference(widget.firstDate).inMinutes % 60;
return new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new _DurationPartWidget('years', years),
new _DurationPartWidget('months', months),
new _DurationPartWidget('days', days),
new _DurationPartWidget('hours', hours),
new _DurationPartWidget('minutes', minutes),
],
),
);
}
}
class _DurationPartWidget extends StatelessWidget {
final int _numberPart;
final String _label;
_DurationPartWidget(this._label, this._numberPart);
#override
Widget build(BuildContext context) {
return new Row(
children: <Widget>[
new Text(_numberPart.toString().padLeft(2, '0')),
new Text(_label),
],
);
}
}
If, later, you want to bring you state even higher up your Widget tree, you could use an InheritedWidget. (I sometimes use one above MaterialApp.) Brian Egan gave an awesome talk at DartConf 2018 on this whole topic.
https://www.youtube.com/watch?v=zKXz3pUkw9A&list=PLOU2XLYxmsIIJr3vjxggY7yGcGO7i9BK5&index=10
Related
I have a simple Widget with a ValueListenableBuilder that listens to a ValueNotifier.
The build function of the ValueListenablebuilder is never triggered when updating the value of the ValueNotifier from a native method call (using setMethodCallHandler).
Instead, if I use valueNotifier.listen(() {}), I'm able to receive new values even in the build function. The native code, in fact, emits a new String each second.
This is the minimal code to reproduce the issue:
class Main extends StatelessWidget {
Main({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
platform.invokeMethod("startNativeMethod");
platform.setMethodCallHandler(handleNativeMethod);
Future.delayed(Duration(seconds: 3)).then((value) {
// this WORKS and updates the Text!
resourceNotifier.value = "test";
});
resourceNotifier.addListener(() {
// this ALWAYS works (prints "test" and then native updates each second)
print(resourceNotifier.value);
});
return ValueListenableBuilder(
valueListenable: resourceNotifier,
builder: (context, String value, child) {
// this is triggered only with "test"
// (when updated from Future.delayed)
return Container(child: Text(value ?? "Empty"));
});
}
ValueNotifier<String> resourceNotifier = ValueNotifier(null);
MethodChannel platform = const MethodChannel("com.example.example");
Future<dynamic> handleNativeMethod(MethodCall call) async {
// this NEVER updates the Text() widget. Always triggers the listener instead.
resourceNotifier.value = "hello from native";
}
}
This goes also beyond ValueNotifiers. I've also tried moving to a Stateful Widget and setting state from handleNativeMethod. Even setState doesn't work from there.
Thanks in advance for the help!
I am learning flutter and didupdatewidget showed up in tutorial. I can't really get what it does. I've made the application such that it adds cards to a column after clicking the button but didupdatewidget doesn't print anything after updating the list of cards(products class).
import 'package:flutter/material.dart';
import './products.dart';
class ProductsManager extends StatefulWidget {
final String startingproduct;
ProductsManager(this.startingproduct);
#override
State<StatefulWidget> createState() => ProductsManagerState();
}
class ProductsManagerState extends State<ProductsManager> {
List<String> _products = [];
#override
void initState() {
super.initState();
_products.add(widget.startingproduct);
print("InitState called");
}
#override
void didUpdateWidget(ProductsManager oldWidget) {
print("Updated the widget");
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
print("build of statefull PM");
return Column(
children: <Widget>[
Container(
margin: EdgeInsets.all(9.0),
child: RaisedButton(
color: Theme.of(context).primaryColor,
child: Text("data"),
onPressed: () {
setState(() {
print("Setstate called ");
_products.add("Advanced Food Tester");
});
},
),
),
Products(_products)
],
);
}
}
How does didupdatewidget work in showing the app was updated??
As far as I understood, calling 'setState()' inside a state doesn't make the state's didUpdateWidget() get called.
However, if the widget returned by this state in the 'build()' method is a StatefulWidget(or if there's any StatefulWidgets down the tree from this point), then 'didUpdateWidget()' in the States of those StatefulWidgets would be called.
To understand the reason, we could take a look at flutter's source code in framework.dart, and here's a rough summary of the code flow when calling 'setState()' in a State:
stateA(say) calls 'setState()'
In 'setState()', it calls "_element.markNeedsBuild()", which adds '_element' to the list of dirty elements who would be re-built later on by flutter( '_element' is simply the element object that StateA is binded to, for convenience let's name it elementA)
In the next frame flutter would go through the list of dirty elements, and for each element 'element', flutter calls element.rebuild(), so elementA.rebuild() would be called in the loop.
In elementA.rebuild(), it eventually calls "built = stateA.build()", then "updateChild(_child, built)" (where _child is elementA's next element down the tree, which this 'built' widget is binded to).
updateChild(_child, built) sets widget 'built' as the binded widget of element '_child', thus here is where the 'change of configuration of an element' happens, and hence calling 'didUpdateWidget()' inside element '_child'(if it is a StatefulElement)
For 'didUpdateWidget()', the flutter docs says that it is "Called whenever the widget configuration changes"
And the key idea is that 'setState()' in stateA does not rebuild widgetA, but rebuilds the widget returned by stateA. Therefore, the 'change of configuration' happens to the returned widget, whilst widgetA remains the same.
I have a scopedModel class where I fetch data into it. The thing is that I'm not being able to render this data from InitState method using my scoped model where I have all my api requests. The method is being called but the inside callings are not, so my initial state of the page is not properly shown.
void initState() {
print("Check initState");
super.initState();
ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
print("Get into the scoped model");
model.fecthCars();
model.fecthCities();
model.fecthBuys(model.getUserDto.token);
print(model.getBuys().length);
return;
});
}
None of the fetches(Api requests) get called. And the scopedModel returns a widget. I need this to be updated the first time I get into the manager and that's it. No need to call it again. Is this possible? or should I hardcode my api requests in each file I need?
UPDATE
If you have your scoped model class set up already you can set a Future like this inside of it
mixin MyModel on Model {
Future<TypeToReturn> methodName(String param) async {
Uri uri = new Uri.http('serverUrl', 'api/call');
return await http.get(uri).then((http.Response response) {
final List<dynamic> myResponse= json.decode(response.body);
return myResponse;
}).catchError((error) {
print(error);
});
}
}
Aftermards you can set up your FutureBuilder
Widget _buildBody(BuildContext context, MainModel model) {
return FutureBuilder(
future: model.methodName(someString), //This is the method name above
builder: (context, AsyncSnapshot<TypeToReturn> snapshot) { //type u return
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
if (snapshot.data.length == 0)
return Center(
child: Text(
"No Data Found",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16.0,
),
),
);
return (create your own widget with the data inside the snapshot)
}
},
);
}
Hope this clarify things a little bit more on how I did it.
I stumbled upon the following solution:
In the State Class of my StatefulWidget I do:
#override
void initState() {
super.initState();
// and here...
MyModel model = ScopedModel.of(context);
// now I can do with the model whatever I need to do:
Text someVar = model.initialText;
model.getValuesFromSomewhere();
// and so on
}
This, in my opinion, is the easiest way of solving the problem as stated by the original Question.
I think you've a slight misunderstanding about the point of ScopedModel and ScopedModelDescendant. The basic idea of how these should work is that the ScopedModel is created with a valid model which can then be used in other parts of the app.
However, the ScopedModelDescendant should be used within the build() function of a one of your widgets and be part of the widget tree as well. The reason your fetch methods aren't being called is that it isn't in the widget tree so the build function will never be called.
I would recommend instead moving the fetch methods out of the model and into some other class (maybe call it a communicator or controller or something). Next, I'd make it so that the model is instantiated as the result of an asynchronous call from that controller.
And finally, rather than instantiating an invalid model then changing the model once the data has been fetched, I'd recommend using a FutureBuilder - this way you have control over what to build based on whether the future is underway, successful, or failed.
So that will look something like this (pseudo-code).
StatefulWidget (MyApp or whatever you call it)
build =>
FutureBuilder(<fetch model data>, ...)
(if done)
ScopedModel<MainModel>
.... (whatever your code has here)
ScopedModelDescendant<MainModel>
(build using the model)
(if not done)
Loading.... (if needed)
If you absolutely want your model to always be there, I'd still recommend doing the fetching in the top stateful widget and simply changing which model you pass in below it rather than modifying the existing model once the data is loaded.
this is my solution i hope it help
#override
void initState() {
super.initState();
User user = ScopedModel.of(this.context);
_controllerFirstName.text = user.userData['first_name'];
_controllerLastName.text = user.userData['last_name'];
}
As newbie in flutter it's very confusing for me when use setState in Flutter application. In below code boolean searching and var resBody used inside setState. My question is why only searching and resBody inside setState? Why not others variable?
var resBody;
bool searching = false,api_no_limit = false;
String user = null;
Future _getUser(String text) async{
setState(() {
searching = true;
});
user = text;
_textController.clear();
String url = "https://api.github.com/users/"+text;
var res = await http
.get(Uri.encodeFull(url), headers: {"Accept":
"application/json"});
setState(() {
resBody = json.decode(res.body);
});
}
According to the docs:
Calling setState notifies the framework that the internal state of this object has changed in a way that might impact the user interface in this subtree, which causes the framework to schedule a build for this State object.
So if the state of the widget changes you have to call setState to trigger a rebuild of the view and see immediatly the changes implied by the new state.
Anyhow the below snippets are equivalent.
first case (directly form flutter create <myproject>):
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
second case:
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
_counter++;
setState(() {});
}
What I don't know is the reason why and if the first case is the conventional way to use setState, I would say because of readability of code.
When you change the state of a stateful widget, use setState() to cause a rebuild of the widget and it's descendants.
You don't need to call setState() in the constructor or initState() of the widget, because build() will be run afterwards anyway.
Also don't call setState() in synchronous code inside build(). You shouldn't need to cause a rerun of build() from within build().
If you look at the implementation of setState:
void setState(VoidCallback fn) {
assert(fn != null);
assert(...);
final dynamic result = fn() as dynamic;
assert(...);
_element.markNeedsBuild();
}
you see that the only things it does are: asserting a few things to help you debug incorrect usage of it, executing the callback, and marking the element so it gets rebuild.
So, technically, it doesn't matter if you change some variables inside the setState callback or outside of it, as long as setState is called.
However, for readability there is a big difference. Rebuilding widgets has impact on the performance of the app, so you want to do so as little as possible. Making all, and only those, changes to variables that require the widget to rebuild inside the setState callback makes it clear to people (including your future self) exactly why a rebuild is needed.
When you need to change the value any widget shows on the screen. For example, in the app there was a task. After completion of which points should be added to the "wallet". But the problem is that we need to refresh the app to see points on the "wallet". To solve this we use Setstate() on Button Onpressed()
For example:
RaisedButton(
onpressed(){
setstate(){
points+10;
}
}
)
Every time the button is pressed it will refresh the widget with the new value returned by the "wallet" variable without the need to restart the entire App.
Whenever you want to update a widget tree (generally with some new data), you call setState. It can only be used in State class. Here's the simple implementation:
class _MyPageState extends State<MyPage> {
int _count = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
onPressed: () => setState(() => _count++),
child: Text('Count = $_count'),
),
),
);
}
}
setState() is only being used with statefulWidget insides flutter. setState() tell the flutter to rebuild the page when something defined inside the setState() changes.
NOT: setState() is a callback function.
Text(questions[questionIndex])
Here I want to change the Text according to the given questions array, and on every button click, I want to increase the index by 1.
void answerQuesion() {
setState(() {
questionIndex = questionIndex + 1;
});
print(questionIndex);
}
So, I have to put this index increment inside the setState(), so that flutter will rebuild the page after every change of questionIndex.
My goal is to have a List of ListTiles, in order to keep track, be able to add/remove items that can later be displayed in a ListView. Here is a simplified version of my code:
class Notes extends StatefulWidget{
List<ListTile> notes;
_NotesState createState() => new _NotesState();
}
class _NotesState extends State<Notes>{
#override
Widget build(BuildContext context){
widget.notes.add(new ListTile(title: new Text("Title")));
return new ListView(
children: <ListTile>[
notes[0],
]
);
}
}
But I receive the following:
NoSuchMethodError: The method 'add was called on null.
Receiver: null
Tried calling: add(Instance of 'ListTile')
I presume it should be possible to do so, though of course I might be mistaken. Thank you in advance for helping me with my potentially stupid question.
The problem two fold: 1) your notes field may not be initialized. 2) you are storing state outside of the State object. Instead, place your notes inside your state object and initialize the member like this:
class _NotesState extends State<Notes>{
List<ListTile> notes = [
new ListTile(title: new Text("Title")),
];
#override
Widget build(BuildContext context){
return new ListView(
children: <ListTile>[
notes[0],
]
);
}
}
The reason mutable data like this needs to be in State is that the widget itself may be rebuilt dozens of times, erasing all of your notes.
For example, if your widget is the child of an animation, it will get rebuilt 60 times a second. This also means that you shouldn't mutate data inside of a build method at all, since it may be called more often then you would think.