I have a flutter app where a list is generated with ListView.Builder, and where the itemCount is the number of documents in a firestore collection. This works fine until a new document is added. When that happens I get the error (17 and 18 are just examples).
Invalid value: Not in range 0..17, inclusive: 18
I assume I would need to update the state when a new document is created, but I have no idea how i can call setState when that happens
Here is the relevant part of the code:
child: StreamBuilder(
stream: Firestore.instance.collection('contact').orderBy(sortby, descending: decending).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Container();
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) =>
_personer(context, snapshot.data.documents[index], index),
);
},
),
use setState?
StreamBuilder(builder: (context, snapshot) {
return snapshot.hasData == null ? Container() : _getListView(snapshot);
} , )
_getListView(snapshot) {
setState(() {
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) =>
_personer(context, snapshot.data.documents[index], index),
);
});
}
StreamBuilder use QuerySnapshot so list data can change
example code :
StreamBuilder<QuerySnapshot>(
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting: return new Text('Loading...');
default:
return new ListView(
children: snapshot.data.documents.map((DocumentSnapshot document) {
return ;
}).toList(),
);
}
},
)
Related
I am using a FutureBuilder inside a StreamBuilder that updates the UI every time a document is added to the activity collection, to get some aditional data from Firestore. The problem is that the FutureBuilder returns a SizedBox widget while the ConnectionState is waiting causing the all the cards to dissapear for a second. I would like to avoid this flickering since it causes a bad ui experience for users.
Is there a way to query the required user data in the activity stream so it all returns at once that way I can remove the FutureBuilder?
If not ... what would be a solution for this?
activityStream() {
return FirebaseFirestore.instance
.collection('activity')
.orderBy('timestamp', descending: true)
.limit(55)
.snapshots();
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const SizedBox(
height: 65.0,
);
}
StreamBuilder<QuerySnapshot>(
stream: activityStream(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const Center(child: CircularProgressIndicator());
default:
final activityContent = snapshot.data?.docs
.map((data) => ActivityModel.fromFirestore(data))
.toList();
return Scrollbar(
controller: widget.scrollController,
child: ListView.builder(
shrinkWrap: true,
controller: widget.scrollController,
itemCount: activityContent!.length,
itemBuilder: (context, i) {
return FutureBuilder(
future: FirebaseFirestore.instance
.collection('users')
.where('uid', whereIn: activityContent[i].players)
.get(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const SizedBox(
height: 65.0,
);
}
final users = snapshot.data!.docs.map((e) {
return UserModel.fromFirestore(e);
}).toList();
return MyWidget(
users: users,
);
},
);
},
),
);
}
},
);
I'm trying to fetch data from firebase and I want that data to be printed in my listview.builder.
Here is my code:-
StreamBuilder(
stream: FirebaseFirestore.instance
.collection("passwordProfile")
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
if (snapshot.data.documents.lenght == 0) {
return Text("no data");
}
return ListView.builder(
itemCount: snapshot.data.documents.lenght,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(
child: snapshot.data.documents[index].data("url")),
title: snapshot.data.documents[index].data["url"],
);
});
}),
And below is a picture:-
From the picture you can observe that documents is not being recognized so help there.
Please specify SteamBuilder return type i-e Querysnapshot here's the example of your code
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance
.collection("passwordProfile")
.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
final userSnapshot = snapshot.data?.docs;
if (userSnapshot!.isEmpty) {
return const Text("no data");
}
return ListView.builder(
itemCount: userSnapshot.length,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(
child: userSnapshot[index]["url"],
),
title: userSnapshot[index]["url"],
);
});
});
Once you check null, you can add ! like snapshot.data!
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
final data = snapshot.data as Map<String, dynamic>?;
if (data == null) {
return Text("no data");
}
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) {
return ListTile(
// leading:
// CircleAvatar(child: data[index].data("url")),
title: Text("${data[index].data["url"]}"),
);
});
}),
I need to wait my function to be done in a ListView because of a Firestore request. I tried using Future.wait() but it does not work.
FutureBuilder<QuerySnapshot>(
future: FirebaseFirestore.instance
.collection('Statements')
.where('userID',isEqualTo: context.watch<User>().uid)
.get(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot){
if (snapshot.hasError) {return Text("Erreur");}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");}
return Expanded(
child:ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
DocumentSnapshot document = snapshot.data.docs[index];
Future.wait([getStatementData(document)]);
return StatementCard(statement:selectedStatement,
accommodation : relatedAccommodation,
owner : relatedOwner);
},
)
Here is the function called :
Future getStatementData(document) async {
selectedStatement = Statement.fromFirebase(document);
document.data()["accommodation"].get().then((value) {
relatedAccommodation = Accommodation.fromFirebase(value);});
await FirebaseFirestore.instance
.collection('Owners')
.doc(document["ownerList"][0])
.get().then((value) {
print(value.data());
relatedOwner = Owner.fromFirebase(value);
});
}
Should I use another future builder ?
You should do it like this by returning another Future Builder inside :
ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
DocumentSnapshot document = snapshot.data.docs[index];
return FutureBuilder(
future: getStatementData(document) ,
builder: (context, snapshot) {
return snapshot.connectionState == ConnectionState.done
? StatementCard(statement:selectedStatement,
accommodation : relatedAccommodation,
owner : relatedOwner);
: Container();
);
},
)
I think you just missed a simple thing, FutureBuilder also has a connectionstate.done state which you can access. This waits until your future function is Done.
return FutureBuilder(
future: yourFunction,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Center(child: Text('No status',);
break;
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
break;
case ConnectionState.done:
return Text('Im done!') // Your listviewbuilder comes here
break;
default:
}
},
),
doc: https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
example FutureBuilder with ListViewBuilder: https://medium.com/nonstopio/flutter-future-builder-with-list-view-builder-d7212314e8c9
[...]connectionState.done = [future] is not null, and has completed. If the future completed successfully, the [AsyncSnapshot.data] will be set to the value to which the future completed. If it completed with an error, [AsyncSnapshot.hasError] will be true and [AsyncSnapshot.error] will be set to the error object.[...]
Scaffold(body: FutureBuilder(
future: fetchTracks(),
builder: (BuildContext context, AsyncSnapshot snapshot){
if(snapshot.hasData)
{
ListView.builder(
scrollDirection: Axis.vertical,
itemExtent: 130.0,
physics: AlwaysScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: trackes.length,
itemBuilder: (BuildContext context, int index) {
print("test");
return makeCard(snapshot.data[index]);
},
).build(context);
}
else
{
return Center(child: new CircularProgressIndicator());
}
} ));
When i call this Scaffold Future build will call my future function fetchTracks() and get the data in snapshot but it is not entering into itemBuilder function. So futurebuilder return NULL.
Please help me to solve .and Thank you in advance
You're missing a return before ListView.builder. If you don't return it, it won't build it.
FutureBuilder has different snapshot connectionstates which you must handle. Data on the stream is not available until ConnectionState equals done and hasData equals true.
_loadData(context)
{
showModalBottomSheet(
context: context,
builder: (BuildContext bc){
return FutureBuilder(
future: fetchTracks(),
builder: (BuildContext context, AsyncSnapshot<List<MyClass>> snapshot){
if (snapshot.connectionState!=ConnectionState.done)
{
return PleaseWaitWidget();
}
else if(snapshot.hasError)
{
DialogCaller.showErrorDialog(context,"future builder has an error").then((value){});
}
else if (snapshot.connectionState==ConnectionState.done)
{
if(snapshot.hasData){
List<Widget> list = snapshot.data.map((MyClass myClass){
return Card(
child:Wrap(children:<Widget>[
Row(children: [Text(myClass.field1)],),
]));}).toList();
return list;
}
}
});
});
}
I'm trying to build an app in flutter and I have come up against this problem which I can't seem to find any existing answers. How do I get only the current users posts to show? My posts collection has a user id field which I want to compare with the current user and display only the post where the userId and currentUser are the same.
return FutureBuilder(
future: FirebaseAuth.instance.currentUser(),
builder: (ctx, futureSnapshot) {
if (futureSnapshot.connectionState == ConnectionState.waiting) {
Center(
child: CircularProgressIndicator(),
);
}
return StreamBuilder(
stream: Firestore.instance.collection('posts').snapshots(),
builder: (context, streamSnapshot) {
if (streamSnapshot.connectionState == ConnectionState.waiting) {
Center(
child: CircularProgressIndicator(),
);
}
final documents = streamSnapshot.data.documents;
return ListView.builder(
itemCount: documents.length,
itemBuilder: (ctx, index) => PostItem(
documents[index]['title'],
documents[index]['imageUrl'],
documents[index]['location']['address'],
));
});
});
here is my post collection structure
You're currently getting all posts with:
Firestore.instance.collection('posts').snapshots()
If you only want posts for the current user, that'd be something like:
var uid = (await FirebaseAuth.instance.currentUser()).uid;
Firestore.instance.collection('posts').where('uid', isEqualTo: uid).snapshots()
The first line determines the UID of the current user, and then the second line uses that to request only documents whose uid field matches the value.
I just realised I hadn't but my future into a variable for the streambuilder where clause !! I was trying to use the original fireauth currentuser!
return FutureBuilder(
future: FirebaseAuth.instance.currentUser(),
builder: (ctx, futureSnapshot) {
if (futureSnapshot.connectionState == ConnectionState.waiting) {
Center(
child: CircularProgressIndicator(),
);
}
**final String currentUser** = futureSnapshot.data.uid;
return StreamBuilder(
stream: Firestore.instance
.collection('posts')
.where('userId', isEqualTo: **currentUser**)
.snapshots(),
builder: (context, streamSnapshot) {
if (streamSnapshot.connectionState == ConnectionState.waiting) {
Center(
child: CircularProgressIndicator(),
);
}
final documents = streamSnapshot.data.documents;
return ListView.builder(
itemCount: documents.length,
itemBuilder: (ctx, index) => PostItem(
documents[index]['userId'],
documents[index]['title'],
documents[index]['imageUrl'],
documents[index]['location']['address'],
));
});
});
```