I am building an app and I'm trying to show all of the posts in the feed page of the people the current user is following.
It's working well, but my problem is the WhereIn limit.. How can I evade this limitation inside a FutureBuilder?
here is the code:
buildFeed() {
return FutureBuilder(
future: storiesRef
.where('uid', whereIn: widget.userIds)
.orderBy('timeStamp', descending: true)
.get(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return loading();
}
final stories = snapshot.data.docs;
List<StoryTickets> tickets = [];
for (var story in stories) {
List<String> categories = List.from(story.data()['categories']);
StoryTickets ticket = StoryTickets(
displayName: story.data()['displayName'],
categories: categories,
storyId: story.data()['sid'],
commentId: story.data()['cid'],
ownerId: story.data()['uid'],
rating: story.data()['rating'].toString(), //TODO: maybe delete
storyPhoto: story.data()['storyPhoto'],
timestamp: story.data()['timeStamp'],
title: story.data()['title'],
);
tickets.add(ticket);
}
return ListView(
children: tickets,
);
},
);
}
Related
I m trying to snapshot the Collection of users and I want to check if the user has the field or not
If the users has then show this
If user doesnβt have then show this
This is the code i got so far.
class _HalfScreenState extends State<HalfScreen> {
final userUid = FirebaseAuth.instance.currentUser!.uid;
#override
Widget build(BuildContext context) {
return StreamBuilder<DocumentSnapshot?>(
stream: FirebaseFirestore.instance
.collection("users")
.doc(userUid)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.data != null) {
return const Text("Loading...");
}
return Text(
(snapshot.data as DocumentSnapshot)['groupId'],
),
});
}
}
Step by step that'd be:
Get the data from the document as a Map.
Check if the map contains a key for the field you're looking for.
In code that'd be something like this:
// π Indicate the type of the data in the document
return StreamBuilder<DocumentSnapshot<Map<String, dynamic>>>(
stream: FirebaseFirestore.instance
.collection("users")
.doc(userUid)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.data != null) {
return const Text("Loading...");
}
// π Get the data (Map<String, dynamic>
var data = snapshot.data.data();
return Text(
// π Check if the field is tere
data.containsKey('groupId') ? 'Yes, it's there' : 'Nope, field not found'
),
}
);
I am trying to build a query that will help me to build tickets for all the current user following,
but when the list is getting bigger (above 10) it is giving me an error that 10 is the limit.
here is the code:
buildFollowingTickets() {
if (!widget.followingList.isEmpty) {
return FutureBuilder(
future: userRef.where('id', whereIn: widget.followingList).get(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return loading();
}
List<UserTicket> userFollowing = [];
final followings = snapshot.data.docs;
for (var following in followings) {
final id = following.data()['id'];
final displayName = following.data()['displayName'];
final photoUrl = following.data()['photoUrl'];
final UserTicket userTicket = UserTicket(
displayName: displayName,
photoUrl: photoUrl,
id: id,
);
userFollowing.add(userTicket);
}
return ListView(
children: userFollowing,
);
},
);
}
}
WhereIn can only look for a list below 10 and im trying to figure out how to change that...
Thanks in advance!
Thanks!
I decided to change it into a stream builder and now its works - I also change my database a little bit
buildFollowersTickets() {
if (!widget.followersList.isEmpty) {
return StreamBuilder(
stream: followersRef
.doc(widget.followersId)
.collection('userFollowers')
.orderBy('displayName', descending: false)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return loading();
}
List<UserTicket> userFollowers = [];
final followers = snapshot.data.docs;
for (var follower in followers) {
final id = follower.data()['id'];
final displayName = follower.data()['displayName'];
final photoUrl = follower.data()['photoUrl'];
final UserTicket userTicket = UserTicket(
displayName: displayName,
photoUrl: photoUrl,
id: id,
);
userFollowers.add(userTicket);
}
return ListView(
children: userFollowers,
);
},
);
}
}
So I need to get access to a piece of data called groupId which will be used as a doc path for a collection called Members that I will be used to retrieve data from and put in a list. The problem is that it requires an async which I don't know where to put as I get errors. Here's the code:
#override
Widget build(BuildContext context) {
final CollectionReference users = firestore.collection('UserNames');
final String uid = auth.currentUser.uid;
final result = await users.doc(uid).get(); //This await requires an async but I don't how to do that
final groupId = result.data()['groupId'];
// <1> Use FutureBuilder
return FutureBuilder<QuerySnapshot>(
// <2> Pass `Future<QuerySnapshot>` to future
future: FirebaseFirestore.instance.collection('Groups').doc(groupId).collection('Members').get(), //Once the async problem is solved i will be able to save the groupId as. variable to be used in my doc path to access this collection. How do I do this?
builder: (context, snapshot) {
if (snapshot.hasData) {
// <3> Retrieve `List<DocumentSnapshot>` from snapshot
final List<DocumentSnapshot> documents = snapshot.data.docs;
return ListView(
children: documents
.map((doc) => Card(
child: ListTile(
title: Text(doc['displayName']),
subtitle: Text(doc['plastics'].toString()),
),
))
.toList());
} else if (snapshot.hasError) {
return Text('Its Error!');
}
});
}
Wrap your FutureBuilder in another FutureBuilder.
users.doc(uid).get() returns a Future. If you are using it in a widget, you use FutureBuilder.
await and futureBuilder do the similar things
#override
Widget build(BuildContext context) {
final CollectionReference users = firestore.collection('UserNames');
final String uid = auth.currentUser.uid;
return FutureBuilder(
future: users.doc(uid).get(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final result = snapshot.data;
final groupId = result.data()['groupId'];
return FutureBuilder<QuerySnapshot>(
// <2> Pass `Future<QuerySnapshot>` to future
future: FirebaseFirestore.instance
.collection('Groups')
.doc(groupId)
.collection('Members')
.get(), //Once the async problem is solved i will be able to save the groupId as. variable to be used in my doc path to access this collection. How do I do this?
builder: (context, snapshot) {
if (snapshot.hasData) {
// <3> Retrieve `List<DocumentSnapshot>` from snapshot
final List<DocumentSnapshot> documents = snapshot.data.docs;
return ListView(
children: documents
.map((doc) => Card(
child: ListTile(
title: Text(doc['displayName']),
subtitle: Text(doc['plastics'].toString()),
),
))
.toList());
} else if (snapshot.hasError) {
return Text('Its Error!');
}
});
}
});
}
Here i have the code where i retrieve all the documents from Firebase, i am only able to retrieve the data present inside the document but i also want to retrieve the document Id with it, how can i do that?
StreamBuilder<QuerySnapshot>(
stream: getData(),
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 new CustomCard(
name: document['name'],
phone: document['phno'],
service: document['Category'],
address: document['address'],
pin: document['pin'],
payment: document['Payment'],
);
}).toList(),
);
}
},
),
This is my getter function
Stream<QuerySnapshot> getData()async*{
FirebaseUser user = await FirebaseAuth.instance.currentUser();
yield* Firestore.instance
.collection('User')
.where("userId", isEqualTo: user.uid)
.orderBy('upload time', descending: true)
.snapshots();
}
To get the id, do the following:
.map((DocumentSnapshot document) {
return new CustomCard(
docId : document.documentID,
name: document['name'],
phone: document['phno'],
service: document['Category'],
address: document['address'],
pin: document['pin'],
payment: document['Payment'],
);
}).toList(),
documentID will give you each document id.
you could as well do something like
snapshot.data!.docs[index].id
this will return the id of the document at that index
I m new to flutter, and I'm trying to paginate a chat when scroll reach top with streambuilder. The problem is: when i make the query in scrollListener streambuilder priorize his query above the scrollListener and returns de old response. Is there any way to do this? What are my options here? Thanks!
Class ChatScreenState
In initState I create the scroll listener.
#override
void initState() {
listScrollController = ScrollController();
listScrollController.addListener(_scrollListener);
super.initState();
}
Here i create the StreamBuilder with the query limited to 20 last messages. Using the _messagesSnapshots as global List.
#override
Widget build(BuildContext context) {
return Scaffold(
key: key,
appBar: AppBar(title: Text("Chat")),
body: Container(
child: Column(
children: <Widget>[
Flexible(
child: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection('messages')
.where('room_id', isEqualTo: _roomID)
.orderBy('timestamp', descending: true)
.limit(20)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
_messagesSnapshots = snapshot.data.documents;
return _buildList(context, _messagesSnapshots);
},
)),
Divider(height: 1.0),
Container(
decoration: BoxDecoration(color: Theme.of(context).cardColor),
child: _buildTextComposer(),
),
],
),
));
}
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
_messagesSnapshots = snapshot;
return ListView.builder(
controller: listScrollController,
itemCount: _messagesSnapshots.length,
reverse: true,
itemBuilder: (context, index) {
return _buildListItem(context, _messagesSnapshots[index]);
},
);
}
And in the _scollListener method i query the next 20 messages and add the result to the Global list.
_scrollListener() {
// If reach top
if (listScrollController.offset >=
listScrollController.position.maxScrollExtent &&
!listScrollController.position.outOfRange) {
// Then search last message
final message = Message.fromSnapshot(
_messagesSnapshots[_messagesSnapshots.length - 1]);
// And get the next 20 messages from database
Firestore.instance
.collection('messages')
.where('room_id', isEqualTo: _roomID)
.where('timestamp', isLessThan: message.timestamp)
.orderBy('timestamp', descending: true)
.limit(20)
.getDocuments()
.then((snapshot) {
// To save in the global list
setState(() {
_messagesSnapshots.addAll(snapshot.documents);
});
});
// debug snackbar
key.currentState.showSnackBar(new SnackBar(
content: new Text("Top Reached"),
));
}
}
I'm gonna post my code i hope someone post a better solution, probably is not the best but it works.
In my app the actual solution is change the state of the list when reach the top, stop stream and show old messages.
All code (State)
class _MessageListState extends State<MessageList> {
List<DocumentSnapshot> _messagesSnapshots;
bool _isLoading = false;
final TextEditingController _textController = TextEditingController();
ScrollController listScrollController;
Message lastMessage;
Room room;
#override
void initState() {
listScrollController = ScrollController();
listScrollController.addListener(_scrollListener);
super.initState();
}
#override
Widget build(BuildContext context) {
room = widget.room;
return Flexible(
child: StreamBuilder<QuerySnapshot>(
stream: _isLoading
? null
: Firestore.instance
.collection('rooms')
.document(room.id)
.collection('messages')
.orderBy('timestamp', descending: true)
.limit(20)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
_messagesSnapshots = snapshot.data.documents;
return _buildList(context, _messagesSnapshots);
},
),
);
}
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
_messagesSnapshots = snapshot;
if (snapshot.isNotEmpty) lastMessage = Message.fromSnapshot(snapshot[0]);
return ListView.builder(
padding: EdgeInsets.all(10),
controller: listScrollController,
itemCount: _messagesSnapshots.length,
reverse: true,
itemBuilder: (context, index) {
return _buildListItem(context, _messagesSnapshots[index]);
},
);
}
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final message = Message.fromSnapshot(data);
Widget chatMessage = message.sender != widget.me.id
? Bubble(
message: message,
isMe: false,
)
: Bubble(
message: message,
isMe: true,
);
return Column(
children: <Widget>[chatMessage],
);
}
loadToTrue() {
_isLoading = true;
Firestore.instance
.collection('messages')
.reference()
.where('room_id', isEqualTo: widget.room.id)
.orderBy('timestamp', descending: true)
.limit(1)
.snapshots()
.listen((onData) {
print("Something change");
if (onData.documents[0] != null) {
Message result = Message.fromSnapshot(onData.documents[0]);
// Here i check if last array message is the last of the FireStore DB
int equal = lastMessage?.compareTo(result) ?? 1;
if (equal != 0) {
setState(() {
_isLoading = false;
});
}
}
});
}
_scrollListener() {
// if _scroll reach top
if (listScrollController.offset >=
listScrollController.position.maxScrollExtent &&
!listScrollController.position.outOfRange) {
final message = Message.fromSnapshot(
_messagesSnapshots[_messagesSnapshots.length - 1]);
// Query old messages
Firestore.instance
.collection('rooms')
.document(widget.room.id)
.collection('messages')
.where('timestamp', isLessThan: message.timestamp)
.orderBy('timestamp', descending: true)
.limit(20)
.getDocuments()
.then((snapshot) {
setState(() {
loadToTrue();
// And add to the list
_messagesSnapshots.addAll(snapshot.documents);
});
});
// For debug purposes
// key.currentState.showSnackBar(new SnackBar(
// content: new Text("Top reached"),
// ));
}
}
}
The most important methods are:
_scrollListener
When reach the top i query old messages and in setState i set isLoading var to true and set with the old messages the array i m gonna show.
_scrollListener() {
// if _scroll reach top
if (listScrollController.offset >=
listScrollController.position.maxScrollExtent &&
!listScrollController.position.outOfRange) {
final message = Message.fromSnapshot(
_messagesSnapshots[_messagesSnapshots.length - 1]);
// Query old messages
Firestore.instance
.collection('rooms')
.document(widget.room.id)
.collection('messages')
.where('timestamp', isLessThan: message.timestamp)
.orderBy('timestamp', descending: true)
.limit(20)
.getDocuments()
.then((snapshot) {
setState(() {
loadToTrue();
// And add to the list
_messagesSnapshots.addAll(snapshot.documents);
});
});
// For debug purposes
// key.currentState.showSnackBar(new SnackBar(
// content: new Text("Top reached"),
// ));
}
}
And loadToTrue that listen while we are looking for old messages. If there is a new message we re activate the stream.
loadToTrue
loadToTrue() {
_isLoading = true;
Firestore.instance
.collection('rooms')
.document(widget.room.id)
.collection('messages')
.orderBy('timestamp', descending: true)
.limit(1)
.snapshots()
.listen((onData) {
print("Something change");
if (onData.documents[0] != null) {
Message result = Message.fromSnapshot(onData.documents[0]);
// Here i check if last array message is the last of the FireStore DB
int equal = lastMessage?.compareTo(result) ?? 1;
if (equal != 0) {
setState(() {
_isLoading = false;
});
}
}
});
}
I hope this helps anyone who have the same problem (#Purus) and wait until someone give us a better solution!
First of all, I doubt such an API is the right backend for a chat app with live data - paginated APIs are better suited for static content.
For example, what exactly does "page 2" refer to if 30 messages were added after "page 1" loaded?
Also, note that Firebase charges for Firestore requests on a per-document basis, so every message which is requested twice hurts your quota and your wallet.
As you see, a paginated API with a fixed page length is probably not the right fit. That why I strongly advise you to rather request messages that were sent in a certain time interval.
The Firestore request could contain some code like this:
.where("time", ">", lastCheck).where("time", "<=", DateTime.now())
Either way, here's my answer to a similar question about paginated APIs in Flutter, which contains code for an actual implementation that loads new content as a ListView scrolls.
I have a way to archive it. Sorry for my bad english
bool loadMoreMessage = false;
int lastMessageIndex = 25 /// assumed each time scroll to the top of ListView load more 25 documents When I scroll to the top of the ListView =>setState loadMoreMessage = true;
This is my code:
StreamBuilder<List<Message>>(
stream:
_loadMoreMessage ? _streamMessage(lastMessageIndex): _streamMessage(25),
builder: (context, AsyncSnapshot<List<Message>> snapshot) {
if (!snapshot.hasData) {
return Container();
} else {
listMessage = snapshot.data;
return NotificationListener(
onNotification: (notification) {
if (notification is ScrollEndNotification) {
if (notification.metrics.pixels > 0) {
setState(() {
/// Logic here!
lastMessageIndex = lastMessageIndex + 25;
_loadMoreMessage = true;
});
}
}
},
child: ListView.builder(
controller: _scrollController,
reverse: true,
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ChatContent(listMessage[index]);
},
),
);
}
},
),