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'];
}
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 trying to build a profile page that pulls a new user's 'name' from my Firestore database. Currently, I am getting an error (specifically with the rowCell line). Does anyone have any ideas or other better ways to do this?
Thank you!
Tried alternative functions to replace the 'rowCell' line.
Right now, values from Firestore are not pulling into the app.
class ProfileWidget extends StatelessWidget {
final String userId;
ProfileWidget (this.userId);
#override
Widget build(BuildContext context) {
return StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance.collection('users').document(userId).snapshots(),
builder: (context, snapshot) {
User user = User.fromSnapshot(snapshot.data);
return Row(
children: <Widget>[
rowCell(user.name, 'Name'),
],);
},
);}}
class User{
final int name;
final DocumentReference reference;
User.fromMap(Map<String, dynamic> map, {this.reference})
: name = map['name'];
User.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data, reference: snapshot.reference);
}
I think you don't get the documents from your snapshot.data. To do so you have to use snapshot.data.documents. In this post is an example of how to do that.
Code example from post:
return snapshot.data.documents
.map((document) => new Text(document['some_field']).toList()
To access fields from a document you can do so as shown above with document[‘some_field’]. Here we use our document reference from our map operation and then access a piece of data from that document using the field name as an accessor.
Hope this helps.
It will be better if you post your error.
According to you error is in rowCell then it means it is unable to build the widget (I guess as you haven't mentioned the error).
Some Suggestions:
Suggestion 1:
builder:(context, snapshot){
if(snapshot.hasData){
// Process the data and return the widget.
}
else{
// IMPORTANT: You have to return a widget or will get error.
// Return the widget that should be shown during processing like:
return CircularProgressIndicator();
}
}
Suggestion 2: Are you sure User.name is an int? Usually it is String.
Suggestion 3: FutureBuilder will be more useful if you only need to fetch data once and build the page but if you need to update it automatically then you are using the correct widget.
If it doesn't help then post your error.
Thanks.
You have to check your snapshots when it has data before return your widget :
if(snapshot.hasData){
User user = User.fromSnapshot(snapshot.data);
return Row(
children: <Widget>[
rowCell(user.name, 'Name'),
],);
}
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.
How can a ListView item refresh after each async task?
I have 2 collections on firebase that needs to be accessed and please, if there's another way please advise since I'm new in Firebase and Flutter.
My users have a collection inside called favorites with the userID field (same of the document id) and I load the ListView with all the users data BUT only with the ones that match that IDs (to avoid loading for example 1000 users for no reason = $$$).
According my code and my tests on the Run window I get the value of each user but my ListView on the app shows blank. I tried to place a setstate but the app refreshs non-stop.
I tried to create a separated function but simply I can't get it returning the list of document snapshots.
Thank you
FutureBuilder(
future: Firestore.instance.collection('users').document(id).collection('favorites').getDocuments(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<DocumentSnapshot> userDocs = [];
snapshot.data.documents.forEach((doc) async {
await Firestore.instance.collection('users').document(doc.documentID).get().then((user) {
userDocs.add(user);
});
});
return ListView(
children: userDocs.map((document) {
return buildItem(context, document);
}).toList(),
);
}
},
),
So, after weeks on this without any help I finally done it.
StreamBuilder is getting only the documents on favorites:
stream: Firestore.instance.collection('u').document(id).collection('f').snapshots()
ListView.Builder is returning a new Widget (MessageTile Stateful widget)
return MessageTile(ctx: context, doc: snapshot.data.documents[index]);
Inside the Statefull widget I'm returning a FutureBuilder<DocumentSnapshot> that is fetching ONLY the user matching the userId:
future: Firestore.instance.collection('u').document(doc['userId']).get()
Simple right? ..
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.