Obtaining data from Firebase and creating an object in Flutter, Dart - firebase

I would like to create an Object using data from my Firebase database. I get the data correctly, but I can't create that Objet in the way im trying. I have no idea why it doesn't work, this is my first time using Dart.
Here is my code
MaterialQR selectFromFirebase(String document) {
MaterialQR res = MaterialQR(exercises: [], image: '', name: '', id: '');
FirebaseFirestore.instance.collection('MaterialQR')
.doc(document).get()
.then((value) => res = new MaterialQR(
exercises: value['exercises'],
image: value['image'],
name: value['name'],
id: value['id']));
return res;
}
The objective is return an "empty" Object if the document in my database doesn't exist or return a correct Object if the document exists. When I print(value['id']) inside .then(), I got a correct id, but it doesn't work when I create the new Object.

This is because data is loaded from Firestore (and pretty much any modern cloud API) asynchronously, because it may take some time before it's available. Instead of blocking your code (and the user) during this time, your main code actually continues to execute. Then when the data is available, your then block executes.
This is easiest to see if you place some logging in the code, run it, and check its output.
print('Before starting to read from the database');
FirebaseFirestore.instance.collection('MaterialQR')
.doc(document).get()
.then((value) => {
print('got data from the database');
});
print('After starting to read from the database');
The output of this is:
Before starting to read from the database
After starting to read from the database
Got data from the database
This is probably not what you expected, but it explains perfectly why you don't get a result from selectFromFirebase: the return res runs before res is ever set by your then block.
There is no way to prevent this asynchronous nature of the call. Instead you'll have to accept that calling cloud APIs is asynchronous, and return a Future from the method and mark is as async:
Future<MaterialQR> selectFromFirebase(String document) async {
That also means you can use await in there, so the entire function then becomes:
Future<MaterialQR> selectFromFirebase(String document) async {
try {
var value = await FirebaseFirestore.instance.collection('MaterialQR')
.doc(document).get();
return MaterialQR(
exercises: value['exercises'],
image: value['image'],
name: value['name'],
id: value['id']);
}
catch {
return MaterialQR(exercises: [], image: '', name: '', id: '');
}
}
See also:
The Flutter documentation on asynchronous operations and futures
The Flutter codelab on Asynchronous programming: futures, async, await.

Related

How to Firebase Firestore realtime update Only new data added to collect?

For example : WorkingTask App. I use Redux - React
Structure
JobOrders (collections) / eachJob (docu)
- the app is allow only insert new JOB.
- the whole day has 500-1000 Jobs.
- the all jobs must be used to recompute the report realtime.
- clear data once every midnight.
I know how to fetch and listen-realtime callback for the whole.
but the problem is the callback function must recompute the whole documents (maybe 300-1000 Jobs every-time when a new JOB arrived.)
This is I want:
- every new JOB arrived only new data in callback then I can recompute in REDUX.
- and prevent for some data is delay due to internet network connection.
I come across with : to listen limit to 1 and sortBy timestamp 'desc'.
But the problem is if some JOB are delay due to network connection, it will not call in callback.
I'm looking for this problem also but now i find out
onAddSnapshotListener
// as real time update.
only get the new or changed documents
https://youtu.be/3aoxOtMM2rc?t=195
Try to watch the whole series
I think Firestore querySnaptshot should have Information for documents changed
for example Node.js
let observer = db
.collection('cities')
.where('state', '==', 'CA')
.onSnapshot(querySnapshot => {
querySnapshot.docChanges().forEach(change => {
if (change.type === 'added') {
console.log('New city: ', change.doc.data());
}
if (change.type === 'modified') {
console.log('Modified city: ', change.doc.data());
}
if (change.type === 'removed') {
console.log('Removed city: ', change.doc.data());
}
});
});

Is there a race condition for Database Triggers? Returning non-null values as null

I have a cloud function that is triggered when an account is created. This function populates a few fields in the user's Firebase Database entry. One of the fields is named 'accntActive', and triggers a second cloud function to do some other tasks.
In the second cloud function that gets triggered on a change of the accntActive field, the first thing I do is read the value of accntActive to check if it was changed to the value 'true'. What I am seeing is that about 50% of the time, the cloud function crashes when I try to use this value, saying it is undefined/null. Up until a week ago this always worked fine, 100% of the time, so the code itself has not changed. When I examine the user's DB entry at a later point in time, 100% of the time it has been populated correctly.
I am wondering if there is some race condition, where the DB cloud function is triggered before the value that got written is stable, and thus cannot be read immediately. This would seem a bit ridiculous, given the entire point of the DB cloud function is to respond to the value that is being changed.
The code for the second function:
exports.myActiveFxn = functions.database.ref('/users/{uid}/accntStatus/active')
.onWrite(event => {
const accntActive = event.data.val();
const userID = event.params.uid;
console.log(accntActive.toString()) //This throws an exception since accntActive is sometimes undefined
In the above, note that the userID variable is always stable. I've even tried re-reading the value once inside the function, and I get the same result:
admin.database().ref('/users/'+userID+'/accntStatus/active').once('value').then(function (snapshot) {
var accntActive = snapshot.val();
console.log(accntActive.toString());
}).then(function() {
Does anybody have any insight into what I can do to fix this? Thank you in advance!
Edit: Here is the code for the first function
exports.authUserCreated = functions.auth.user().onCreate(function(event) {
var UID = event.data.uid;
admin.database().ref('/users/'+UID).set({
email: event.data.email,
accntStatus: {
active: "true",
key: randomstring.generate(22)
},
uid: UID
});
return;
});
In your function, you aren't returning a promise that resolves when the work is complete. If you don't do that, strange and inconsistent things may happen. Please read the documentation on async programming with Cloud Functions.
You should return from the function the promise that's returned by the set() method on the database ref.
exports.authUserCreated = functions.auth.user().onCreate(function(event) {
var UID = event.data.uid;
return admin.database().ref('/users/'+UID).set({
email: event.data.email,
accntStatus: {
active: "true",
key: randomstring.generate(22)
},
uid: UID
});
});

EmberFire: Getting property generated by Cloud Function when saving record completes

I use a Cloud Function to generate a short unique URL on a record on the 'onWrite' event, and save it. This works well, but when I save a record from my Ember app using EmberFire, I do get a model back as an argument to a callback, but the URL of this model is undefined. Is there a way to return this back to the client? Or do I need to query the record to get the generated URL?
This is how my Cloud Function code looks:
exports.generateUrl = functions.database.ref('/tournaments/{tid}')
.onWrite(event => {
if (event.data.previous.exists()) {
return;
}
if (!event.data.exists()) {
return;
}
const url = shortid.generate();
return event.data.ref.update({ url });
});
Here is my component that saves data through form submission. I'm using an add-on called ember-changeset to handle some validations, but this shouldn't be related to the issue.
export default Ember.Component.extend({
submit(e) {
e.preventDefault();
let snapshot = this.changeset.snapshot();
return this.changeset
.cast(Object.keys(this.get('schema')))
.validate()
.then(() => {
if (this.changeset.get('isValid')) {
return this.changeset
.save()
.then((result) => {
// Here, result.get('url') is undefined.
})
}
})
}
});
If you have a function that writes new data back to a location in the database after a write, you'll have to keep listening to that location on the client in order to get that data back. Don't use a one-time read (once()), use a persistent listener (on()), and in that listener, make sure you're getting the URL or whatever you expect to be generated by the function. Then remove that listener if you don't need it any more.
(Sorry, I don't know Ember or what abstractions it provides around Realtime Database - I'm giving you the plain JavaScript API methods you'd use on a reference.)

How to 'transform' data returned via a Meteor.publish?

Meteor Collections have a transform ability that allows behavior to be attached to the objects returned from mongo.
We want to have autopublish turned off so the client does not have access to the database collections, but we still want the transform functionality.
We are sending data to the client with a more explicit Meteor.publish/Meteor.subscribe or the RPC mechanism ( Meteor.call()/Meteor.methods() )
How can we have the Meteor client automatically apply a transform like it will when retrieving data directly with the Meteor.Collection methods?
While you can't directly use transforms, there is a way to transform the result of a database query before publishing it. This is what the "publish the current size of a collection" example describes here.
It took me a while to figure out a really simple application of that, so maybe my code will help you, too:
Meteor.publish("publicationsWithHTML", function (data) {
var self = this;
Publications
.find()
.forEach(function(entry) {
addSomeHTML(entry); // this function changes the content of entry
self.added("publications", entry._id, entry);
});
self.ready();
});
On the client you subscribe to this:
Meteor.subscribe("publicationsWithHTML");
But your model still need to create a collection (on both sides) that is called 'publications':
Publications = new Meteor.Collection('publications');
Mind you, this is not a very good example, as it doesn't maintain the reactivity. But I found the count example a bit confusing at first, so maybe you'll find it helpful.
(Meteor 0.7.0.1) - meteor does allow behavior to be attached to the objects returned via the pub/sub.
This is from a pull request I submitted to the meteor project.
Todos = new Meteor.Collection('todos', {
// transform allows behavior to be attached to the objects returned via the pub/sub communication.
transform : function(todo) {
todo.update = function(change) {
Meteor.call('Todos_update', this._id, change);
},
todo.remove = function() {
Meteor.call('Todos_remove', this._id);
}
return todo;
}
});
todosHandle = Meteor.subscribe('todos');
Any objects returned via the 'todos' topic will have the update() and the remove() function - which is exactly what I want: I now attach behavior to the returned data.
Try:
let transformTodo = (fields) => {
fields._pubType = 'todos';
return fields;
};
Meteor.publish('todos', function() {
let subHandle = Todos
.find()
.observeChanges({
added: (id, fields) => {
fields = transformTodo(fields);
this.added('todos', id, fields);
},
changed: (id, fields) => {
fields = transformTodo(fields);
this.changed('todos', id, fields);
},
removed: (id) => {
this.removed('todos', id);
}
});
this.ready();
this.onStop(() => {
subHandle.stop();
});
});
Currently, you can't apply transforms on the server to published collections. See this question for more details. That leaves you with either transforming the data on the client, or using a meteor method. In a method, you can have the server do whatever you want to the data.
In one of my projects, we perform our most expensive query (it joins several collections, denormalizes the documents, and trims unnecessary fields) via a method call. It isn't reactive, but it greatly simplifies our code because all of the transformation happens on the server.
To extend #Christian Fritz answer, with Reactive Solution using peerlibrary:reactive-publish
Meteor.publish("todos", function() {
const self = this;
return this.autorun(function(computation) {
// Loop over each document in collection
todo.find().forEach(function(entry) {
// Add function to transform / modify each document here
self.added("todos", entry._id, entry);
});
});
});

angularFireCollection is not returning any data

I have no issues when using implicit updates (angelFire). However I need for some of my data use explicit updating. So I implemented angelFireCollection on the exact same ref I was using previously but despite the console.log explicitly saying that the read was granted and trying it with both with the onloadcallback and without, I don't get data directly into my assigned variable AND once the callback fires I get a strange looking object that DOES contain the data but not in the form I expect. My scope variable ends up with an empty collection. Never gets populated. Here is the code:
var streamController = function ($rootScope, $scope, $log, $location, angularFireCollection, profileService) {
//Wait for firebaseLogin...
$rootScope.$watch('firebaseAuth', init);
function init() {
if ($rootScope.firebaseAuth == false) {
return
};
var refUsers = new Firebase($rootScope.FBURL+'/users/'+$rootScope.uid);
$scope.profile = angularFireCollection(refUsers, function onload(snapshot) {
console.log(snapshot)
});
};
};
myApp.gwWebApp.controller('StreamController', ['$rootScope', '$scope', '$log', '$location', 'angularFireCollection', 'profileService',
streamController]);
}());
Here is what the console.log looks like ( ie; what snapshot looks like ):
>snapshot
T {z: R, bc: J, V: function, val: function, xd: function…}
Here is the earlier message before the snapshot was returned:
Firebase Login Succeeded! fbLoginController.js:16
FIREBASE: Attempt to read /users/529ccc5d1946a93656320b0a with auth={"username":"xxxxxxx#me.com","id":"529ccc5d1946a93656320b0a"} firebase.js:76
FIREBASE: /: "auth.username == 'admin'" firebase.js:76
FIREBASE: => false firebase.js:76
FIREBASE: /users firebase.js:76
FIREBASE: /users/529ccc5d1946a93656320b0a: "auth.id == $user" firebase.js:76
FIREBASE: => true firebase.js:76
FIREBASE:
FIREBASE: Read was allowed.
and finally the desired binding that ends up with an empty array: again from the console:
$scope.profile
[]
Anyone know what I could possibly be doing wrong?? This is like 5 lines of code. Frustrating.
I have put stops in angelFireCollection factory function and can see that the data is getting added to the collection in the callbacks inside that function but my binded variable never gets updated.
UPDATE
Ok experimenting with a plnkr. It seems that angularFireCollection EXPECTS your returning a LIST of items. The snapshot returns properly if you inspect snapshot.val() it will be whatever object structure was stored in firebase. IF you use angularFireCollection it does indeed bind to the variable HOWEVER it turns a non-list object into a garbled mess and you can not access the object user the normal dot operator. This is either a bug or it is a severe limitation of angularFireCollection which will cause me to revaluate how easily I can use firebase as the backend. I can't share my plnkr because it is accessing non-public data but tomorrow if i have time I will create a public firebase with an object store and demonstrate.
Ok. So it appears that indeed angularFireCollection is MEANT to be array based. Which is fine. It would be VERY helpful if the angularFire documentation was updated to make that clear. As such it is not an implicit vs explicit update technique.
For an explicit non-array based approach I have come up with the following code. Had I not been mislead by the documentation I would have gone down this path originally.
var MainCtrl = function($scope, angularFire) {
$scope.test = {};
var _url = 'https://golfwire.firebaseio.com/tmp';
var _ref = new Firebase(_url);
var promise = angularFire(_ref, $scope, 'implicit');
promise.then ( function(data){
$scope.explicit=angular.copy($scope.implicit );
});
}
You then work locally with the 'explicit' copy and when ready just update the 'implicit' by assigning: $scope.implicit = $scope.explicit.
Here is a plnkr: http://plnkr.co/edit/bLJrL1

Resources