Fetch multiple documents by id in Firestore - firebase

I'm currently doing a proof of concept for an Android app with the new Firestore as backend/db. I need to fetch a bunch of documents by their id (they are all in the same collection)
Right now, I'm looping thru the id list and fetching them one by one and storing them in a list which in turn updates a RecycleView in the app. This seems to be a lot of work and it does not perform very well.
What is the correct way to fetch a list of documents from Firestore without having to loop all the ids and getting them one by one?
Right now my code looks like this
for (id in ids) {
FirebaseFirestore.getInstance().collection("test_collection").whereEqualTo(FieldPath.documentId(), id)
.get()
.addOnCompleteListener {
if (it.isSuccessful) {
val res = it.result.map { it.toObject(Test::class.java) }.firstOrNull()
if (res != null) {
testList.add(res)
notifyDataSetChanged()
}
} else {
Log.w(TAG, "Error getting documents.", it.exception)
}
}
}

This is the way i'm using until they will add this option
I made that with AngularFire2 but also with the rxfire libary is the same if you are using react or vue.
You can map the null items before subscribing if there are some documents that deleted.
const col = this.fire.db.collection('TestCol');
const ids = ['a1', 'a2', 'a3', 'a4'];
const queries = ids.map(el => col.doc(el).valueChanges());
const combo = combineLatest(...queries)
.subscribe(console.log)

Firestore does not currently support query by IDs.
According to AngularFirebase, this is in the roadmap for development in the future, but its not official:
Keep in mind, Firestore is still in beta. Firebase engineers hinted at some really cool features on the roadmap (geo queries, query by array of ids) - I’ll be sure to keep you posted :)

Here is the way you can get specific documents,
here is a sample code:
List<String> documentsIds = {your document ids};
FirebaseFirestore.getInstance().collection("collection_name")
.whereIn(FieldPath.documentId(), documentsIds).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (DocumentSnapshot document : Objects.requireNonNull(task.getResult())) {
YourClass object = document.toObject(YourClass.class);
// add to your custom list
}
}
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
e.printStackTrace();
}
});

Related

Kotlin Save Firestore query result for a variable

I have a problem with Firebase.
I also use Firestore on a website and in a Kotlin app.
On the website, I can save the result of the query to a variation in the following way:
const addStudentManu = async($this) => {
const userId = await db.collection('users').where('neptun','==',ASD123).get();
const getUserId = userId.docs.map(doc=>doc.id);
}
How can i do this in kotlin?
This is how it goes:
db.collection("users")
.whereEqualTo("neptun", "ASD123")
.get()
.addOnSuccessListener { documents ->
val list = mutableListOf<String>()
for (document in documents) {
Log.d(TAG, "${document.id}")
list.add(document.id)
}
println(list)
}
.addOnFailureListener { exception ->
Log.w(TAG, "Error getting documents: ", exception)
}
You can checkout the sample code snippets in the documentation.
While #Dharmaraj answer will work perfectly fine, when it comes to Kotlin, the most convenient way for saving the result of a query would be to use Kotlin Coroutines, We can create a suspend function and map all documents to their corresponding IDs, similar with your example. So please try the following lines of code:
private suspend fun getIdsFromFirestore(): List<String> {
val ids = db.collection("users").whereEqualTo("neptun", "ASD123").get().await()
return ids.documents.mapNotNull { doc ->
doc.id
}
}
As you can see, we have now an extension function called await() that will interrupt the Coroutine until the data from the database is available and then return it. That's almost the same thing when using async on the web.
Now we can simply call this from another suspend method like in the following lines of code:
private suspend fun getIds() {
try {
val ids = getIdsFromFirestore()
// Do what you need to do with the list of IDs
} catch (e: Exception) {
Log.d(TAG, e.getMessage()) //Don't ignore potential errors!
}
}

Firebase list is not updating in set state

The Flutter setState function not updating the list after retrieving from Firebase.
I am trying to develop a Flutter app. I am not getting updating the list in setState() function. The list is successfully retrieving from firebase. I have written the firebase connections in Services.dart file.
But my method _getList() is not getting the value in main.dart file.
main.dart
class DetailsPageState extends State<DetailsPage> {
List<Product> list;
#override
void initState() {
_checkUser(); // for getting user id from firebase auth
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Container(
child:new Text("data");
);
)
}
void _checkUser(){
debugPrint("Entering in _checkUser");
this.firebaseAuth.onAuthStateChanged.listen((firebaseUser)async{
_getList(firebaseUser.uid);
});
}
void _getList(String id)
debugPrint("Entering in _getList");
Services.retrieveItems(firestore, uid).then((onValue){
setState(() {
list=onValue;
debugPrint("items list:"+onValue.length.toString());
debugPrint("items list:"+listCart.length.toString());
});
});
}
}
Services.dart
static Future<List> retrieveItems(Firestore firestore, String userId) async {
List<Product> items = new List<Product>();
try {
firestore.collection("Items").document(userId)
.collection("ItemsMain").snapshots().listen((QuerySnapshot snapshot) {
List docList = snapshot.documents;
items = snapshot.documents.map((documentSnapshot) => Product.fromMap(documentSnapshot.data)).toList();
debugPrint("items:"+items.length.toString());
//return items;
});
} on Exception catch (e) {
print (e.toString());
}
debugPrint("items 2:"+items.length.toString());
return items;
}
Expected results:
Entering in _checkUser
Entering in _getList
items:6
items 2:6
items list:6
items list:6
Actual results:
Entering in _checkUser
Entering in _getList
items list:0
items list:0
items 2:0
items:6
You're returning the items before they are loaded. The simplest way to fix this is to use await in retrieveItems to wait for the data to be loaded from Firestore:
static Future<List> retrieveItems(Firestore firestore, String userId) async {
List<Product> items = new List<Product>();
var snapshot = await firestore.collection("Items").document(userId)
.collection("ItemsMain").getDocuments()
List docList = snapshot.documents;
items = snapshot.documents.map((documentSnapshot) => Product.fromMap(documentSnapshot.data)).toList();
debugPrint("items:"+items.length.toString());
return items;
}
You'll note that I:
Call get() instead of listen(). Since listen() starts actively monitoring the collection, it is impossible to say when it is "done". A get() on the other hand, returns the documents once, and is then done.
Removed the exception handling, just to make the code a bit more readable. But I also recommend only adding exception handlers in functional code like this if you're actually handling the exception. Leave "log and continue" handlers for higher-level code, such as your main method.

Not able to retrieve datas from Short dynamic link - Firebase

When I create dynamic links which contain UTM parameters and share it, I was able to retrieve the data and encoded queries from the link. But when I try to create a short link of a dynamic link using firebase recommended method, I can only able to retrieve the path, but not the encoded queries. how do I solve it?
METHOD FOR CREATING DYNAMIC LINK :
public void buildReferral() {
DynamicLink dynamicLink = FirebaseDynamicLinks.getInstance().createDynamicLink()
.setLink(Uri.parse("sample link"))
.setDynamicLinkDomain("sample domain")
.setAndroidParameters(
new DynamicLink.AndroidParameters.Builder("com.package.my")
.build())
.setGoogleAnalyticsParameters(
new DynamicLink.GoogleAnalyticsParameters.Builder()
.setSource("referral")
.setContent("content")
.setMedium("Android")
.build())
.buildDynamicLink();
buildShortUrl(dynamicLink);
}
METHOD FOR CREATING SHORT LINK :
public void buildShortUrl(DynamicLink dynamicLink) {
Task<ShortDynamicLink> shortLinkTask = FirebaseDynamicLinks.getInstance().createDynamicLink()
.setLongLink(Uri.parse(dynamicLink.getUri().toString()))
.buildShortDynamicLink()
.addOnCompleteListener(this, new OnCompleteListener<ShortDynamicLink>() {
#Override
public void onComplete(#NonNull Task<ShortDynamicLink> task) {
if (task.isSuccessful()) {
// Short link created
Uri shortLink = task.getResult().getShortLink();
Uri flowchartLink = task.getResult().getPreviewLink();
} else {
// Error
// ...
}
}
});
}
You can use appendQueryParameter() to add multiple parameters to the link, and by using getQueryParameter() you can retrieve parameters from link. You can see this answer how you can achieve it.

Adding custom data to a firebase storage upload?

I'm uploading files to firebase storage like so:
var storageRef = firebase.storage();
var fileRef = storageRef.ref(file.name);
fileRef.put(file)
.then(function (snapshot) {
console.log('Uploaded a blob or file!');
window.URL.revokeObjectURL(file.preview);
})
After the upload I have a firebase storage trigger:
export const processUploadedFile = functions.storage.object().onChange(event => {
}
What I want to do is upload some additional information with the original upload so that the processUploadedFile knows what to do with it (for example extract the file, move it to a special directory, etc, etc).
I tried using metadata like so:
var newMetadata = {
customMetadata: {
"Test": "value"
}
}
fileRef.put(file, newMetadata)
But on the cloud storage trigger function I don't know how to get the metadata, I logged out fileMetaData like so:
file.getMetadata().then((metaData)=>console.log(metaData))
But did not see my metadata anywhere in there (or in fileMetaData[0].metadata which returned undefined)
Not sure how I can achieve this...
I think providing file meta info will do the trick. Here is the reference. Firebase Storage File metadata. You can pass custom parameters for the file with customMetadata. For instance :
customMetadata: {
'actionType': 'ACTION_CODE',
'action': 'do something info'
}
You can access this metadata with storage trigger and take the action accordingly. Here is how you can achieve that Automatically Extract Images Metadata
I believe there are some properties that cannot be changed as they are not writeable. However, if you indeed want to add a custom data to firebase storage, you can set custom metadata as an object containing String properties. For example:
var myCustomMetadata = {
customMetadata : {
'file_name': 'this is the file name'
}
}
In the case above, the file_name is the custom metadata that you want to create.
After creating a reference to the file in the firebase storage, you can then call the updateMetadata() method on the reference.
For example:
Get the reference to an image file using downloadUrl:
var getRef = firebase.storage().refFromURL(imageUrl);
Use the reference to update the metadata:
getRef.updateMetadata(myCustomMetadata).then(()=>{
//do other things
})
For me, I had to call to Firebase Storage 2x. I'm using Java on an Android device to edit the metadata. First time is to upload the image. Second time is to set the image's metadata.
Instructions to set the Metadata of a Storage file is here= https://firebase.google.com/docs/storage/android/file-metadata
"You can update file metadata at any time after the file upload completes by using the updateMetadata() method. "
Here's my functions:
private void uploadImageToFBStorageAndFS(byte[] profilePic, final StorageUrlEstablished_CL storageUrlCallback) {
String storage_directory = //You get this
StorageReference profileImageRef = FirebaseStorage.getInstance().getReference(storage_directory).child(final_filename);
//1st time, upload the image/bytes.
if (profilePic != null) {
profileImageRef.putBytes(profilePic).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
#Override
public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
Task<Uri> result = taskSnapshot.getMetadata().getReference().getDownloadUrl();
result.addOnSuccessListener(new OnSuccessListener<Uri>() {
#Override
public void onSuccess(Uri uri) {
updateImageMetadata(profileImageRef);
String urlWhereProfilePicIsStored = uri.toString();
}
});
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
//Error handling
}
});
}
}
private void updateImageMetadata(StorageReference profileImageRef){
//Some devices, like the Asus tablet, doesn't upload good meta-data with the image.
// Create file metadata including the content type
StorageMetadata metadata = new StorageMetadata.Builder()
.setContentType("image/png")
.setCustomMetadata("myCustomProperty", "Rent App")
.build();
// Update metadata properties
profileImageRef.updateMetadata(metadata);
}

Is it possible to make a query based on the child of a child?

I have the following structure.
Is it possible to query all the children of 27032017 node where the node teamLeaderID of those children is equal to a given value?
If it's not possible (which seems to be the case for me), should I then move teamLeaderID one level up, under 27032017?
All you need to update your query
REST API
{{base_url}}/27032017.json?orderBy="teamLeaderID"&equalTo="12daca12tDy8xD1FiXw1"
base_url will be url to your firebase database. you may need to modify rules to access your database.
Web (JS)
var ref = firebase.database().ref("27032017");
ref.orderByChild("teamLeaderID").equalTo("12daca12tDy8xD1FiXw1")
.once("value")
.then(function (snapshot) {
console.log(snapshot.key);
});
Android (Java)
FirebaseDatabase database = FirebaseDatabase.getInstance();
ref = database.getReference().child("27032017");
refer = ref.orderByChild("teamLeaderID").equalTo("12daca12tDy8xD1FiXw1");
refer.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
Log.e("Count " ," - "+snapshot);
}
#Override
public void onCancelled(FirebaseError firebaseError) {
Log.e("The read failed: " ,firebaseError.getMessage());
}
});

Resources