Flutter Firebase subscribe to multiple Firebase Cloud Messaging topics - firebase

I am a relative beginner with Dart & right now for my Flutter app I am unsure on how to properly subscribe to multiple topics at once to Firebase Cloud Messaging. I want to receive notifications whenever my Cloud Function sends notifications through FCM to these topics at a certain time interval. The FlutterFire documentation seems to say that the subscribe/unsubscribeToTopic methods return Futures, but what is the best way in Dart to resolve multiple futures? So far, I have thought of 2 options below:
Method 1
Future<void> subscribeTopicV1(List<int> selectedUsers) async {
for (var userId in selectedUsers) {
await messaging.subscribeToTopic('$userId');
}
}
My understanding is this basically turns all the subscribe/unsubscribe topic operations from asynchronous to synchronous if in the parent I call subscribeTopicV1 like the following:
...
if (shouldSubscribe) {
await subscribeTopicV1
}
...
// OR
if (shouldSubscribe) {
subscribeTopicV1.then( () => {
... // do whatever
})
}
This should mean that I am waiting on each messaging.subscribeToTopic to return before I move on to subscribe the next topic?
Method 2
Future<void> subscribeTopicV2(List<int> selectedUsers) async {
Future.wait(selectedUsers
.map((userId) => messaging.subscribeToTopic('$userId')));
}
Now this second method would run all the messaging.subscribeToTopic operations in parallel and if I call subscribeTopicV2 in the same way like:
...
if (shouldSubscribe) {
await subscribeTopicV2
}
...
// OR
if (shouldSubscribe) {
subscribeTopicV2.then( () => {
... // do whatever
})
}
In this second method, I should be waiting for when all futures are resolved inside subscribeTopicV2? I would like to mention that I do not care about the order in which the topics are subscribed so is it fair to say in this case I should be going with Method 2 for optimal performance since that it allows me to execute those async operations in parallel? I can't find any documentation of how people deal with subscribing to multiple topics so any insight would be much appreciated!

Related

Wait for Cloud Function to be finished

Is there any way to wait for a Cloud Function, that was triggered by a Firestore document write, to finish?
Context:
My app has groups. Owners can invite other users to a group via an invite code. Users can write themselves as member of a group if they have the right invite code. They do this by writing the groups/{groupId}/members/{userId} document that contains their profile info.
To make reading more efficient, this info is copied to array members in the groups/{groupId} document by a Cloud Function.
The Cloud Function that does that is triggered by the document write. It is usually finished after a couple of seconds, but there's no predictable execution time and it might take a bit longer if it is a cold start.
After the user has joined the group, I forward them to the groups view in my app which reads the group document. In order for the view to render correctly, the membership info needs to be available. So I would like to forward AFTER the Cloud Function has finished.
I found no way to track the execution of a Cloud Function that was triggered by a Firestore document write.
A fellow developer recommended to just poll the groups/{groupId} document until the info is written and then proceed but this doesn't seem like a clean solution to me.
Any ideas how this could be done better?
Is it possible to get a promise that resolves after the Cloud Function has finished? Is there a way to combine a Firestore document write and a Cloud Function execution into one transaction?
Thanks for the hints, I came up with the following ways to deal with the problem. The approach depends on if/when the user is allowed to read a document:
A) User is member and leaves the group > at the start of the transaction they are allowed to read the group > the moment they can't read anymore confirms that the membership was successfully revoked:
async function leaveGroup (groupId) {
await deleteDoc(doc(db, 'groups', groupId, 'members', auth.currentUser.uid))
// Cloud Function removes the membership info
// from the group doc...
await new Promise((resolve) => {
const unsubscribeFromSnapshot = onSnapshot(
doc(db, 'groups', groupId),
() => { }, // success callback
() => { // error callback
// membership info is not in the group anymore
// > user can't read the doc anymore
// > transaction was successful
// read access was revoked > transaction was successful:
unsubscribeFromSnapshot()
resolve()
}
)
})
}
B) User is not a member and wants to join the group > at the start of the transaction they are allowed to read the group > the moment they can read the group confirms that the membership was successfully confirmed (this is a simplified version that does not check the invite code):
async function joinGroup (groupId) {
try {
await setDoc(
doc(db, 'groups', groupId, 'members', auth.currentUser.uid),
{
userId: auth.currentUser.uid,
userDisplayName: auth.currentUser.displayName
}
)
// Cloud Function adds the membership
// information to the group doc ...
await new Promise((resolve) => {
let maxRetries = 10
const interval = setInterval(async () => {
try {
const docSnap = await getDoc(doc(db, 'groups', groupId))
if (docSnap.data().members.includes(auth.currentUser.uid)) {
// membership info is in the group doc
// > transaction was successful
clearInterval(interval)
resolve()
}
} catch (error) {
if (maxRetries < 1) {
clearInterval(interval)
}
}
maxRetries--
}, 2000)
})
}
Note: I went with polling here, but similar to what #samthecodingman suggested, another solution could be that the Cloud Function confirms the membership by writing back to the members document (which the user can always read) and you listen to snapshot changes on this document.
C) Most straightforward way: someone else (the group owner) removes a member from the group > they have read access through the whole transaction > directly listen to snapshot changes:
async function endMembership (groupId, userId) {
await deleteDoc(doc(db, 'groups', groupId, 'members', userId))
// Cloud Function removes the membership info
// from the group doc...
await new Promise((resolve) => {
const unsubscribe = onSnapshot(doc(db, 'groups', groupId), (doc) => {
if (!doc.data().members.includes(userId)) {
// membership info is not in the group doc anymore
// > transaction was successful
unsubscribe()
resolve()
}
})
})
}
In any case you should do proper error handling that covers other causes. I left them out to demonstrate how to use the error handlers when waiting for gaining/loosing read access.

Future ignores await in flutter

I'm trying to make a version control for my application. In other words, trying to connect to firebase documents, pick version number from there and compare it with value from shared preferences in order to decided if it's required to update the information.
I've followed the official tutorial on Futures.
here is my code:
#override
void initState(){
loadit();
}
Future _getIntVersionFromSharedPref() async {
await Firestore.instance.collection('DataIndex')
.document('dbversion').snapshots().listen((event) {
setState(() {
fbVersion = event['currentversion'];
});
print('_getIntVersionFromSharedPref() future -> $fbVersion');
return fbVersion;
});
}
Future<String> loadit() async {
var myFBversion = await _getIntVersionFromSharedPref();
print('loadit future -> $myFBversion');
}
However I endup with the following console output:
I/flutter: loadit future -> null
I/flutter: _getIntVersionFromSharedPref() future -> 1
Why would it fire null right away without waiting for the value? According to the official tutorial it should be executed in turn.
listen() doesn't return a Future, so you can't await it. It returns a StreamSubscription object asynchronously, and you must handle the ongoing results of the query within your stream handler function. When you are done with the Stream, you should unsubscribe from the stream using the StreamSubscription.
If you just want a single snapshot of the document, don't use snapshots() at all. Just use get() to get a single snapshot as shown in the documentation.
Listen isn't part of the dart Future API, so that listen callback isn't tied to the Promise that is implicitly returned from _getIntVersionFromSharedPref.
I think you need to manually return a Promise from that method, and resolve it from your listen callback. You can manually resolve a Future like this with the Completer class.
Something like this:
Future _getIntVersionFromSharedPref() async {
var completer = Completer<num>();
await Firestore.instance.collection('DataIndex')
.document('dbversion').snapshots().listen((event) {
setState(() {
fbVersion = event['currentversion'];
});
print('_getIntVersionFromSharedPref() future -> $fbVersion');
completer.complete($fbVersion);
});
// This will be returned right away, but anything that awaits it will block
// until the callback above has completed the future.
return completer.future;
}

How to Use Firebase with Nativescript-Vue?

I've been trying to implement just a simple Firebase fetch since November. At this point, I wish I'd just created a new Rails api; it would have been faster.
But everyone insists Firebase is Oh So Simple.
In app.js,
import firebase from 'nativescript-plugin-firebase';
That part seems OK.
Instructions are all over the place after that.
The plugin's ReadMe suggests an initialization:
firebase.init({
// Optionally pass in properties for database, authentication and cloud messaging,
// see their respective docs.
}).then(
function () {
console.log("firebase.init done");
},
function (error) {
console.log("firebase.init error: " + error);
}
);
Several others have insisted that the init code is unnecessary. It does run without errors, but the code he gives after that produces nothing. Also,
const db = firebase.firestore;
const UserStatusCollection = db.collection("UserStatus");
UserStatusCollection.get();
produce an empty object {}.
Here's my Firebase collection:
If I wrap the firebase call in async/await (and no one is showing it as this complicated),
async function getFireStoreData() {
try {
let result = await this.UserStatusCollection.get();
console.log(result);
return result;
}
catch (error) {
console.error(
"UserStatusCollection.get()" + error
);
}
}
And call that
let temp2 = getFireStoreData();
console.log("temp2:" + temp2);
All I ever get is an object promise.
As I said, I wish I had just built up a new Rails API and had a far simpler life since November.
Your getFireStoreData method is asynchronous and you're not awaiting it. That is probably the reason why you're getting a promise back. Try to await getFireStoreData(). See if that works.
Since it's also a promise, you can try to use .then.
getFireStoreData().then(data => {
console.log(data);
})

What is a method of using Firebase Cloud Functions in Flutter.

I have been searching all over on how to implement firebase functions with a flutter application. It does not seem like there is an SDK available (yet). I've also tried adding the gradle dependency implementation 'com.google.firebase:firebase-functions:15.0.0' to my app/build.gradle but this causes build errors.
Has anyone done an implementation that works? I am unable to find any documentation on how to handle credentials and the transport of data in order to build my own firebase functions call.
I have created a rough outline of how I am thinking this is intended to work, but may be way off base.
Future<dynamic> updateProfile(String uid, AccountMasterViewModel avm) async {
Uri uri = Uri.parse(finalizeProfileFunctionURL);
var httpClient = new HttpClient();
String _result = '';
try {
return await httpClient
.postUrl(uri)
.then((HttpClientRequest request) {
return request.close();
// authentication??
// Fields and data??
})
.then((HttpClientResponse response) async {
print(response.transform(new Utf8Codec().decoder).join());
if (response.statusCode == HttpStatus.OK) {
String json = await response.transform(new Utf8Codec().decoder).join();
_result = jsonDecode(json);
// Do some work
return json;
}
else {
return ':\nHttp status ${response.statusCode}';
}
});
}
catch (exception) {
return 'Failed ' + exception.toString();
}
}
I'd like to be able to send an object, like
{
accountID: src.accountID,
accountName: src.name,
accountImg: src.image
}
and then handle the response. But as I said, I can't find any working examples or tutorials on how to do this. It's fairly simple to do this client size and talk directly to the database, however, there are validations and data components that need to be hidden from the client, so cloud functions is the way I would like to do this.
Yes, there is a cloud_function package available here: https://pub.dartlang.org/packages/cloud_function.
so as to make a call to the function you can just call
CloudFunctions.instance.call(
functionName: 'yourCloudFunction',
parameters: <String, dynamic>{
'param1': 'this is just a test',
'param2': 'hi there',
},
);
An updated answer to calling Firebase's Cloud Functions in Flutter would be
var callable = CloudFunctions.instance.getHttpsCallable(functionName: 'functionName'); // replace 'functionName' with the name of your function
dynamic response = callable.call(<String, dynamic>{
'param1': param1 //replace param1 with the name of the parameter in the Cloud Function and the value you want to insert
}).catchError((onError) {
//Handle your error here if the function failed
});
This is a good tutorial on cloud functions in flutter which helped me:
https://rominirani.com/tutorial-flutter-app-powered-by-google-cloud-functions-3eab0df5f957
Cloud functions can be triggered by data change triggers in the realtime database, Firestore, or Datastore, as well as authentication triggers.
You could just persist
{
accountID: src.accountID,
accountName: src.name,
accountImg: src.image
}
to the database and register a trigger that runs a Cloud Function when data at a specific path is inserted, updated, or deleted.
https://firebase.google.com/docs/functions/firestore-events

Process queue with redux-saga

I am trying to implement a queue handler for managing notifications with a redux-saga generator.
Basically, I need to show notifications sequentually as they enter the queue.
For this, I have a queue array in the redux store, an action QUQUE_NOTIFICATION action to add to queue and SHOW_NOTIFICATION to remove a notification for queue.
My current saga implementation is that simple :
export function* watchQueue() {
while (true) {
const state = yield select()
const queue = state.queue
if (queue.length > 0) {
yield put({ action: 'SHOW_NOTIFICATION', queue[0])
}
yield call(delay, 5000);
}
}
}
The problem with current implementation is that when a queue is empty a QUQUE_NOTIFICATION is dispatched generator can be waiting for the delay to finish.However, I want to show the first notification as soon as it enters the queue. Any ideas?
I've had the same idea for showing up notification (queueing them) however saga provides already implemented solution in terms of channels.
I have:
export function * notificationSaga () {
const requestChan = yield actionChannel(Notification.request)
while (true) {
const { payload } = yield take(requestChan)
yield call(showNotification, payload)
}
}
which I believe is elegant solution to your problem.
showNotification is another function which actually shows notifications and waits a bit before taking it down.

Resources