Map an Array of objects from Firebase to List Flutter - firebase

I'm trying to retrieve an array of objects from Firebase and store it in Flutter Object as a List.
This is the collection, Firebase,
And this is the model class
class Merchant {
String shopName;
String address;
String description;
String thumbNail;
LatLng locationCoords;
Merchant(
{this.shopName,
this.address,
this.description,
this.thumbNail,
this.locationCoords});
}
final List<Merchant> merchant = []; // Map it to This List
I'd like to map it into this list above
final List<Merchant> merchant = [];

First add this method to your Merchant class:
Merchant.fromMap(Map<String, dynamic> map) {
shopName = map['shopName'];
address = map['address'];
description = map['description'];
thumbnail = map['thumbnail'];
locationCoords = map['locationCoords'];
}
This method will then be used to write the data into the Merchant Class/Struct.
Retrieving the data the could look something like the following:
import 'package:cloud_firestore/cloud_firestore.dart';
FirebaseFirestore _firestore = FirebaseFirestore.instance;
CollectionReference merchRef = _firestore.collection('merchants'));
Future<List<Merchant>> getAllMerchants() async {
List<Merchant> merchantList = [];
await merchRef.get().then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
Merchant merchant = Merchant.fromMap({
'shopName': doc['shopname'],
'address': doc['address'],
'description': doc['description'],
'thumbnail': doc['thumbnail'],
'locationCoords': doc['location'],
});
merchantList.add(merchant);
});
});
return merchantList;
}
P.S.:
HavenĀ“t tried this out yet and you might need some parsing for locationCoords, since it is of type LatLng.

Related

Flutter Firestore Read specific field from specific document via stream function

basically im struggling to create a function in my database.dart where i can call this function to display a specfic field in a specific document.
Example, i want to use Database.readUserProfileData('Profile', 'surname') to display the string/alt to a widget.
static Future<QuerySnapshot> readUserProfileData({
required String docId,
required String field,
}) async {
var ref = _mainCollection.get().then((querySnapshot) {
querySnapshot.docs.forEach((result) {
Object? data = result.data();
print(data);
});
});
}
Here is my database.dart
(please ignore the UpdateItem function as i have not configured it properly as i copied this from a template, or maybe update it for me lol sorry ;))
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/cupertino.dart';
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
/* ------------------ Database Reference - Main Collection ------------------ */
final CollectionReference _mainCollection = _firestore.collection('_TestFB');
class Database {
static String? userUid;
/* -------------------------- Create User Database -------------------------- */
static Future<void> createUserDataFile({
required String uid,
required String surname,
required int mobile,
}) async {
DocumentReference documentReferencer =
_mainCollection.doc('UserData').collection(uid).doc('Profile');
Map<String, dynamic> data = <String, dynamic>{
"surname": surname,
"mobile": mobile,
};
_mainCollection
.doc('UserData')
.collection(uid)
.limit(1)
.get()
.then((snapshot) async {
if (snapshot.size == 1) {
print("**User Profile Exists");
} else {
await documentReferencer
.set(data)
.whenComplete(() => print("**New Profile Created for - " + uid))
.catchError((e) => print(e));
}
});
}
/* ------------------------- Read User Profile Data ------------------------- */
static Future<QuerySnapshot> readUserProfileData({
required String docId,
required String field,
}) async {
var ref = _mainCollection.get().then((querySnapshot) {
querySnapshot.docs.forEach((result) {
Object? data = result.data();
print(data);
});
});
}
Thanks in Advance
If you already have the document id, then just do:
static Future<DocumentSnapshot> readUserProfileData({
required String docId,
required String field,
}) async {
var ref = _mainCollection.doc(docId).get().then((snapshot) {
print(snapshot);
print(snapshot.data()[field]);
});
}
You don't need to use forEach since you are only retrieving one document and get() will have a return type Future<DocumentSnapshot>

Firebase Security Rules for users to read and write their own data

I'm working with Flutter and Firebase and came up with the following Firebase structure.
Room and user collections:
I want users to be able to read and write their data.
And I wrote the following rules:
For getting rooms, I've following code in Flutter:
Stream<List<RoomModel>> getRooms() {
var user = firebaseAuth.currentUser;
return _db
.collection('rooms')
// .where("userId", "==", user.uid)
.snapshots()
.map((snapshot) => snapshot.docs
.map((document) => RoomModel.fromFirestore(document.data()))
.toList());
}
Room Model:
// import 'package:firebase_auth/firebase_auth.dart';
class RoomModel {
final String roomId;
final double price;
final String location;
final String userId;
RoomModel(this.roomId, this.price, this.location, this.userId);
Map<String, dynamic> toMap() {
// final firebaseAuth = FirebaseAuth.instance;
// var user = firebaseAuth.currentUser;
return {
'roomId': roomId,
'price': price,
'userId': userId,
'location': location,
};
}
RoomModel.fromFirestore(Map firestore)
: roomId = firestore['roomId'],
price = firestore['price'],
location = firestore['location'],
userId = firestore['userId'];
}
But still, I'm getting all the rooms, including other user's room.
And later I also wanted to read all the rooms. How should I do it?
I would add subcollections. users/USERID/rooms/. To get All rooms use collectionGroup by adding an index
var myUserId = firebase.auth().currentUser.uid;
var myReviews = firebase.firestore().collectionGroup('room')
.where('userId', '==', myUserId);
myReviews.get().then(function (querySnapshot) {
// Do something with these rooms!
})
Read More here.
https://firebase.google.com/docs/firestore/query-data/indexing?authuser=0
https://firebase.googleblog.com/2019/06/understanding-collection-group-queries.html

Using a Firebase Stream as an input for another Stream in Flutter?

Context: I've got two Firebase Streams that work correctly, and they fetch i) a list of user profiles ('users' collection), and ii) a list of locations belonging to each user profile ('locations' collection), and then map them over to a custom User and Location model.
Users stream:
class DatabaseService {
final String uid;
final String friendUid;
final String locationId;
DatabaseService({ this.uid, this.locationId, this.friendUid });
// collection reference for users
final CollectionReference userCollection = FirebaseFirestore.instance.collection('users');
// get users stream
Stream<List<CustomUserModel>> get users {
final FirebaseAuth auth = FirebaseAuth.instance;
final User user = auth.currentUser;
final uid = user.uid;
List<CustomUserModel> userList = [];
List<CustomUserModel> _streamMapper(DocumentSnapshot snapshot) {
CustomUserModel individualUser = CustomUserModel(
uid: snapshot.id,
name: snapshot.data()['name'],
username: snapshot.data()['username'],
email: snapshot.data()['email'],
);
userList.add(individualUser);
return userList;
}
return userCollection.doc(uid).snapshots().map(_streamMapper);
}
and the Location Stream:
// collection reference for location
final CollectionReference locationCollection =
FirebaseFirestore.instance.collection('locations');
Stream<List<Location>> get locations {
final FirebaseAuth auth = FirebaseAuth.instance;
final User user = auth.currentUser;
final uid = user.uid;
List<Location> _locationListFromSnapshot(QuerySnapshot snapshot) {
List<Location> locationList = [];
snapshot.docs.forEach((element) {
Location individualLocation = Location(
locationId: element.id,
locationName: element.data()['locationName'],
city: element.data()['city'],
);
locationList.add(individualLocation);
});
return locationList;
}
return userLocationCollection.doc(uid).collection('locations').snapshots()
.map(_locationListFromSnapshot);
}
What I want to do is to generate a custom Stream which outputs all the locations for all users - in other words to use the users stream as an input for the locations stream.
I'm not sure what approach works here - I considered adding the users stream as an input parameter to the locations stream and then creating a for-loop, something like this:
Stream<List<Location>> allLocations(Stream<List<CustomUserModel>> users) {
final FirebaseAuth auth = FirebaseAuth.instance;
final User user = auth.currentUser;
final uid = user.uid;
List<Location> locationList = [];
users.forEach((element) {
// append user's locations to empty list
locationList.add(locationCollection.doc(element.first.uid).collection('locations')
.snapshots().map(SOME FUNCTION TO MAP A DOCUMENT SNAPSHOT TO THE CUSTOM LOCATION MODEL)
}
return locationList;
but of course I get an error as this returns a list, not a stream. So I've no idea how to proceed...
I hear your pain. I have been there. You were pretty close. Let me explain how I like to do it.
First of all, some clean up:
It seemed like you were not using these in the allLocations functions, so I deleted them
final FirebaseAuth auth = FirebaseAuth.instance;
final User user = auth.currentUser;
final uid = user.uid;
Second, I changed the return type of the function from Stream<List<Location>> to Stream<Map<String, List<Location>> where the key of the map would be the userId. I find this type useful, because you don't have to worry about the order of users being in sync with the stream.
Third, when you are creating streams, you cannot return, but have to yield from a function. You also have to mark the function async* (* is not a typo).
With this, I propose you use something like this for your allLocations function:
class DataService {
List<Location> convertToLocations(QuerySnapshot snap) {
// This is the function to convert QuerySnapshot into List<Location>
return [Location()];
}
Stream<Map<String, List<Location>>> allLocations(
Stream<List<CustomUserModel>> usersStream) async* {
Map<String, List<Location>> locationsMap = {};
await for (List<CustomUserModel> users in usersStream) {
for (CustomUserModel user in users) {
final Stream<List<Location>> locationsStream = locationCollection
.doc(user.uid)
.collection('locations')
.snapshots()
.map(convertToLocations);
await for (List<Location> locations in locationsStream) {
locationsMap[user.uid] = locations;
yield locationsMap;
}
}
}
}
}
I hope you like this method. Please let me know if something is not what you want. I can make adjustments.

Fllutter/Firestore - realtime listening to collections and subcollections

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

Get Firebase Entity Key upon its creation in Flutter

When creating a new entity in Firebase with the following test code:
class Person{
String name;
int age;
Person({this.name, this.age});
}
Person person = new Person(name: 'John', age: '42');
.
.
.
aMethod async() {
Firestore _firestore = Firestore.instance;
var firestoreResult = await _firestore.collection('Example').add({
'name': person.name,
'age': person.age
});
}
I want to retrieve its key from the firebase.
I saw in debugging mode that the firestoreResult returns the wanted key within its parameters like this:
firestoreResult._delegate._pathComponents[1]
The problem is that its parameters are private and thus I am getting the following error:
The getter _delegate is not defined for the class DocumentReference
To get the document id try the following:
var firestoreResult = await _firestore.collection('Example').add({
'name': person.name,
'age': person.age
});
var documentId = firestoreResult.documentID;
The add() method returns a Future of type DocumentReference and inside the class DocumentReference you can use the property documentID to get the id:
https://github.com/FirebaseExtended/flutterfire/blob/master/packages/cloud_firestore/cloud_firestore/lib/src/document_reference.dart#L39
var documentID = firestoreResult.documentID;

Resources