In my app I have a model which consists of the store name and store image and looks like this:
class StoreModel
{
String? imageofStore;
String? storeName;
StoreModel({ this.imageofStore, this.storeName});
//data from server
factory StoreModel.fromMap(map)
{
return StoreModel(
imageofStore: map['imageofStore'],
storeName: map['storeName'],
);
}
// data to server
Map<String, dynamic> toMap(){
return{
'imageofStore': imageofStore,
'storeName': storeName,
};
}
}
and my database for stores looks like this:
to call the store name I use initstate and setState as such:
class addStore extends StatefulWidget {
const addStore({Key? key}) : super(key: key);
#override
_addStoreState createState() => _addStoreState();
}
class _addStoreState extends State<addStore> {
User ? user = FirebaseAuth.instance.currentUser;
StoreModel storebox = StoreModel();
#override
void initState()
{
super.initState();
FirebaseFirestore.instance
.collection("stores")
.doc("XQjbm665g2a2xAiiydjr")
.get()
.then((value){
this.storebox = StoreModel.fromMap(value.data());
setState(() {
});
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Text("${storebox.storeName}"),
);
}
}
With this, I get the store name of the store with id XQjbm665g2a2xAiiydjr displaying but the thing is I want to get the name of all the stores. I know I need to change the .doc() but im not sure as to what I am to put in it that will start displaying all the names. Can someone please help?
By providing a document id, what you're getting is a DocumentSnapshot which is the data of a particular Document, but when you remove it, you get QuerySnapshot which is a list of the data of all the documents. So, to read all, you change your code as:
List<StoreModel> storesList = [];
FirebaseFirestore.instance
.collection("stores")
.get()
.then((value){
//Now, this value is of type QuerySnapshot unlike earlier.
if (value != null && value.docs.isNotEmpty) {
//If it comes here means the collection is not empty.
//Because this value is a list of DocumentSnapshot, We've to map it to extract documents.
//After mapping, returning it as a list and assigning it to storesList
storesList = value.docs.map((doc) => StoreModel.fromMap(doc.data())).toList();
setState(() {
});
} else {
//If it comes here means there are no documents in your collection.
//Notify User there's no data.
}
});
This code will get all the documents your collection have, but, you can limit or filter using limit or where respectively, just place .limit(int) or .where() before .get().
for model try to convert to json or you can use your either way this is just for example model
import 'dart:convert';
List<StoreModel> storeModelFromJson(String str) => List<StoreModel>.from(json.decode(str).map((x) => StoreModel.fromJson(x)));
String storeModelToJson(List<StoreModel> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class StoreModel {
StoreModel({
this.imageofStore,
this.storeName,
});
final String? imageofStore;
final String? storeName;
factory StoreModel.fromJson(Map<String, dynamic> json) => StoreModel(
imageofStore: json["imageofStore"] == null ? null : json["imageofStore"]!,
storeName: json["storeName"] == null ? null : json["storeName"]!,
);
Map<String, dynamic> toJson() => {
"imageofStore": imageofStore == null ? null : imageofStore!,
"storeName": storeName == null ? null : storeName!,
};
}
try this get all list
static Future<List<StoreModel>> getStorelist() async {
List<dynamic> list = [];
await FirebaseFirestore.instance
.collection("stores")
.get()
.then((value){
for(var x in value.docs){
final Map<String,dynamic> toMap = x.data() as Map<String,dynamic>;
/// Try to print all data first to see if fetching use log to view on terminal
log(toMap.toString());
list.add(toMap);
}
});
return list.map((e)= > StoreModel.fromJson(e)).toList();
}
as in my case to get only 1 data
static getSingleName(String? uid) async {
final result =
await FirebaseFirestore.instance
.collection("stores").doc(uid).get();
final response = result.data() as Map<String, dynamic>;
return response['storeName'].toString();
}
Related
I have two queries for Firebase. One that returns a list of posts ("PostsModel") and one that returns a list of liked posts for a particular user ("LikedPostsModel"). I want to set an attribute called "liked" within "PostsModel" to true if it's also in the liked posts streams.
Pseudo Code:
/* Returns PostsModel, ex. [PostsModel(id: 1, liked: false), PostsModel(id: 2, liked: false), PostsModel(id: 3, liked: false), ...] */
Stream<List<PostsModel>> getListOfPosts();
/* Returns LikedPostsModel, ex. [LikedPostsModel(postsmodel_id: 2), ...] */
Stream<List<LikedPostsModel>> getListOfLikedPosts();
/* Returns combining PostsModel and LikedPostsModel, ex. [PostsModel(id: 1, liked: false), PostsModel(id: 2, liked: true), PostsModel(id: 3, liked: false), ...] */
Stream<List<LikedPostsModel>> getCombinedPosts() {
var listOfPosts = getListOfPosts();
var listOfLikedPosts = getListOfLikedPosts();
if PostsModel.id within listOfPosts is matches LikedPostsModel.postsmodel_id within listOfLikedPosts, then set PostsModel.liked = true;
return Stream<List<PostsModel>> with some fields "liked" set to true;
}
Actual Code:
My query to retrieve all posts:
Stream<List<PostsModel>> getListOfPostsStream() {
return _firestore
.collection("posts")
.snapshots()
.map((QuerySnapshot<Map<String, dynamic>> query) {
List<PostModel> postsModelList = [];
query.docs.forEach((element) {
postsModelList.add(PostsModel.fromDocumentSnapshot(element));
});
return postsModelList;
});
}
My query to retrieve liked posts:
#override
Stream<List<LikedPostsModel>> getLikedPostsByUser(User user) {
return _firestore
.collection("users")
.doc(user.id)
.collection("liked_posts")
.snapshots()
.map((QuerySnapshot<Map<String, dynamic>> query) {
List<LikedPostsModel> likedModelList = [];
query.docs.forEach((element) {
likedModelList.add(LikedPostsModel.fromDocumentSnapshot(element));
});
return likedModelList;
});
}
PostsModel
class PostsModel {
late String id;
late String bodyText;
bool liked = false;
PostsModel(
this.id,
this.bodyText,
);
...
}
LikedPostsModel
class LikedPostsModel {
late String id;
late String postsmodel_id;
bool liked;
LikedPostsModel(
this.id,
this.postsmodel_id,
this.liked
);
...
}
Any help is appreciated. Thanks!
To achieve it you should use the combineLatest2 method of the RxDart package.
I used it when developing a chat project. You know, at the conversations page of WhatsApp lists all chats and shows us the talker and the last message, message time, and seen information. My problem was my talker detail values and chat metadatas were in separate collections and I had to show the profile image and name of a talker and the last message, last message time and isSeen of the chat at the same time.
Here is an example from my chat project;
Stream<List<Chats>> getChats() {
final _userId = FirebaseAuth.instance.currentUser?.uid;
return _firestore
.collection('Chats')
.where('members', arrayContains: _userId)
.orderBy("lastMessageDate", descending: true)
.snapshots()
.map((convert) {
return convert.docs.map((f) {
String? talkerId;
var members = f.data()['members'];
if (members.length == 2) {
talkerId = members[0] != _userId ? members[0] : members[1];
}
Stream<ChatMetadata> chatStream = Stream.value(f).map<ChatMetadata>(
(document) => ChatMetadata.fromMap(
f.reference.path,
document.data(),
),
);
Stream<Talker> talkerStream = _firestore
.collection("Users")
.where('uid', isEqualTo: talkerId)
.snapshots()
.map<Talker>(
(querySnapshot) => querySnapshot.docs
.map((e) => Talker.fromMap(
e.data(),
))
.first,
);
return Rx.combineLatest2(
chatStream,
talkerStream,
(ChatMetadata chatMetadata, Talker talker) =>
Chats.combine(chatMetadata, talker),
);
});
}).switchMap((observables) {
return observables.isNotEmpty
? rx.Rx.combineLatestList(observables)
: Stream.value([]);
});
}
As you can see the getChats() method returns a Stream<List<Chats>>. The Chats model is a class that combines two other classes like ChatMetadata and Talker. Here is my Chats model;
import '/model/talker.dart';
import '/model/chat_metadata.dart';
class Chats {
String? docId;
String? talkerId;
String? talkerUsername;
String? talkerUnvan;
String? talkerProfileImage;
String? lastMessage;
DateTime? dateTime;
String? lastMessageOwner;
bool? isSeen;
List? members;
Chats({
this.docId,
this.talkerId,
this.talkerUsername,
this.talkerUnvan,
this.isSeen,
this.talkerProfileImage,
this.lastMessage,
this.dateTime,
this.lastMessageOwner,
this.members,
});
factory Chats.combine(ChatMetadata chat, Talker talker) {
return Chats(
docId: chat.docId,
talkerId: talker.id,
talkerUsername: talker.username,
talkerUnvan: talker.unvan,
isSeen: chat.isSeen,
talkerProfileImage: talker.photoUrl,
lastMessage: chat.lastMessage,
dateTime: chat.lastMessageDate,
lastMessageOwner: chat.lastMessageOwner,
members: chat.members,
);
}
}
You have to pay attention to the Rx.combineLatest2 method and switchMap method at the end of the code.
You can research all of them but in summary, the combineLatest2() merges the given Streams into a single Stream sequence by using the [combiner] function whenever any of the stream sequences emits an item. Then switchMap() converts each emitted item into a Stream using the given mapper function. Because the getChats() method has to returns the Stream<List<Chats>>.
You can store your likes in a Map rather than a List.
Stream<Map<String, bool>> getLikedPostsByUser(User user) {
return _firestore
.collection("users")
.doc(user.id)
.collection("liked_posts")
.snapshots()
.map((QuerySnapshot<Map<String, dynamic>> query) {
final likedPostsMap = HashMap<String, bool>();
query.docs.forEach((element) {
final model = LikedPostsModel.fromDocumentSnapshot(element);
likedPostsMap[model.postmodel_id] = model.liked;
});
return likedPostsMap;
});
}
Then use the resulting Map for a constant time lookup when populating your list of posts.
Stream<List<PostsModel>> getListOfPostsStream(Map<String, bool> likedPosts) {
return _firestore
.collection("posts")
.snapshots()
.map((QuerySnapshot<Map<String, dynamic>> query) {
List<PostModel> postsModelList = [];
query.docs.forEach((element) {
final model = PostsModel.fromDocumentSnapshot(element);
model.liked = likedPosts[model.id] ?? false;
postsModelList.add(model);
});
return postsModelList;
});
}
To improve performance, likes can be persisted locally so they don't have to be fetched each time your app is opened. Add a timestamp to your likes document to limit your query to changes since the last time you fetched.
I have a model and I want to use my services file to fill it from Firebase but I don't know how to do that ?
I am filling it with FutureBuilder that's okey. But it is exhausting me.
Here is my model:
class ReviewModel {
String? uid;
String? userID;
String? comment;
dynamic rate;
ReviewModel({
this.uid,
this.userID,
this.comment,
this.rate,
});
Map<String, dynamic> toMap() {
return {
'uid': uid,
'userID': userID,
'comment': comment,
'rate': rate,
};
}
factory ReviewModel.fromMap(Map<String, dynamic> map) {
return ReviewModel(
uid: map['uid'],
userID: map['userID'],
comment: map['comment'],
rate: map['rate'],
);
}
factory ReviewModel.fromDatabase(
DocumentSnapshot snapshot, Map<String, dynamic> map) {
return ReviewModel(
uid: snapshot['uid'],
userID: map['userID'],
comment: map['comment'],
rate: map['rate'],
);
}
}
Code is Following below,
Future<ReviewModel> getSalonReviews(String salonUID) async {
CollectionReference aRef = FirebaseFirestore.instance
.collection("salons")
.doc(salonUID)
.collection('bucket')
.doc('reviewbox')
.collection('reviews');
dynamic _doc;
var snapshot;
try {
await aRef.get().then((querySnapshot) => {
for (var dummyDoc in querySnapshot.docs)
{
_doc = dummyDoc.data(),
print(_doc),
}
});
return ReviewModel.fromMap(_doc);
} on FirebaseException catch (e) {
Get.snackbar("Hata", e.code);
rethrow;
}
}
This code is not returning my ReviewModel.
Also I am using GetX and this is my GetX code:
final Rx<ReviewModel> _reviewModel = ReviewModel().obs;
ReviewModel get reviewModel => _reviewModel.value;
set reviewModel(ReviewModel value) => _reviewModel.value;
Future fillReviewModel(String uid) async {
SalonController.instance.reviewModel =
await FakeService().getSalonReviews(uid);
}
it return me this:
And this is my Firebase docs:
How do I achive my ReviewModel with Obx. If I try it, it returns null.
You don't have to return a model you'll do something like this in your prvoider file:
List _reviews = [];
List get reviews => [..._reviews];
// IN your future void function
Future<void> myFunction () async{
myReviews = ...result of forEach;
// now update _reviews
_reviews = [...myReviews];
//And then notify listeners
notifylisteners;
}
And then in your futurebuilder
FutureBuilder(future: Provider.of<myClass>(context, listen:false).myFunction(),
builder:(context, snapshot){
// check the state like the following
if(snapshot.connectionState == ConnectionState.done){
final myValues = Provider.of<myClass>(context, listen:false).reviews;
...do something
return your_values}
if(snapshot.connectionState == ConnectionState.waiting){return progressIndicator}
})
I am having an issue with my FutureBuilder in flutter. It's supposed to map the custom class 'GeoPost' to a 'List' from a firebase query but somewhere in my code it is not reading the Geopost list as a non Future
here is the code where the error occurs:
FutureBuilder(
future: postsRef.get().then((doc) {
geoPosts = doc.docs.map((doc) async => await GeoPost.fromDoc(doc)).toList();
}),
builder: (context, snapshot) { }
custom class and functions:
class GeoPost {
final String caption;
final String postId;
final String authorId;
final int likesCount;
final int commentCount;
final Position position;
final Timestamp timeStamp;
final String? imageUrl;
final Userdata authordata;
GeoPost(this.caption, this.postId, this.authorId, this.likesCount, this.commentCount, this.position, this.timeStamp, this.imageUrl, this.authordata);
static Future<GeoPost> fromDoc(DocumentSnapshot doc) async {
return GeoPost.fromDocument(doc, await getPostUserDataFromFirestore(doc['userId']));
}
factory GeoPost.fromDocument(DocumentSnapshot doc, Userdata author) {
return GeoPost(
doc['caption'],
doc['postId'],
doc['userId'],
doc['likeCount'] ?? 0,
doc['commentCount'] ?? 0,
doc['position'],
doc['timeStamp'],
doc['imageUrl'],
author
);
}
Future<void> getPostUserDataFromFirestore (String did) async {
return Userdata.fromDoc(await usersRef.doc(uid).get());
}
}
error:
Error: A value of type 'List<Future<GeoPost>>' can't be assigned to a variable of type 'List<GeoPost>?'.
You need to wait for all the Futures in the List<Future<GeoPost> (which converts them into a List<GeoPost>) before assiging them to a variable of type List<GeoPost>.
Update your FutureBuilder code to this:
FutureBuilder<List<GeoPost>>(
future: () async {
var doc = await FirebaseFirestore.instance.collection('collectionPath').get();
var data = doc.docs.map((doc) async => await GeoPost.fromDoc(doc)).toList();
geoPosts = await Future.wait(data);
return geoPosts;
}(),
builder: (context, snapshot) { }
Also, you need to change the return type of the getPostUserDataFromFirestore method from Future<void> to Future<Userdata>. void is used when there is no value returned.
Update the getPostUserDataFromFirestore method to this:
Future<Userdata> getPostUserDataFromFirestore (String did) async {
return Userdata.fromDoc(await usersRef.doc(uid).get());
}
I have a firebase collection named 'reviews' with a sub-collection 'clients'.
I am looking to fetch all reviews in realtime with their owners from Firebase Firestore but I got a bit lost when it came to correctly mapping the data and returning the listener's result.
This is 'reviews' model:
class Review {
final String reviewTitle;
final String reviewContent;
final String reviewCategory;
final String reviewTimestamp;
final int reviewVotesCount;
final Client client;
Review(
{this.reviewTitle,
this.reviewContent,
this.reviewCategory,
this.reviewTimestamp,
this.reviewVotesCount,
this.client});
}
This is the Service class:
class ReviewService {
var currentUser = FirebaseAuth.instance.currentUser;
var firestoreInstance = FirebaseFirestore.instance;
List<Review> fetchAllThreads() {
Review review;
Client client;
List<Thread> mReviewsList = new List<Review>();
firestoreInstance.collection('reviews').snapshots().listen((result) {
result.docs.forEach((result) {
firestoreInstance
.collection('reviews')
.doc(result.id)
.collection('clients')
.get()
.then((result) {/*here I get the result.data()*/});
});
});
}
Question after I get result.data() how can I map it to my model so I can add the result object to mReviewsList and then return mReviewsList ?
You can add a factory constructor in your Review class to create it from a Map and same applies for Client.
factory Review.fromMap(Map<String, dynamic> map) {
if (map == null) return null;
return Review(
reviewTitle: map['reviewTitle'],
reviewContent: map['reviewContent'],
reviewCategory: map['reviewCategory'],
reviewTimestamp: map['reviewTimestamp'],
reviewVotesCount: map['reviewVotesCount'],
client: Client.fromMap(map['client']),
);
}
If you're using VS Code, 'Dart Data Class Generator' extension can be handy there, and also there are multiple code generation packages in pub.dev for serialization and deserialization
Now in the place of your comment, you can do this:
mReviewsList.add(Review.fromMap(result.data()));
Update:
Based on Doug's comment, if you like to map your the data to your model and return a stream, you can create a helper function as follow:
Stream<List<T>> collectionStream<T>({
#required String path,
#required T builder(Map<String, dynamic> data),
}) {
final reference = FirebaseFirestore.instance.collection(path);
final snapshots = reference.snapshots();
return snapshots
.map((snapshot) => snapshot.docs.map((snapshot) => builder(snapshot.data())).toList());
}
To use it, simply call it as follow:
final stream = collectionStream<Review>(path: "reviews", builder: (data) => Review.fromMap(data));
if you like to fetch the data only once, you can create a helper function for that too:
Future<List<T>> getDocuments<T>({
String path,
#required T builder(Map<String, dynamic> data),
}) async {
final reference = FirebaseFirestore.instance.collection(path);
final snapshots = await reference.get();
final docs = snapshots.docs.map((doc) => builder(doc.data())).toList();
return docs;
}
and call it the same way:
final reviews = getDocuments<Review>(path: "reviews", builder: (data) => Review.fromMap(data));
I have the following problem. I'm trying to receive data from my Firestore collection called tournaments. I'm querying the database from within my DatabaseService class. That looks like the following:
class Collection<T> {
final Firestore _db = Firestore.instance;
final String path;
CollectionReference ref;
Collection({this.path}) {
ref = _db.collection(path);
}
Future<List<Tournament>> getData() async {
var snapshots = await ref.getDocuments();
return snapshots.documents
.map((doc) => Global.models[Tournament](doc.data))
.toList();
}
}
The widget implements a FutureBuilder
Widget build(BuildContext context) {
return FutureBuilder(
future: Global.tournamentRef.getData(),
builder: (BuildContext context, AsyncSnapshot snap) {
if (snap.connectionState == ConnectionState.done) {
List<Tournament> tournaments = snap.data;
...
I want to deserialize the firestore data into a Tournament object. I defined the Tournament class as this:
class Tournament {
String id;
String name;
String mode;
String owner;
int size;
Tournament({this.id, this.name, this.mode, this.owner, this.size});
factory Tournament.fromMap(Map data) {
return Tournament(
id: data["id"] ?? '',
mode: data["mode"] ?? '',
name: data["name"] ?? "group",
owner: data["owner"] ?? "",
size: data["size"] ?? 6);
}
}
The last class that is important is the globals.dart
class Global {
static final Map models = {Tournament: (data) => Tournament.fromMap(data)};
static final Collection<Tournament> tournamentRef =
Collection<Tournament>(path: "tournaments");
}
It simply specifies the collection path. I want the data to be deserialized but I don't have a clue why it isn't returning anything. I've tried querying the database in a simple old-fashioned way like
Future<dynamic> retrieveData() async {
var querySnap = await ref.getDocuments();
var list = querySnap.documents.map((snap) => snap.data);
return list;
}
That worked perfectly fine, however it doesn't help with deserialization at all. I think i missed something at some point and as you may notice I'm still a Flutter/dart beginner and some of these discussed topics are a bit too complicated to me.
I appreciate any help.
Thank you