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;
}
Related
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!
We currently have a method that returns a Future<Stream<Position>> just because internally we have to await the result of a method returning a Future before we can call another method that returns the Stream<Position> which we are actually interested in. Here is the code:
Future<Stream<Position>> getPositionStream(
[LocationOptions locationOptions = const LocationOptions()]) async {
PermissionStatus permission = await _getLocationPermission();
if (permission == PermissionStatus.granted) {
if (_onPositionChanged == null) {
_onPositionChanged = _eventChannel
.receiveBroadcastStream(
Codec.encodeLocationOptions(locationOptions))
.map<Position>(
(element) => Position._fromMap(element.cast<String, double>()));
}
return _onPositionChanged;
} else {
_handleInvalidPermissions(permission);
}
return null;
}
So what happens here is:
We await the _getLocationPermission() method so that we can test if the user grants us permission to access to the location services on their device (Android or iOS);
If the user grants us permission we return a Stream<Position> which will update every time the device registers a location change.
I have the feeling we can also handle this without doing an await and returning a Future. Something along the lines of:
Manually create and return an instance of the Stream<Position> class;
Handle the logic of checking the permissions and calling the _eventChannel.receiveBroadcastStream in the then() method of the Future<PermissionStatus> returned from the _getLocationPermission() method (so we don't have to await it);
Copy the events send on the stream from the _eventChannel.receiveBroadcastStream onto the earlier created (and returned) stream.
Somehow this seems to be possible, but also includes some overhead in managing the stream and make sure it closes and is cleaned up correctly during the live cycle of the plugin or when the user unsubscribes pass through the events to the _eventChannel etc.
So I guess the question would be, what would be the best way to approach this situation?
You can write the code as an async* function, which will return a Stream and still allows await in the body:
Stream<Position> getPositionStream(
[LocationOptions locationOptions = const LocationOptions()]) async* {
PermissionStatus permission = await _getLocationPermission();
if (permission == PermissionStatus.granted) {
if (_onPositionChanged == null) {
_onPositionChanged = _eventChannel
.receiveBroadcastStream(
Codec.encodeLocationOptions(locationOptions))
.map<Position>(
(element) => Position._fromMap(element.cast<String, double>()));
}
yield* _onPositionChanged;
} else {
_handleInvalidPermissions(permission);
}
}
Alternatively, if you are using a non-async function, you can also use StreamCompleter from package:async.
It allows you to return a Stream now, even if you only get the real stream later. When that happens, you "complete" the StreamCompleter with the real stream, and the original stream will behave as if it was the real stream.
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
Consider this (textbook) sample dart code:
// Sequential processing using async and await.
main() async {
await expensiveA();
await expensiveB();
doSomethingWith(await expensiveC());
}
I understand that await will wait for the result of the future "expensiveA" to complete before continuing.
But what I don't understand is why main() must itself be declared async? Apparently you have to do this or the program will not compile. But why can a synchronous method not await the result of asynchronous calls?
I understand this must be a newbie question but I have not been able to find an answer for this yet.
Before async/await, which was only added a while ago to Dart, you would have written like
main() {
var result = expensiveA()
.then((_) => expensiveB())
.then((_) => expensiveC()); // the Future returned by expensiveC will be returned because `doSomethingWith` is not awaited in your code
result.then(doSomethingWith);
return result;
}
An async call returns a Future, and you can pass a function to it's then method to be called when the async call completes.
This code
main() async {
await expensiveA();
await expensiveB();
doSomethingWith(await expensiveC()); // doSomethingWith is not awaited
}
get's rewritten to something similar to the code in the first code block, before it's executed.
This shows that async/await doesn't change the nature of the code, it only changes the syntax to something that looks more like sync code.
async is a marker that the code in the method needs to be rewritten, and this marker allows the use of await, without rewriting await would have no meaning.
async implicitly returns the last awaited Future, so that the caller is able to await the completion of this Future.
Because a Future is returned, strong mode requires the return type to match.
Why can a synchronous method not await the result of asynchronous calls?
Because that would mean blocking until expensive call is finished.
However Dart is single threaded with event loop, so having easy way to block whole Dart app would be extremely dangerous.
Suppose I have 2 collections "PlanSubscriptions" and "ClientActivations". I am serially doing a insert on both the collections.
Later one depends on previous one, if any of the transaction fails then the entire operation must rollback.
How can I achieve that in Meteor 1.4?
Since MongoDB doesn't support atomicity, you will have to manage it with Method Chaining.
You can write a method, say, transaction where you will call PlanSubscriptions.insert(data, callback). Then in the callback function you will call ClientActivations.insert(data, callback1) if the first insertion is success and in callback1 return truthy if second insertion is succes, otherwise falsy. If the first insertion returns error you don't need to do anything, but if the second insertion returns error then remove the id got from the insertion in first collection.
I can suggest following structure:
'transaction'(){
PlanSubscriptions.insert(data, (error, result)=>{
if(result){
// result contains the _id
let id_plan = result;
ClientActivations.insert(data, (error, result)=>{
if(result){
// result contains the _id
return true;
}
else if(error){
PlanSubscriptions.remove(id_plan);
return false;
}
})
}
else if(error){
return false;
}
})
}
There is no way to do that in Meteor, since mongodb is not an ACID-compliant database. It has a single-document update atomicity, but not a multiple-document one, which is your case with the two collections.
From the mongo documentation:
When a single write operation modifies multiple documents, the modification of each document is atomic, but the operation as a whole is not atomic and other operations may interleave.
A way to isolate the visibility of your multi-document updates is available, but it's probably not what you need.
Using the $isolated operator, a write operation that affects multiple documents can prevent other processes from interleaving once the write operation modifies the first document. This ensures that no client sees the changes until the write operation completes or errors out.
An isolated write operation does not provide “all-or-nothing” atomicity. That is, an error during the write operation does not roll back all its changes that preceded the error.
However, there are a couple of libraries which try to tackle the problem at the app-level. I recommend taking a look at fawn
In your case, where you have exactly two dependent collections, it's possible to take advantage of the two phase commits technique. Read more about it here: two-phase-commits
Well I figured it out myself.
I added a package babrahams:transactions
At server side Meteor Method call, I called tx Object that is globally exposed by the package. The overall Server Side Meteor.method({}) looks like below.
import { Meteor } from 'meteor/meteor';
import {PlanSubscriptions} from '/imports/api/plansubscriptions/plansubscriptions.js';
import {ClientActivations} from '/imports/api/clientactivation/clientactivations.js';
Meteor.methods({
'createClientSubscription' (subscriptionData, clientActivationData) {
var txid;
try {
txid = tx.start("Adding Subscription to our database");
PlanSubscriptions.insert(subscriptionData, {tx: true})
ClientActivations.insert(activation, {tx: true});
tx.commit();
return true;
} catch(e){
tx.undo(txid);
}
return false;
}
});
With every insert I had added {tx : true}, this concluded it to be a apart of transaction.
Server Console Output:
I20170523-18:43:23.544(5.5)? Started "Adding Subscription to our database" with
transaction_id: vdJQvFgtyZuWcinyF
I20170523-18:43:23.547(5.5)? Pushed insert command to stack: vdJQvFgtyZuWcinyF
I20170523-18:43:23.549(5.5)? Pushed insert command to stack: vdJQvFgtyZuWcinyF
I20170523-18:43:23.551(5.5)? Beginning commit with transaction_id: vdJQvFgtyZuWcinyF
I20170523-18:43:23.655(5.5)? Executed insert
I20170523-18:43:23.666(5.5)? Executed insert
I20170523-18:43:23.698(5.5)? Commit reset transaction manager to clean state
For more Information you can goto link : https://github.com/JackAdams/meteor-transactions
NOTE: I am using Meteor 1.4.4.2
Just sharing this link for future readers:
https://forums.meteor.com/t/solved-transactions-with-mongodb-meteor-methods/48677
import { MongoInternals } from 'meteor/mongo';
// utility async function to wrap async raw mongo operations with a transaction
const runTransactionAsync = async asyncRawMongoOperations => {
// setup a transaction
const { client } = MongoInternals.defaultRemoteCollectionDriver().mongo;
const session = await client.startSession();
await session.startTransaction();
try {
// running the async operations
let result = await asyncRawMongoOperations(session);
await session.commitTransaction();
// transaction committed - return value to the client
return result;
} catch (err) {
await session.abortTransaction();
console.error(err.message);
// transaction aborted - report error to the client
throw new Meteor.Error('Database Transaction Failed', err.message);
} finally {
session.endSession();
}
};
import { runTransactionAsync } from '/imports/utils'; // or where you defined it
Meteor.methods({
async doSomething(arg) {
// remember to check method input first
// define the operations we want to run in transaction
const asyncRawMongoOperations = async session => {
// it's critical to receive the session parameter here
// and pass it to every raw operation as shown below
const item = await collection1.rawCollection().findOne(arg, { session: session });
const response = await collection2.rawCollection().insertOne(item, { session: session });
// if Mongo or you throw an error here runTransactionAsync(..) will catch it
// and wrap it with a Meteor.Error(..) so it will arrive to the client safely
return 'whatever you want'; // will be the result in the client
};
let result = await runTransactionAsync(asyncRawMongoOperations);
return result;
}
});