whereIn 10 limit Flutter - firebase

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,
);
},
);
}
}

Related

Flutter: Streambuilder for documents from query

How do I get a listview of all the items returned from query snapshot?
FirebaseFirestore.instance
.collection("children")
.where("parentUID", isEqualTo: uid)
.snapshots()
.listen((result) {
result.docs.forEach((result) {
setState(() {
childFirstName = result["childFirstName"];
childLastName = result["childLastName"];
points = result["points"];
docID = result.id;
print('$docID');
});
});
});
}
This is all I could come up with, here is an image of the database,
Image of database
How can I make a listview which creates an item for every document which the parentUID is equal to the current users uid which is already stored in a variable uid
You can use the sample code below.
It shows how to use the stream from your query and return a ListView.builder which displays the information in a ListTile.
StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
stream: FirebaseFirestore.instance
.collection('children')
.where('parentUID', isEqualTo: uid)
.snapshots(),
builder: (BuildContext context, snapshot) {
if (snapshot.data == null) {
return Center(child: CircularProgressIndicator());
} else {
final List<QueryDocumentSnapshot<Map<String, dynamic>>> docs = snapshot.data!.docs;
return ListView.builder(
itemCount: docs.length,
itemBuilder: (_, index) {
final doc = docs[index];
final childFirstName = doc["childFirstName"];
final childLastName = doc["childLastName"];
final points = doc["points"];
final docID = doc.id;
return ListTile(
title: Text('$childFirstName $childLastName'),
subtitle: Text(points),
);
},
);
}
},
)

WhereIn limit in flutter firebase

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,
);
},
);
}

Flutter Firestore query 2 collections

Question is updated
How would I query 2 collections? I have a wishlist collection where each wishlist document looks like this:
documentID: "some-wishlist-id",
modified: "1564061309920",
productId: "2tqXaLUDy1IjxLafIu9O",
userId: "0uM7Dt286JYK6q8iLFyF4tG9cK53"
And a product collection where each product document looks like this. The productId in the wishlist collection would be a documentID in the product collection:
documentID: "2tqXaLUDy1IjxLafIu9O",
dateCreated: "1563820643577",
description: "This is a description for Product 9",
images: ["some_image.jpg"],
isNegotiable: true,
isSold: false,
rentPrice: 200,
sellPrice: 410,
title: "Product 9",
totalWishLists: 0,
userId: "0uM7Dt286JYK6q8iLFyF4tG9cK53"
NOTE: To be clear, the wishlist query should return a list of documents that I need to iterate over to retrieve the product.
Not sure if I need to use streams or futures in this case, but this is what I have so far:
Future<Product> getProduct(String documentId) {
return Firestore.instance
.collection(APIPath.products())
.document(documentId)
.get()
.then((DocumentSnapshot ds) => Product.fromMap(ds.data));
}
Query getWishListByUser(userId) {
return Firestore.instance
.collection(APIPath.wishlists())
.where('userId', isEqualTo: userId);
}
Future<List<Product>> getProductsWishList(userId) async {
List<Product> _products = [];
await getWishListByUser(userId)
.snapshots()
.forEach((QuerySnapshot snapshot) {
snapshot.documents.forEach((DocumentSnapshot snapshot) async {
Map<String, dynamic> map = Map.from(snapshot.data);
map.addAll({
'documentID': snapshot.documentID,
});
WishList wishList = WishList.fromMap(map);
await getProduct(wishList.productId).then((Product product) {
print(product.title); // This is printing
_products.add(product);
});
});
});
// This is not printing
print(_products);
return _products;
}
Thanks
With any database you'll often need to join data from multiple tables to build your view.
In relational databases, you can get the data from these multiple tables with a single statement, by using a JOIN clause.
But in Firebase (and many other NoSQL databases) there is no built-in way to join data from multiple locations. So you will have to do that in your code.
Create Wishlist Model:
class Wishlist {
Wishlist({
this.id,
this.modified,
this.productId,
this.userId
});
final String id;
final String modified;
final String productId;
final String userId;
Wishlist.fromMap(json)
: id = json['id'].toString(),
modified = json['modified'].toString(),
productId = json['productId'].toString(),
userId = json['userId'].toString();
}
And in your API file, do this:
final Firestore _fireStore = Firestore.instance;
getWishList(wishlistId) async {
return await _fireStore.collection('wishlists').document(wishlistId).get();
}
getProduct(productId) async {
return await _fireStore.collection('product').document(productId).get();
}
Future<List<Product>>getProductsWishList(wishlistId) async {
var _wishlists = null;
List<Product> _products = []; // I am assuming that you have product model like above
await getWishList(wishlistId).then((val) {
_wishlists = Wishlist.fromMap(val.data);
_wishlists.forEach((wish) {
await getProduct(wish.productId).then((product) {
_products.add(product));
});
});
});
return _products;
}
I found a solution last weekend but forgot to post it. Figured I do that now in case someone else has this problem.
I used a combination of StreamBuilder and FutureBuilder. Not sure if there is a better answer, perhaps combining multiple streams? But this worked for me.
return StreamBuilder<List<Wishlist>>(
stream: wishListStream(userId),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Wishlist> wishlists = snapshot.data;
if (wishlists.length > 0) {
return new GridView.builder(
scrollDirection: Axis.vertical,
itemCount: wishlists.length,
itemBuilder: (context, index) {
Future<Product> product =
getProduct(wishlists[index].productId);
return FutureBuilder(
future: product,
builder: (context, snapshot) {
if (snapshot.hasData) {
Product product = snapshot.data;
// Do something with product
} else {
return Container();
}
},
);
},
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
);
} else {
return Text('No products in your wishlist');
}
}
if (snapshot.hasError) {
print('WishlistPage - ${snapshot.error}');
return Center(child: Text('Some error occurred'));
}
return Center(child: CircularProgressIndicator());
},
);

StreamBuilder Firestore Pagination

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]);
},
),
);
}
},
),

Flutter Firebase: StreamBuilder Not Listening to Changes?

I'm trying to create a UI where the user submits a "comment" on each "article". As you can see below, in my code, I get username of the user from Firestore and then checks if this user already has a comment for the respective article.
If the comment exists, it returns a stream of all the comments for that given article. If it does not exist, I return the CommentCollector that is a widget to collect the comment and post it to Firestore. This overall works, expect when I submit a comment via CommentCollector, the UserPostGetter widget does not does not rebuild.
How can I trigger this rebuild? I thought using StreamBuilder to listen to see if there is a comment would be enough, but clearly not so. What am I missing?
Really appreciate the help.
class UserPostGetter extends StatelessWidget {
final String articleId;
final String articleHeader;
UserPostGetter({this.articleId, this.articleHeader});
#override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
body: new Container(
child: new FutureBuilder<FirebaseUser>(
future: FirebaseAuth.instance.currentUser(),
builder: (context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
String userNumber = snapshot.data.uid;
return new FutureBuilder(
future: getUser(userNumber),
builder: (context, AsyncSnapshot<User> snapshot) {
if (snapshot?.data == null)
return new Center(
child: new Text("Loading..."),
);
String username = snapshot.data.username.toString();
return StreamBuilder(
stream: doesNameAlreadyExist(articleId, username),
builder: (context, AsyncSnapshot<bool> result) {
if (!result.hasData)
return Container(); // future still needs to be finished (loading)
if (result
.data) // result.data is the returned bool from doesNameAlreadyExists
return PostGetter(
articleId: articleId,
);
else
return CommentCollector(
articleID: articleId,
userName: username,
articleTitle: articleHeader,
);
},
);
},
);
} else {
return new Text('Loading...');
}
},
),
),
);
}
}
Stream<bool> doesNameAlreadyExist(String article, String name) async* {
final QuerySnapshot result = await Firestore.instance
.collection('post')
.where('article', isEqualTo: article)
.where('author', isEqualTo: name)
.getDocuments();
final List<DocumentSnapshot> documents = result.documents;
yield documents.length == 1;
}
Future<User> getUser(idNumber) {
return Firestore.instance
.collection('user')
.document('$idNumber')
.get()
.then((snapshot) {
try {
return User.fromSnapshot(snapshot);
} catch (e) {
print(e);
return null;
}
});
}
class User {
String email;
String user_id;
String username;
User.fromSnapshot(DocumentSnapshot snapshot)
: email = snapshot['email'],
user_id = snapshot['user_id'],
username = snapshot['username'];
}

Resources