How to execute firebase cloud function one minute after an event occurred? - android

I am using Firebase realtime database for my Android app. I want to execute a firebase function one minute after a database write occurred(not every after 1 minute like Cron job). Is it possible? If possible how can I do that?

Cloud Functions does not offer delayed invocations, but you can integrate with Cloud Tasks to arrange for a callback to another function after some delay.

this is a work around, if you want to be delayed for quite amount of time consider pay attention to the cloud functions timeout option increase it if needed the default timeout is after 60s of the functions first begin executed
export const {Your function Name} = functions
.runWith({ timeoutSeconds: 120 }) // set the timeout in second, (the type is number)
.https.onRequest(async (req, res) => {}
you could wrap everything in you want to be delayed inside the functions in a
setTimeout(() => { // your code inside the functions you want it to be delayed }, delay time in seconds) function

One simple way would to calculate the time in one minute and add this to a database collection.
Then have cloud scheduler trigger a query for all items in that minute and process those items with you functions and delete or update the item in the database.
You could also use this collection to make sure your process completed by updating this table. If the item didn't get updated as completed, you could have another scheduler query to rerun the process.

Related

How many times onUpdate function will trigger on batch update?

I want to update two fields in a document in a collection called recipes and i used the batch update to do it:
let recipeRef = db.collection('recipes').doc(`${recipeId}`);
let batch = db.batch();
batch.update(recipeRef, {ratingsCount : admin.firestore.FieldValue.increment(1)});
batch.update(recipeRef, {totalRating : admin.firestore.FieldValue.increment(snap.data().rating)})
return batch.commit();
And I have a trigger function on the recipes collection like this:
exports.RecipeUpdated = functions.firestore
.document('recipes/{recipeId}')
.onUpdate((change, context) =>
{
//code
});
My question is as there are two updates in the above batch, will this also trigger the onUpdate function twice Or since the batch writes completes atomically, trigger will be called only ones? I want the trigger to be called only one time.
As #DougStevenson mentioned in his comment, you should just simply run the code and see the behaviour. But note, even if you are using the same recipeRef, you are creating two difference updates in your batch. In this case, the result that you'll get will be that onUpdate() will fire twice.
If you want to be called only once, then you should not create two different updates, you can create a single one. update() function allows you to pass multiple properties that can be updated. If you are using objects, than simply get a reference to the document, get it, make the changes and write it back.

Why addSnapshotListener is called twice on firestore CollectionReference? [duplicate]

My firestore onSnapshot() function is being called twice.
let user = firebase.firestore().collection('users').doc(userID).onSnapshot
({
next: (documentSnapshot: firebase.firestore.DocumentSnapshot) =>
{
this.userArray.push(documentSnapshot as User);
console.log(documentSnapshot);
//here
},
error: (firestoreError: firebase.firestore.FirestoreError) =>
{
console.log(firestoreError);
//here
}
});
I have also tried subscribing like in https://firebase.google.com/docs/firestore/query-data/listen#detach_a_listener by including user() at the //here comment but to no avail.
How can I modify such that the function only executes one time, i.e. push only one user object per time instead of twice.
I don't know if this is related to your question. If one is using
firebase.firestore.FieldValue.serverTimestamp()
to give a document a timestamp, then onSnaphot will fire twice. This seem to be because when you add a new document to your database onSnapshot will fire, but the serverTimestamp has not run yet. After a few milliseconds serverTimestamp will run and update you document => onSnapshot will fire again.
I would like to add a small delay before onSnapshot fires (say 0,5s or so), but I couldn't find the way to do this.
You can also make a server side function for onCreate event, I believe that would solve your problem. Maybe your userArray.push-action would be more suitable to execute in server side.
Update: To learn more about the behavior of serverTimestamp() and why it triggers the listener twice read this article: The secrets of Firestore’s FieldValue.serverTimestamp() — REVEALED!. Also, the official documentation states:
When you perform a write, your listeners will be notified with the new data before the data is sent to the backend.
In the article there are a couple of suggested solutions, one of which is to use the metadata property of the snapshot to find whether the Boolean value of metadata.hasPendingWrites is true (which tells you that the snapshot you’re looking at hasn’t been written to the server yet) or false.
For example, in your case you can check whether hasPendingWrites is false and then push the object:
if ( !documentSnapshot.metadata.hasPendingWrites ){
// This code will only execute once the data has been written to the server
this.userArray.push(documentSnapshot as User);
console.log(documentSnapshot);
}
In a more generic example, the code will look like this:
firestore.collection("MyCollection")
.onSnapshot( snapshot => {
if ( snapshot.metadata.hasPendingWrites ){
// Local changes have not yet been written to the backend
} else {
// Changes have been written to the backend
}
});
Another useful approach, found in the documentation is the following:
If you just want to know when your write has completed, you can listen to the completion callback rather than using hasPendingWrites. In JavaScript, use the Promise returned from your write operation by attaching a .then() callback.
I hope these resources and the various approaches will help anyone trying to figure out a solution.
REFERENCES:
Events for local changes
The hasPendingWrites metadata property
Snapshot Listen Options
If you need a one time response, use the .get() method for a promise.
firebase.firestore().collection('users').doc(userID).get().then(snap => {
this.userArray = [...this.userArray, snap.doc);
});
However, I suggest using AngularFire (totally biased since I maintain the library). It makes handling common Angular + Firebase tasks much easier.

Coroutines And Firebase: How to Implement Javascript-like Promise.all()

In Javascript, you can start two (or more) asynchronous tasks simultaneously, wait both of them to finish and then do something/continue:
const [firstReturn, secondReturn] = await Promise.all([
firstPromise,
secondPromise ];
// Do something with first and second return.
What I want to do in Kotlin (Android) is to start downloading two images from Firebase and when both downloads are completed - update the UI with them. So I have two Firebase async functions like this:
FirebaseStorage.getInstance().reference..child("Images/Events/$eventID/eventPhoto.jpeg").getBytes(1024 * 1024).addOnSuccessListener { byteArray ->
event.image = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
}
//And another one with different path
I thought about using Kotlin's "async/await" but since I can't return a value from Firebase function, it didn't work. Is there a straightforward way to do this in Kotlin so I can start to download both images at the same time and do some work after both of them are downloaded?
You will likely want to make use of Play services integration with coroutines that's documented here. What you get from this library is an extension function for Task (returned by Play and Firebase async APIs) called await() that you can use in a coroutine to defer execution until it's done.

How do I increase/decrease counter using FirebaseUI for Android without delay?

I created something similar to a subscription/like counter using Firebase Real-time Database and Firebase's cloud functions (using a transaction):
// This is a cloud function that increases subs by 1
export const onSubscriberCreate = functions.database
.ref('/channels/{$ch_id}/subscribers/{$uid}')
.onCreate((snapshot, context) => {
const countRef = snapshot.ref.parent.parent.child('subs_count')
return countRef.transaction(count => {
return count + 1
})
})
Then, I used FirebaseUI (FirebaseRecyclerAdapter) for Android to populate a RecyclerView of channels. When the user presses a channel's "Subscribe" button, his id is being sent to /channels/{$ch_id}/subscribers/ which triggers the cloud function.
However, the cloud function is really slow (about 5 secs), so I want to "fake" the update of the counter displayed to the user even before the cloud function is executed (I tried it by changing the TextView):
channelRef.child("subscribers")
.child(user.getUid()).setValue(true)
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
subsInfo.setText(channel.getSubs_count() + 1) + " SUBSCRIBERS");
}
});
The problem is that the channel object is being updated twice (the user id on the subscribers' list and the counter increased) so that the information of the channel is being downloaded again and binded to the ViewHolder even before the server updates the counter, so that's not a good solution.
I thought about moving the transaction code into the client, is it really necessary? Is there a better solution?
The best thing I feel you should do is to move the subscriber's list from inside the channel node to somewhere out. This will also make your channel object weigh lesser and you can store/update number of subscribers easily inside the channel node. Now for every user, you are downloading the entire list of users everytime you want to download a channel's information. And you don't need a cloud function to update the number of subscribers. You can do that totally on the client side using Transactions.
root/channels/{$channel}/{channelName,numberOfSubscribers,etc}
root/subscribers/{&channel}/{$userId}
This is probably how you want your data structure should be unless you really want to get all the users list. If that's the case, you can just show the size of the list of subscribers inside the TextView where you are showing the number of subscribers.

RxAndroid: Possible to use zip function on an array of api requests with interval or delay?

I am playing around with RxAndroid. I have a List of Observables all of which are api requests (using Retrofit). I want to fire one of them every x seconds or milliseconds but then zip the responses together. I seems that once I subscribe to Observable.zip(requests, someFunction) all of them are fired off at once. Any tips?
Thanks!
EDIT: looks like adding delaySubscription to each request maybe the answer
You are looking for either delay() or delaySubscription().
delay() will delay the result of the Observable being published to the subscriber.
delaySubscription() will delay subscription to the Observable.
Observable.zip(someObservable.delaySubscription(100, TimeUnit.MILLISECONDS),
someOtherObservable.delaySubscription(200, TimeUnit.MILLISECONDS),
someThirdObservable.delaySubscription(300, TimeUnit.MILLISECONDS),
new Func3<Object, Object, Object, Void>() {
...
}).subscribe();
Also, it's posible to achieve a periodical sending effect by using the interval() operator.
Let's see a simple example. Imagine you have an array, numbers, whose values have to emitted each x time. You could create an Observable that emits them:
Observable<Integer> values = Observable.from(numbers);
And now, another Observable that emits each (for instance) 30 milliseconds:
Observable<Long> interval = Observable.interval(30, TimeUnit.MILLISECONDS);
So, through the zip() operator you could combine both to achieve the periodical emission of the values in your number array:
Observable.zip(values, interval, (arrayElement, aLong) -> arrayElement)
.subscribe(arrayElement -> doSomething(arrayElement));
I used it to get an animation effect for a progress indicator. I wrote a complete example project you can check in github.

Categories

Resources