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? ..
Related
I need to make multiple query the field is .child() in FirebaseDatabase.instance.reference()
For example, I want it to work like this:
final _itemRef2 = FirebaseDatabase.instance
.reference()
.child('lists')
.child('-Mdso4T8PFejXPXjVhpW','-MdshmYfuEeZWK_hNwZe','-Mdsi6vSVHAmRbxoELNm')
.child('items');
Of course, it doesn't work š
I don't understand how do I use the list of values in .child? What solution should I use?
Then, i want use _itemRef2 in FirebaseAnimatedList like this:
Flexible(
child: FirebaseAnimatedList(
query: _itemRef2,
itemBuilder: (BuildContext context,
DataSnapshot snapshot,
Animation<double> animation,
int index) {
return ListTile(
You cannot pass multiple values in child that way. You'll have to make separate requests for each node
final databaseReference = FirebaseDatabase.instance.reference().child("lists");
databaseReference.child("list_id_1").once(),
databaseReference.child("list_id_2").once()
You can try using Future.wait() to run all Futures simultaneously.
I have a text field inside a Flushbar, and the text field makes use of Google Places API to search for places, based on the provided input. I am calling a function on the 'onChanged' parameter of the text field, as can be seen in the code, but this does not show on the screen as something is typed in the text field. Although printing the values shows that the values were updated. Additionally, on popping the Flushbar and opening it back shows the expected result.
Also, this happens only when using the updated values inside a Flushbar. It renders as expected on the scaffold.
I know this is similar to other questions that have been asked before, but no answers provided yet seem to work for me. I have even come to discover that this could be happening as the Flushbar itself does not have a 'state' and so 'setState()' does not affect its' contents. But I don't know how to go around the problem. Any help on this will be highly appreciated.
Update: I have tried using a StatefulBuilder widget inside the Flushbar but it gives similar results. I called StaefulBuilder in the return statement after the ListView.builder inside the Flushbar.
Sample Code:
#override
Widget build (BuildContext context){
return Scaffold(
body : FlatButton(
child : Text('Search Location'),
onPressed : Flushbar(
flushbarPosition: FlushbarPosition.BOTTOM,
userInputForm : Form(
child : Container(
child : Column(
children : <Widget>[
getSearchFieldFlushBar(),
_displaySearchedPlaces.length != 0
? ListView.builder(
shrinkWrap : true,
itemCount : _displaySearchedPlaces.length
itemBuilder : (BuildContext context, index){
return ListTile(
title : Text(_displaySearchedPlaces[index])
);
})
: Container(height : 0, width : 0),
])
)
)
)..show(context);
));
}
Here's the function, which calls the text field:
TextFormField getSearchFieldFlushbar(){
return TextFormField(
style: TextStyle(color: Colors.black),
onChanged: (text) {
getLocationResults(text);
},
);
And this is the function that gets called when something is entered in the text field:
void getLocationResults(String input) async {
String baseURL = 'https://maps.googleapis.com/maps/api/place/autocomplete/json';
String type = 'geocode';
String request = '$baseURL?input=$input&key=$places_Api_Key&type=$type';
Response response = await Dio().get(request);
final predictions = response.data['predictions'];
_displayResults = [];
for (int i = 0; i < predictions.length; i++) {
String name = predictions[i]['structured_formatting']['main_text'];
_displayResults.add(name);
}
setState(() {
_displaySearchedPlaces = _displayResults;
});
print(_searchedPlacesResults);
}
}
Screenshot1
ScreenShot2
I finally managed to fix this using a few answers that have been given before to similar questions on Stackoverflow such as here and here.
Basically, we use a StatefulBuilder Widget and put all the children widgets which can potentially be updated, inside this widget. As has been mentioned in the question, using this was not working for me initially, however, it started working after a while.
For reference, I added the StatfulBuilder widget at the 'child' parameter of the Form widget.
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.
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'];
}