How do I make a subcollection of user data in Firebase? - firebase

I'm creating an Hospital App in flutter. My goal is to configure firebase in such a way that the root collection is named User/uid(uid which is automatically generated when user is created)/someText/documentID(automatically generated by Firebase). This is my code:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:mgm_app/models/vaccList.dart';
class DatabaseService {
final String uid;
DatabaseService({this.uid});
CollectionReference userReg = Firestore.instance.collection('User');
CollectionReference vaccTaken = Firestore.instance.collection('User').document(uid).collection('Vaccine Administered');
Future regUserData(String email) async {
return await userReg.document(uid).setData({
'email': email,
});
}
Future updateUserData(String vaccName,String dateAdmin) async {
return await vaccTaken.document().setData({
'name': vaccName,
'vaccine given': dateAdmin,
}
);
}
When a new user registers, a document with their uid is automatically created in the User collection. But when I'm trying to enter some personal data in the folder User/uid/VaccineAdmnistered,
I am not able to pass the value of the current user uid on this line of code
Firestore.instance.collection('User').document(uid).collection('Vaccine Administered');
The error i'm getting is
Only static members can be accessed in initializers.

You are using the uid inside the method document before the constructor is called. Therefore do the following:
class DatabaseService {
final String uid;
DatabaseService({this.uid});
CollectionReference vaccTaken = Firestore.instance.collection('User');
Future regUserData(String email) async {
return await vaccTaken.document(uid).setData({
'email': email,
});
}
Future updateUserData(String vaccName,String dateAdmin) async {
return await vaccTaken.document(uid).collection('Vaccine Administered').document().setData({
'name': vaccName,
'vaccine given': dateAdmin,
}
);
}
Create a variable vaccTaken which will reference the collection User, then inside the method updateUserData access the uid.

Related

How do I retrieve a document ID in Firestore after creating a user collection using Flutter

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
Future<void> userSetup(String displayName) async{
FirebaseAuth auth = FirebaseAuth.instance;
String uid = auth.currentUser.uid.toString();
CollectionReference users = FirebaseFirestore.instance.collection("Users");
users.add({
'displayName': displayName,
'uid': uid,
// 'docID': docID,
});
return;
}
Just to preface, I'm a complete beginner with Flutter.
This is what I'm using to add the collection in Firestore but I'd like to retrieve the the specific document ID and use it for further storage. Is there a method to obtain and store the document ID in the user details? I'm trying to retrieve the specific user's name when they login and display it on the homepage.
You may try this method
Preset the document id with your own, you may use the package uuid
https://pub.dev/packages/uuid
or use the user's uid as the doc id
//final docID = Uuid().v4(); //use custom uuid for doc id
//final docID = uid; //use user id as doc id
users.doc(docID).set({
'displayName': displayName,
'uid': uid,
});
Then save the docID to user device with shared_preferences for persistant storage
https://pub.dev/packages/shared_preferences
If you want to store the ID inside the document, you can first create the DocumentReference for the new document by calling CollectionReference.doc() without parameters:
CollectionReference users = FirebaseFirestore.instance.collection("Users");
var newDocRef = users.doc();
var newDocId = newDocRef.id
users.add({
'displayName': displayName,
'uid': uid,
'docID': newDocId
});
You can retrieve **firestore** doc id only when you retrieve the data using a stream builder. Take a look at the code.
Streambuilder(
stream: Firestore.instance.collection("Your collection").snapshot(),
builder: (context,snapshot) {
if(snapshot.hasData) {
List<User> users = []
for(var doc in sanpshot.data.documents) {
users.add(
new User(
name: doc.data["name"],
email: doc.data["email"]
**id: doc.documentID**
)
)
return Column(
children: users
)
}
}
}
)
In the case that you do not want to use custom IDs for your documents but instead rely on firebase auto generated IDs you can do the following.
Future<void> userSetup(String displayName) async {
FirebaseAuth auth = FirebaseAuth.instance;
String uid = auth.currentUser.uid.toString();
CollectionReference users = FirebaseFirestore.instance.collection("Users");
users.doc(uid).set({
'displayName': displayName,
});
return;
}
This will set the document ID to be the same as the users uid. The benefit of this approach is two fold.
You do not have to set an additional id property in the document.
You can now query the users collection for the specific user using the currently signed in users uid
At runtime, components are able to read the contents of their own package by accessing the path /pkg/ in their namespace. The resource() template may be used to add contents to the package that may be accessed this way.

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.

How to create a collection inside a uid document in Firestore using Flutter?

How do you create a sub-collection in Firestore with Flutter based on a dynamic uid?
Architecture should look like this:
users (collection) --> UID (document - unique to each user) --> vault (collection) --> credential (document)
I am currently using the implementation below, which obviously when called just creates the architecture I need except for using a string "uid" in place of the dynamic uid of the user as the document that the vault collection resides in. How to change this so that the document('uid') element is the actual uid document of the current user rather than just the string "uid"?
import 'package:mindpass/models/user.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class DatabaseService {
final String uid;
DatabaseService({ this.uid });
String credential;
// collection reference
final CollectionReference vaultCollection = Firestore.instance.collection('users').document('uid').collection('vault');
Future<void> updateVaultData(String credUN, String timeStamp) async {
return await vaultCollection.document(credential).setData({
'credUN': '"aribtraryUN"',
'timeStamp': Timestamp.now()
});
}
If the user is signed in with Firebase Authentication, you can get their UID with:
var user = await FirebaseAuth.instance.currentUser();
var uid = user.uid;
And then get their vault collection with:
final CollectionReference vaultCollection = Firestore.instance.collection('users').document(uid).collection('vault');

How to delete firebase account when user data is deleted on flutter?

is it possible to delete firebase account in authentication on flutter? if yes, how to do that? I have been search but not found the way.
Firestore.instance.collection("users").document(uid).delete().then((_){
// delete account on authentication after user data on database is deleted
});
Using flutter, if you want to delete firebase accounts together with the associated firestore user collection document, the following method works fine. (documents in user collection named by the firebase uid).
Database Class
class DatabaseService {
final String uid;
DatabaseService({this.uid});
final CollectionReference userCollection =
Firestore.instance.collection('users');
Future deleteuser() {
return userCollection.document(uid).delete();
}
}
Use Firebase version 0.15.0 or above otherwise, Firebase reauthenticateWithCredential() method throw an error like { noSuchMethod: was called on null }.
Authentication Class
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
Future deleteUser(String email, String password) async {
try {
FirebaseUser user = await _auth.currentUser();
AuthCredential credentials =
EmailAuthProvider.getCredential(email: email, password: password);
print(user);
AuthResult result = await user.reauthenticateWithCredential(credentials);
await DatabaseService(uid: result.user.uid).deleteuser(); // called from database class
await result.user.delete();
return true;
} catch (e) {
print(e.toString());
return null;
}
}
}
Then use the following code inside the clickable event of a flutter widget tree to achieve the goal;
onTap: () async {
await AuthService().deleteUser(email, password);
}
Code for deleting user:
FirebaseUser user = await FirebaseAuth.instance.currentUser();
user.delete();
To delete a user account, call delete() on the user object.
For more on this, see the reference documentation for FirebaseUser.delete().
User user = FirebaseAuth.instance.currentUser;
user.delete();
From this you can delete user

Waiting for result of Future<DocumentSnapshot> in Flutter Firestore

I have a cloud FireStore database with 2 fields.
imageUrl (url of a remote file)
user (reference field of a document in users collection)
Below is how I get the documents from the images collection.
class ImagePost {
final String imageUrl;
final User user;
const ImagePost(
{this.imageUrl,
this.user});
factory ImagePost.fromDocument(DocumentSnapshot document) {
User userInfo;
DocumentReference userReference = document['user'];
Future<DocumentSnapshot> userRef = userReference.get();
userRef.then((document) {
userInfo = User.fromJSON(document.data);
});
ImagePost post = new ImagePost(
imageUrl: document['imageUrl'],
user: userInfo // ==> always null while returning
);
return post;
}
}
When getting the reference user document, the post object always contains null value for user field. I expect the user object to be populated.
But the user value is retrieved late and not returned along with the post object.
How can I ensure that user value is retrieve before returning the post value?
That's because the get() method return a Future and you need to use async 'await' in order to wait for the response , but is not posible to use it in your constructor.
Just create a method (not constructor) and use like this :
Future<ImagePost> getImagePostFromDocument(DocumentSnapshot document) async {
DocumentReference userReference = document['user'];
DocumentSnapshot userRef = await userReference.get();
User userInfo = User.fromJSON(userRef);
ImagePost post = new ImagePost(
imageUrl: document['imageUrl'],
user: userInfo
);
return post;
}
I recommend you to call it form a FutureBuilder

Resources