If I implement onSnapshot real-time listener to Firestore in Cloud Function will it cost more? - firebase

I have a listener to Firestore DB changes and it fetches automatically every time there is a change, however, if I decide to implement it in Cloud Function and call it from the client app, will it cost more because it will running 24h/7 even when users are not using the app?
This is in Client side:
firestore()
.collection('Collection').doc().collection('public')
.where('act', '==', 1)
.orderBy('time', 'asc')
.limit(10)
.onSnapshot({
error: (e) => this.setState({ errorMessage: e, loading: false }),
next: (querySnapshot) => { this._calculateLocationDistance(querySnapshot) },
});
Moreover, is it necessary to do it in Cloud Function? Is it risky if I leave it in the client side?

You can't really use listeners effectively in Cloud Functions. Cloud Functions are meant to be stateless. They serve a single request at a time, and clean up afterward. If you try to use a listener, it just won't work the way you expect. Cloud Functions also don't keep a socket open to the requester. Once a response is sent, the connection is closed, and there's no way to keep it open.
Given these constraints, functions typically just use get() to fetch data a single time, and return the results to the client. If you want realtime results, that should be implemented on the client.
If you are working with a backend that can keep a socket connection open to a client, it is no less expensive to have a listener on the backend that delivers results to the client. You are still charged a document read for each document read by the listener as it continues to receive results.

Related

How to continue running Firebase Cloud Function after request is finished

Really bizarre that Firebase doesn't seem to work quite like typical Express app. Whatever I write in Express and copy-paste to Firebase Functions I typically get error. There is one that I can't figure out on my own though.
This endpoint is designed to start a function and live long enough to finish even longer task. That request is a webhook (send docs, we will transform them and ping you when it's done to specified another webhook). Very simplified example below:
router.post('/', (req, res) => {
try {
generateZipWithDocuments(data) // on purpose it's not async so request can return freely
res.sendStatus(201)
} catch (error) {
res.send({ error })
}
})
On my local machine it works (both pure Express app and locally emulated Firebase Functions), but in the cloud it has problems and even though I put a cavalcade of console.log() I don't get much information. No error from Firebase.
If generateZipWithDocuments() is not asynchronous res.sendStatus() will be immediately executed after it, and the Cloud Function will be terminated (and the job done by generateZipWithDocuments() will not be completed). See the doc here for more details.
You have two possibilities:
You make it asynchronous and you wait its job is completed before sending the response. You would typically use async/await for that. Note that the maximum execution time for a Cloud Function is 9 minutes.
You delegate the long time execution job to another Cloud Function and, then, you send the response. For delegating the job to another Cloud Function, you should use Pub/Sub. See Pub/Sub triggers, the sample quickstart, and this SO thread for more details on how to implement that. In the Pub/Sub triggered Function, when the job is done you can inform the user via an email, a notification, the update of a Firestore document on which you have set a listener, etc... If generateZipWithDocuments() takes a long time, it is clearly the most user friendly option.

Using Offline Persistence in Firestore in a Flutter App

I'm developing a app that uses Firebase's Firestore to send data to the web. One of the functions of the app is being able to save data in the device while being offline and send it to Firestore when internet connection is restored.
I activated offline persistence but it dosen't work.
DEBUG CONSOLE:
W/OkHttpClientTransport(28536): Failed closing connection
W/OkHttpClientTransport(28536): javax.net.ssl.SSLException: Write error: ssl=0x7f7acfc408: I/O error during system call, Broken pipe
W/OkHttpClientTransport(28536): at com.google.android.gms.org.conscrypt.NativeCrypto.SSL_write(Native Method)
W/OkHttpClientTransport(28536): at com.google.android.gms.org.conscrypt.NativeSsl.write(:com.google.android.gms#14798020#14.7.98 (040406-222931072):4)
How can I activate offline persistence and sync with Firestore when internet is restored?
MY CODE:
Future<Null> sendFirebaseData(var selectedModel) async {
Firestore.instance.enablePersistence(true);
var certID = await getIDCertificado();
var dateTime = new DateTime.now();
var nowHour = new DateFormat('kk:mm:ss').format(dateTime);
Map<String, dynamic> dataHeader = {
'ID': certID,
};
Map<String, dynamic> finalDataMap = {}..addAll(dataGeneral)
..addAll(dataInstrumento)..addAll(dataPadrao)
..addAll(dataAdicional)..addAll(dataHeader);
await Firestore.instance.collection('certificados').document((certID.toString()))
.setData(finalDataMap);}
when you use offline persistence in Firestore, don't use Transactions or await for response.
so, change this :
await Firestore.instance.collection('certificados').document((certID.toString()))
.setData(finalDataMap);
to this:
Firestore.instance.collection('certificados').document((certID.toString()))
.setData(finalDataMap);
When you restore your internet connection your data will be sync automatically, even if you are in background.
Doesn't work when your app is closed.
Context Of Promises & Callbacks when Offline
Why the above code change to remove "await" works.
Reference: Firebase Video - How Do I Enable Offline Support 11:13
Your callback won't be called and your promise won't complete until the document write has been successful on the server. This is why if your UI waits until the write completes to do something, it appears to freeze in "offline mode" even if the write was actually made to the local cache.
It is OK to not use async / await, .then() or callbacks. Firestore will always "act" as if the data change was applied immediately, so you don't need to wait to be working with fresh data.
You only need to use callbacks and promises when you need to be sure that a server write has happened, and you want to block other things from happening until you get that confirmation.
I think the currrent answer is outdated. According to the firebase documentation, offline persistentence is enabled by default for Android and iOS. For the web, it is not.
In flutter, the firestore implementation is based on the underlying OS. So you're safe on mobile apps, but not with flutter for web.
It is enabled by default but still only when you are not using await or transactions, further you can use timeout to stop listening to network connection by firestore after a specific time.
ref.setData(newNote.toMap()).timeout(Duration(seconds: 2),onTimeout:() {
//cancel this call here
print("do something now");
});

Firebase: First write is slow

Currently developing a hybrid mobile app using ionic. When the app starts up, and a user writes to the Realtime Database for the first time, it's always delayed by around 10 or more seconds. But any subsequent writes are almost instantaneous (less than 1 second).
My calculation of delay is based on watching the database in the Firebase console.
Is this a known issue, or maybe I am doing something wrong. Please share your views.
EDIT:
The write is happening via Firebase Cloud Function.
This is the call to the Firebase Cloud function
this.http.post(url+"/favouritesAndNotes", obj, this.httpOptions)
.subscribe((data) => {
console.log(data);
},(error)=>{
console.log(error);
});
This is the actual function
app.post('/favouritesAndNotes', (request, response) => {
var db = admin.database().ref("users/" + request.body.uid);
var favourites = request.body.favourites;
var notes = request.body.notes;
if(favourites!==undefined){
db.child("favourites/").set(favourites);
}
if(notes!==undefined){
db.child("notes/").set(notes);
}
console.log("Write successfull");
response.status(200).end();
});
The first time you interact with the Firebase Database in a client instance, the client/SDK has to do quite some things:
If you're using authentication, it needs to check if the token that it has is still valid, and if not refresh it.
It needs to find the server that the database is currently hosted on.
It needs to establish a web socket connection.
Each of these may take multiple round trips, so even if you're a few hundred ms from the servers, it adds up.
Subsequent operations from the same client don't have to perform these steps, so are going to be much faster.
If you want to see what's actually happening, I recommend checking the Network tab of your browser. For the realtime database specifically, I recommend checking the WS/Web Socket panel of the Network tab, where you can see the actual data frames.

Firebase: Cloud Functions, How to Cache a Firestore Document Snapshot

I have a Firebase Cloud Function that I call directly from my app. This cloud function fetches a collection of Firestore documents, iterating over each, then returns a result.
My question is, would it be best to keep the result of that fetch/get in memory (on the node server), refreshed with .onSnapshot? It seems this would improve performance as my cloud function would not have to wait for the Firestore response (it would already have the collection in memory). How would I do this? Simple as populating a global variable? How to do .onSnaphot realtime listener with cloud functions?
it might depend how large these snapshots are and how many of them may be cached ...
because, it is a RAM disk and without house-keeping it might only work for a limited time.
Always delete temporary files
Local disk storage in the temporary directory is an in-memory file-system. Files that you write consume memory available to your function, and sometimes persist between invocations. Failing to explicitly delete these files may eventually lead to an out-of-memory error and a subsequent cold start.
Source: Cloud Functions - Tips & Tricks.
It does not tell there, what exactly the hard-limit would be - and caching elsewhere might not improve access time that much. it says 2048mb per function, per default - while one can raise the quotas with IAM & admin. it all depends, if the quota per function can be raised far enough to handle the cache.
here's an example for the .onShapshot() event:
// for a single document:
var doc = db.collection('cities').doc('SF');
// this also works for multiple documents:
// var docs = db.collection('cities').where('state', '==', 'CA');
var observer = doc.onSnapshot(docSnapshot => {
console.log(`Received doc snapshot: ${docSnapshot}`);
}, err => {
console.log(`Encountered error: ${err}`);
});
// unsubscribe, to stop listening for changes:
var unsub = db.collection('cities').onSnapshot(() => {});
unsub();
Source: Get realtime updates with Cloud Firestore.
Cloud Firestore Triggers might be another option.

Firebase REST API persistent connection, speed optimisation

Retrieving multiple documents in the loop with Firebase JavaScript library is almost as fast as retrieving one document. Whether it is thanks to websocket of anything else.
Doing same with the REST Api is linearly slow. Each request takes a bit less than one second, 10 GET requests takes about 9 seconds on my machine. Setting 'Connection' header to 'keep-alive' does not improve the speed.
Given that quote from Firebase docs, I'd like to know how can one optimise the speed of the multiple lookup requests via REST Api.
Is it really okay to look up each record individually? Yes. The Firebase protocol uses web sockets, and the client libraries do a great deal of internal optimization of incoming and outgoing requests. Until we get into tens of thousands of records, this approach is perfectly reasonable. In fact, the time required to download the data (i.e. the byte count) eclipses any other concerns regarding connection overhead.
You can use Firebase JavaScript SDK in node.js for backend functions to take advantage of sockets.
For example, I'm using JavaScript API to send SMS on child_added event in firebase.
var Firebase = require('firebase');
var ref = new Firebase('YOUR_FIREBASE_URL_REFERENCE');
ref.authWithCustomToken("YOUR_SECRET_TOKEN", function(error, aut$
if (error) {
console.log("Authentication Failed!", error);
} else {
console.log("Authenticated successfully with payload:", authData);
}
});
var messageRef = new Firebase('https://cozywait.firebaseio.com/messages');
messageRef.orderByChild('status').equalTo('requested').on('child_added', function(snaps$
console.log('Message notification sending to ', snapshot.val().number);
initSendSMS(snapshot);
});

Resources