Reputation: 21
I am trying to get a bool value from Firestore when the app is being initialized: it returns True if it is "like" and False if it is not "like". Every time a user likes/unlikes a post, a database (called userFavorites) is being created or update on Firestore. The userFavorite database is composed of: document (user's ID), collection ('posts'), document (post's ID), collection (isLiked: true OR isLiked: false). So when initializing the app, I'm trying to get access to this True/False for each of the posts that are being displayed on the UI (if the user has never liked or unliked the post, the value for this bool will automatically be False).
I would really appreciate if you can give me feedback/corrections on the code I use to get the True/False bool value from Firestore, because even though I am not getting any errors, the bool value on my IU is Null, and I don't know whether I made an error in this part of my code or in another part.
Here is the code I used:
class HomeFeed extends StatelessWidget {
final user = FirebaseAuth.instance.currentUser;
ValueKey valueKey;
Future<DocumentSnapshot> getDocumentSnapshotForCurrentUserLikes(String userId, String documentId) async {
final String userId = user.uid;
final String documentId = valueKey.value;
return await FirebaseFirestore.instance.collection('userFavorites').doc(userId).collection('posts').doc(documentId).get();
}
bool getCurrentUserLikesValue(DocumentSnapshot documentSnapshotForCurrentUserLikes) {
return documentSnapshotForCurrentUserLikes.data()['isLiked'];
}
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseFirestore.instance.collection('post').doc('Post in Feed').collection('posts').orderBy('createdAt', descending: true,).snapshots(),
builder: (ctx, AsyncSnapshot<QuerySnapshot> postSnapshot) {
if (postSnapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
final postDocs = postSnapshot.data.docs;
return ListView.builder(
reverse: false,
itemCount: postDocs.length,
itemBuilder: (ctx, index) {
ValueKey valueKey = ValueKey(postDocs[index].id);
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
child: PostContainer(
user.uid,
postDocs[index].data()['likes'],
getCurrentUserLikesValue(postDocs[index]) == null ? false : getCurrentUserLikesValue(postDocs[index]),
key: valueKey,
),
),
);
},
);
},
);
}
}
Thank you for the input! Do you mean using something like this:
IconButton(
icon: Icon(
isLiked == true ? Icons.favorite : Icons.favorite_border,
color: Colors.red,
size: 25.0,
),
onPressed: () async{
DocumentReference docRef = FirebaseFirestore.instance.collection('userFavorites').doc(this.widget.userId);
DocumentSnapshot doc = await docRef.get();
List userLikedPosts = doc.data['userLikedPosts'];
if(userLikedPosts.contains(documentId)==true) {
docRef.update({'userLikedPosts' : FieldValue.arrayRemove([documentId])});
} else {
docRef.update({'userLikedPosts' : FieldValue.arrayUnion([documentId])});
}
If this is kind of code you are referring to, how can I use it with "set", instead of "get" (because if I use "get", the user reference would have to be created in Firebase beforehand for every user, which would be inefficient)? Also for doc.data['userLikedPosts'], I get an error for ['userLikedPosts'], which says "The operator '[]' isn't defined for the type 'Map<String, dynamic> Function()'. Try defining the operator '[]'." How would you solve this? Thanks a lot for the help!
Hello! I have been researching and working on it for a while, and the problem that I am having is that I am not able to get and display the T/F bool value from the Firestore database into the UI each time a post is initialized (or seen on the screen). What code could I use to do that? I would really appreciate your help here. Thanks!
Upvotes: 0
Views: 383
Reputation: 1236
You're making your life much harder than it should be:
Consider a db structure like this:
userFavorite (collection)
|---{userId} (document)
|--- liked_posts (Array) [array of post Ids the posts the user liked]
|--- ['postA', 'postB', 'post3131',...]
By doing it this way, for each postID, you can just check if it exists in that liked_posts
array. This a cleaner way to do things. You don't need extra document and collection levels.
When the user clicks a "like" on a post, you can use ArrayUnion to add that postId to the liked_posts field of that user.
Update:
Sorry, I can't help you with Flutter code. All I can say is this:
? did the user like the post? If so, you can update the userLikedPosts
(Array) field WITHOUT reading it first. With ArrayUnion, if the postId is within that array, it won't be added, if it's not there it will be added, the other elements in the Array will not be changed.
? did the user dislike the post? If so, you can update userLikedPosts
(Array) field WITHOUT reading it first. With ArrayRemove, if the postId is within that array, it will be removed, if it's not there then nothing happens, the other elements in the Array will not be changed.
In your place, I would not use update():
docRef.update({'userLikedPosts' : FieldValue.arrayRemove([documentId])});
Instead, I would use set() with {merge:true}
:
docRef.set( {'userLikedPosts' : FieldValue.arrayRemove([documentId])}, {merge:true} );
ArrayUnion/ArrayRemove works flawlessly with set() and won't rewrite the array. Also, if the document doesn't exist, then it will be created automatically.
Sorry I can't help you with actual Flutter code, but my main point is that you do not need to read the document containing the userLikedPosts
Array when responding to LIKE/DISLIKE user actions. You only need to read it when displaying whether or not the post is liked by the user, and only on subsequent post page visits. When the user presses like, you can respond in the UI immediately and the logic above to update the db with set/merge:true and ArrayUnion.
Upvotes: 1