How to do a dart flutter firebase query join - firebase

I have a firestore table of users, and another firestore table of blogs which has uid on each document. I would like to do join query in flutter by joining the two tables by their uid. I can't find a way of doing this in the fire quiz state management. I wanted to define a new class class.
BlogData<T> {
final Firestore _db = Firestore.instance;
final String path;
CollectionReference ref;
BlogData({ this.path }) {
ref = _db.collection(path);
}
Future<List<T>> getData() async {
// get blog data and do a join here to a user document by uid (document/blog.uid) return a future of blogs with user data such as name, country added to each blog in the future list.
}
Stream<List<T>> streamData() {
// get blog data and do a join here to the user document by uid (document/blog.uid) return a stream of blogs with user data such as name, country added to each blog in the stream.
}
}
How can I achieve this?

Related

How to batch update arrays?

So I am creating an attendance app in which there is a list named attendees which contains the list of all students attending the lecture.
List<String> attendees = [uid0, uid1, uid2];
and the student data model is as follows
Student(
List<String> attendance = [at0, at1]; //this is the list of lectures attended
String name;
)
and the document name of every student is their user-id
How do I update the list of every student via the batch() function?
I assume you already have a list of attendees that contains user-id as you mentioned the userId would be the documentId in Firestore.
Therefore, you can do a loop and update your doc directly and then use commit to apply your changes.
updateBatch(List<String> attendees) async {
try {
final db = FirebaseFirestore.instance;
final WriteBatch batch = db.batch();
// based on this doc https://firebase.flutter.dev/docs/firestore/usage/#batch-write
// Each transaction or batch of writes can write to a maximum of 500 documents.
for (final userid in attendees) {
// Use the batch to update a document that exist otherwise it will throw error,
// in case you want to create that doc, maybe you should use `set` instead
// ref: https://pub.dev/documentation/cloud_firestore/latest/cloud_firestore/WriteBatch/update.html
batch.update(
//Give the ref of document.
db.document('Path/to/firestore/document'), // e.g: /Students/userId
//And the data to update.
{"key": value},
);
}
await batch.commit();
print('success');
} catch (e) {
print('error $e');
}
}
A few notes:
According to this doc, Each transaction or batch of writes can write to a maximum of 500 documents.
You can check out different WriteBatch methods doc here

Flutter and Firestore: How to update multiple specific documents from collection group

Okay, so I have multiple specific document ids that are all the same, but each one is used for different collections and purposes. Using a collection group as these documents are nested in subcollections, I was able to locate these documents and print them. How would I go about updating a specific field in all of these documents at the same time with some other data? In my case these documents despite being in different collections have a field called, plastics and I want to update them with int data.
Here is the code I used to retrieve these documents and filter to only print the one's I specificly need:
final String uid = FirebaseAuth.instance.currentUser.uid;
QuerySnapshot querySnapshot = await FirebaseFirestore
.instance
.collectionGroup("Members")
.get();
for (int i = 0; i < querySnapshot.docs.length; i++) {
var a = querySnapshot.docs[i];
if (a.id == uid) {
print(a.id);
}
}
Also here is some code I've used before for updating a field in a single document, but not all of them like I need to in this case.
Future<bool> addPlastic(String amount) async {
try {
String uid = auth.currentUser.uid;
var value = double.parse(amount);
DocumentReference documentReference =
FirebaseFirestore.instance.collection('some collection').doc(some document);
FirebaseFirestore.instance.runTransaction((transaction) async {
DocumentSnapshot snapshot = await transaction.get(documentReference);
if (!snapshot.exists) {
documentReference.set({'plastics': value});
return true;
}
double newAmount = snapshot.data()['plastics'] + value;
transaction.update(documentReference, {'plastics': newAmount});
return true;
});
} catch (e) {
return false;
}
}
You have two options, either loop through all the collections and sub collections (tedious), or store a list document references in one of the documents and change the data by looping through all of these document references ( better option). If you need some code or guidelines on how to do that, let me know.
EDIT
To use the document reference part, first, when creating the document, you have to do something like this
document(id).set({
"value1":"oko",
"plastic":"240",
//And the rest of your values
"otherSnapshots":[/*Your list of documentSnapshots with the same ID. Update it in this whenever you have new one by following the next snippet*/]
})
When you create a new document, navigate to this and add in the document Reference by
snapshot.reference.update({
'otherSnapshots':FieldValue.arrayUnion([/*Document Reference*/])
});
And next time, when you want to update all of them, use this field, loop through it and then you will get all the document references. I cannot create a code that you can directly copy paste into your code without seeing how u store data

Firestore query to variable to populate shared preferences

New to Flutter, and although I've been around code for many years, this is my first time actually developing. I've been trying to figure out how to query the Cloud Firestore and map one of those properties to a shared preference. Since my app is talking to the cloud firestore when a user logs in anyway, I feel like I can update my shared preferences with a few fields that will save me from having to call firestore everytime I want to retrieve that piece of information.
I created a class that represents the collection layout in the firestore
class FirestoreUser {
FirestoreUser(this.aboutMe, this.nickname);
final String aboutMe;
final String nickname;
factory FirestoreUser.fromMap(Map<String, dynamic> data) {
if (data == null) {
return null;
}
final String aboutMe = data['aboutMe'];
final String nickname = data['nickname'] ?? '';
return FirestoreUser(aboutMe, nickname);
}
Map<String, dynamic> toMap() {
return {
'aboutMe': aboutMe,
'nickname': nickname,
};
}
}
I also have a function that writes data to the Firestore collection. That works just fine.
What I'm having a hard time with is how do I query for "aboutMe" in the collection above? I'm using
a provider to get the uid, but how do I do something like this:
aboutMe = Firestore.query.tothatdocument
Here's what I have been working on so far:
final user = Provider.of<User>(context, listen: false);
final firestore = Provider.of<FirestoreService>(context, listen:false);
final firebaseUser = await FirebaseAuth.instance.currentUser();
final QuerySnapshot result = await Firestore.instance.collection('users').where('id', isEqualTo: firebaseUser.uid).getDocuments();
final List<DocumentSnapshot> documents = result.documents;
SharedPreferences prefs = await SharedPreferences.getInstance();
And I want to do something like this:
prefs.setString('aboutMe', 'thatAboutMeVariable);
Is this possible? I think I'm missing capturing the stream as a snapshot? but not to sure.
Thanks again!
I ended up creating a new class with functions to retrieve the fields that I wanted. I then assigned a variable to the results of that function.
Here's the call:
aboutMe = await FirestoreUserService(id: widget.id).getAboutMeFromFirestore();
Here's the class and function:
class FirestoreUserService {
FirestoreUserService({#required this.id});
final FirebaseFirestore firestore = FirebaseFirestore.instance;
CollectionReference users;
String aboutMe;
Future getAboutMeFromFirestore() async {
DocumentSnapshot ds = await FirebaseFirestore.instance.collection('users').doc(id).get();
aboutMe = ds.data()['aboutMe'];
//print('\nFirestore aboutMe is value: $aboutMe');
return aboutMe;
}
}

Firebaseui FirestoreRecyclerAdapter: How to use data pointed to by DocumentReference

My data is set up as follows:
- The Users collection contains User documents.
- A User document contains a Friends subcollection.
- A Friends subcollection contains UserRef documents.
- A UserRef document contains a DocumentReference to a User document.
I want to display all the friends of a particular user in a RecyclerView using the FirestoreRecyclerAdapter. From this answer, it seems like I cannot retrieve the User documents pointed to by a DocumentReference in a query. So, the following code is my attempt at using a SnapshotParser to do so. However, I don't know how to can return the User from parseSnapshot() since it is retrieved asynchronously.
Query query = friendsSubcollection;
FirestoreRecyclerOptions<User> options = new FirestoreRecyclerOptions.Builder<User>()
.setQuery(query, User.class, new SnapshotParser<User>() {
#NonNull
#Override
public User parseSnapshot(#NonNull DocumentSnapshot snapshot) {
DocumentReference userRef = snapshot.toObject(DocumentReference.class);
userRef.get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
User user = documentSnapshot.toObject(User.class); // How do I return this user from parseSnapshot()?
}
});
}
})
.build();
You won't be able to do this because parseSnapshot needs to happen synchronously, but getting another document is asynchronous. The document get won't complete with a callback until after parseSnapshot returns. So, you won't be able to use Firebase UI for this - you'll have to come up with your own way of making multiple requests to populate the RecyclerView, and it will likely require a lot of work.

Get users by name property using Firebase

I'm trying to create an application where I can get/set data in specific users accounts and I was tempted by Firebase.
The problem I'm having is that I don't know how to target specific users data when my structure looks like this:
online-b-cards
- users
- InnROTBVv6FznK81k3m
- email: "hello#hello"
- main: "Hello world this is a text"
- name: "Alex"
- phone: 12912912
I've looked around and I can't really find anything on how to access individual data let alone when they're given some random hash as their ID.
How would I go about grabbing individual user information based of their name? If there is a better way of doing this please tell me!
Previously, Firebase required you to generate your own indexes or download all data at a location to find and retrieve elements that matched some child attribute (for example, all users with name === "Alex").
In October 2014, Firebase rolled out new querying functionality via the orderByChild() method, that enables you to do this type of query quickly and efficiently. See the updated answer below.
When writing data to Firebase, you have a few different options which will reflect different use cases. At a high level, Firebase is a tree-structured NoSQL data store, and provides a few simple primitives for managing lists of data:
Write to Firebase with a unique, known key:
ref.child('users').child('123').set({ "first_name": "rob", "age": 28 })
Append to lists with an auto-generated key that will automatically sort by time written:
ref.child('users').push({ "first_name": "rob", "age": 28 })
Listen for changes in data by its unique, known path:
ref.child('users').child('123').on('value', function(snapshot) { ... })
Filter or order data in a list by key or attribute value:
// Get the last 10 users, ordered by key
ref.child('users').orderByKey().limitToLast(10).on('child_added', ...)
// Get all users whose age is >= 25
ref.child('users').orderByChild('age').startAt(25).on('child_added', ...)
With the addition of orderByChild(), you no longer need to create your own index for queries on child attributes! For example, to retrieve all users with the name "Alex":
ref.child('users').orderByChild('name').equalTo('Alex').on('child_added', ...)
Engineer at Firebase here. When writing data into Firebase, you have a few different options which will reflect different application use cases. Since Firebase is a NoSQL data store, you will need to either store your data objects with unique keys so that you can directly access that item or load all data at a particular location and loop through each item to find the node you're looking for. See Writing Data and Managing Lists for more information.
When you write data in Firebase, you can either set data using a unique, defined path (i.e. a/b/c), or push data into a list, which will generate a unique id (i.e. a/b/<unique-id>) and allow you to sort and query the items in that list by time. The unique id that you're seeing above is generated by calling push to append an item to the list at online-b-cards/users.
Rather than using push here, I would recommend using set, and storing the data for each user using a unique key, such as the user's email address. Then you can access the user's data directly by navigating to online-b-cards/users/<email> via the Firebase JS SDK. For example:
function escapeEmailAddress(email) {
if (!email) return false
// Replace '.' (not allowed in a Firebase key) with ',' (not allowed in an email address)
email = email.toLowerCase();
email = email.replace(/\./g, ',');
return email;
}
var usersRef = new Firebase('https://online-b-cards.firebaseio.com/users');
var myUser = usersRef.child(escapeEmailAddress('hello#hello.com'))
myUser.set({ email: 'hello#hello.com', name: 'Alex', phone: 12912912 });
Note that since Firebase does not permit certain characters in references (see Creating References), we remove the . and replace it with a , in the code above.
You can grab the details by the following code.
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("users");
myRef.orderByChild("name").equalTo("Alex").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot childDataSnapshot : dataSnapshot.getChildren()) {
Log.d(TAG, "PARENT: "+ childDataSnapshot.getKey());
Log.d(TAG,""+ childDataSnapshot.child("name").getValue());
}
I think the best approach is to define the ids of the users based o the auth object provided by the Firebase. When I create my users, I do:
FirebaseRef.child('users').child(id).set(userData);
This id comes from:
var ref = new Firebase(FIREBASE);
var auth = $firebaseAuth(ref);
auth.$authWithOAuthPopup("facebook", {scope: permissions}).then(function(authData) {
var userData = {}; //something that also comes from authData
Auth.register(authData.uid, userData);
}, function(error) {
alert(error);
});
The Firebase auth services will always ensure a unique id among all their providers to be set at uid. This way always you will have the auth.uid and can easily access the desired user to update it, like:
FirebaseRef.child('users').child(id).child('name').set('Jon Snow');
This was a paraphrasing of a post that helped me when trying to access the auto-generated unique id. Access Firebase unique ids within ng-repeat using angularFire implicit sync
Thanks, bennlich (source):
Firebase behaves like a normal javascript object. Perhaps the example below can get you on the right track.
<div ng-repeat="(name, user) in users">
{{user.main}}
</div>
Edit: Not 100% sure of your desired outcome, but here's a bit more that might spark an 'aha' moment. Click on the key that you are trying to access right in your Firebase dashboard. From there you can use something like:
var ref = new Firebase("https://online-b-cards.firebaseio.com/users/<userId>/name);
ref.once('value', function(snapshot) {
$scope.variable= snapshot.val();
});
This is how to access the auto generated unique keys in Firebase:
data structure:
- OnlineBcards
- UniqueKey
database.ref().on("value", function(snapshot) {
// storing the snapshot.val() in a variable for convenience
var sv = snapshot.val();
console.log("sv " + sv); //returns [obj obj]
// Getting an array of each key in the snapshot object
var svArr = Object.keys(sv);
console.log("svArr " + svArr); // [key1, key2, ..., keyn]
// Console.log name of first key
console.log(svArr[0].name);
}, function(errorObject) {
console.log("Errors handled: " + errorObject.code);
});
The simplest way is to stop using the .push(){}
function which will generate that random key. But instead use the .update(){} function where you may specify the name of the child instead of having the random key.
Retrieving data:
In your database, you are using a random id that is generated using the push(), therefore if you want to retrieve the data then do the following:
Using Firebase in Android App:
DatabaseReference ref=FirebaseDatabase.getInstance().getReference().child("users");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot datas : dataSnapshot.getChildren()) {
String name=datas.child("name").getValue().toString();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Using Firebase in Javascript:
firebase.database().ref().child("users").on('value', function (snapshot) {
snapshot.forEach(function(childSnapshot) {
var name=childSnapshot.val().name;
});
});
Here you have the snapshot(location of the data) at users then you loop inside all the random ids and retrieve the names.
Retrieving data for a Specific User:
Now if you want to retrieve information for a specific user only, then you need to add a query:
Using Firebase in Android App:
DatabaseReference ref=FirebaseDatabase.getInstance().getReference().child("users");
Query queries=ref.orderByChild("name").equalTo("Alex");
queries.addListenerForSingleValueEvent(new ValueEventListener() {...}
Using Firebase with Javascript
firebase.database().ref().child("users").orderByChild("name").equalTo("Alex").on('value', function (snapshot) {
snapshot.forEach(function(childSnapshot) {
var name=childSnapshot.val().name;
});
});
Using orderByChild("name").equalTo("Alex") is like saying where name="Alex" so it will retrieve the data related to Alex.
Best Way:
The best thing is to use Firebase Authentication, thus generating a unique id for each user and using it instead of a random id push(), this way you do not have to loop through all the users since you have the id and can easily access it.
First, the user needs to be signed in then you can retrieve the unique id and attach a listener to retrieve the other data of that user:
Using Firebase with Android:
DatabaseReference ref = FirebaseDatabase.getInstance().getReference("users");
String uid = FirebaseAuthentication.getInstance().getCurrentUser().getUid();
ref.child(uid).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
String name=dataSnapshot.child("name").getValue().toString();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Using Firebase with Javascript:
var user = firebase.auth().currentUser;
var uid=user.uid;
firebase.database().ref().child("users").child(uid).on('value', function (snapshot) {
var name=snapshot.val().name;
});
The simplest and better way to add unique Id to ur database according to authenticated user is this:
private FirebaseAuth auth;
String UId=auth.getCurrentUser().getUid();
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("Users");
User user = new User(name,email,phone,address,dob,bloodgroup);
myRef.child(UId).setValue(user);
The UId will be a unique Id for a specific authenticated email/user

Resources