Get new created data back - firebase

Is it possible to get the data you just saved back from Firebase via AngularFire2?
I want to have the saved data with it's generated UUID.
I'm saving the data like this:
const phase = this.fireStore.collection("pr").doc(prId).collection("col1").doc(phase)
phase.collection("content").add({"content": post.content})
Now I want to return the saved data back as an Observable that has a map for my custom object.
What I now have is that I first convert it to an Observable and then I do the get via valueChanges.
To me this seems like overkill.

I'm getting it done like this:
const phase = this.fireStore.collection("projects").doc(post.projectId).collection("canvas1").doc(post.phase)
return from(phase.collection("content").add({ "content": post.content })).pipe(map(ref => {
return new Post(ref.id, post.content)
}))
if someone knows a better way please share :-)

Related

How to save a data to nested document

I have a data structure that looks like this:
users > {user-auto-id} > network > {network-auto-id}
I m not sure how I can add entries to the network. I m updating a document of users like the following which is working fine
const captureCol = collection(
firestore,
'users'
);
const docToUpdate = doc(myCol, 'id123');
const udata: any = {
title: 'title',
};
if (data.testExecutionLinkData) {
delete udata.testExecutionLinkData;
}
await updateDoc(docToUpdate, udata);
I tried to get the reference of the nested document like this, but it's failing
docToUpdate.collection('network1'); // ERROR: collection is not a function
You're getting an error when using:
docToUpdate.collection('network1');
Because you're creating a collection reference and not a document reference. To solve this, you have to create a reference that points exactly to the document you need to be updated. In code it should look like this:
db.collection('users').doc('user-auto-id').collection('network').doc('network-auto-id')
And right after that perform the update.

Retrieve values from firebase database in conversation flow

I am trying to grab information from my firebase database after a particular intent is invoked in my conversation flow.
I am trying to make a function which takes a parameter of user ID, which will then get the highscore for that user, and then say that users highscore back to them.
app.intent('get-highscore', (conv) => {
var thisUsersHighestscore = fetchHighscoreByUserId(conv.user.id);
conv.ask('your highest score is ${thisUsersHighestScore}, say continue to keep playing.');
});
function fetchHighscoreByUserId(userId){
var highscoresRef = database.ref("highscores");
var thisUsersHighscore;
highscoresRef.on('value',function(snap){
var allHighscores= snap.val();
thisUsersHighscore = allHighscores.users.userId.highscore;
});
return thisUsersHighscore;
}
An example of the data in the database:
"highscores" : {
"users" : {
"1539261356999999924819020" : {
"highscore" : 2,
"nickname" : "default"
},
"15393362381293223232222738" : {
"highscore" : 78,
"nickname" : "quiz master"
},
"15393365724084067696560" : {
"highscore" : "32",
"nickname" : "cutie pie"
},
"45343453535534534353" : {
"highscore" : 1,
"nickname" : "friendly man"
}
}
}
It seems like it is never setting any value to thisUsersHighScore in my function.
You have a number of issues going on here - both with how you're using Firebase, how you're using Actions on Google, and how you're using Javascript. Some of these issues are just that you could be doing things better and more efficiently, while others are causing actual problems.
Accessing values in a structure in JavaScript
The first problem is that allHighscores.users.userId.highscore means "In an object named 'allHighscores', get the property named 'users', from the result of that, get the property named 'userId'". But there is no property named "userId" - there are just a bunch of properties named after a number.
You probably wanted something more like allHighscores.users[userId].highscore, which means "In an object named 'allHighscores', get the property named 'users', fromt he result of that, get the property named by the value of 'userId'".
But if this has thousands or hundreds of thousands of records, this will take up a lot of memory. And will take a lot of time to fetch from Firebase. Wouldn't it be better if you just fetched that one record directly from Firebase?
Two Firebase Issues
From above, you should probably just be fetching one record from Firebase, rather than the whole table and then searching for the one record you want. In firebase, this means you get a reference to the path of the data you want, and then request the value.
To specify the path you want, you might do something like
var userRef = database.ref("highscores/users").child(userId);
var userScoreRef = userRef.child( "highscore" );
(You can, of course, put these in one statement. I broke them up like this for clarity.)
Once you have the reference, however, you want to read the data that is at that reference. You have two issues here.
You're using the on() method, which fetches the value once, but then also sets up a callback to be called every time the score updates. You probably don't need the latter, so you can use the once() method to get the value once.
You have a callback function setup to get the value (which is good, since this is an async operation, and this is the traditional way to handle async operations in Javascript), but you're returning a value outside of that callback. So you're always returning an empty value.
These suggest that you need to make fetchHighScoreByUserId() an asynchronous function as well, and the way we have to do this now is to return a Promise. This Promise will then resolve to an actual value when the async function completes. Fortunately, the Firebase library can return a Promise, and we can get its value as part of the .then() clause in the response, so we can simplify things a lot. (I strongly suggest you read up on Promises in Javascript and how to use them.) It might look something like this:
return userScoreRef.once("value")
.then( function(scoreSnapshot){
var score = scoreSnapshot.val();
return score;
} );
Async functions and Actions on Google
In the Intent Handler, you have a similar problem as above. The call to fetchHighScoreByUserId() is async, so it doesn't finish running (or returning a value) by the time you call conv.ask() or return from the function. AoG needs to know to wait for an async call to finish. How can it do that? Promises again!
AoG Intent Handlers must return a Promise if there is an asyc call involved.
Since the modified fetchHighScoreByUserId() returns a Promise, we will leverage that. We'll also set our response in the .then() part of the Promise chain. It might look something like this:
app.intent('get-highscore', (conv) => {
return fetchHighscoreByUserId(conv.user.id)
.then( function(highScore){
conv.ask(`Your highest score is ${highScore}. Do you want to play again?`);
} );
});
Two asides here:
You need to use backticks "`" to define the string if you're trying to use ${highScore} like that.
The phrase "Say continue if you want to play again." is a very poor Voice User Interface. Better is directly asking if they want to play again.

Ordering data does not actually order anything

I'm trying to get a dataset of messages out of my firebase database and want the messages sorted by added/timestamp. But for some reason no orderby I put in the code is actually used. I tried doing these 2 things.
_messagesRef = FirebaseDatabase.instance.reference().child('messages/'+key);
_membersSubscription = _messagesRef
.orderByChild('timestamp')
.onValue//On valuechange
.listen((Event event) => _messagesSubscriptionCallback(event));
_messagesRef = FirebaseDatabase.instance.reference().child('messages/'+key);
_membersSubscription = _messagesRef
.orderByKey()
.onValue//On valuechange
.listen((Event event) => _messagesSubscriptionCallback(event));
Both give me back the same dataset that is not ordered by timestamp or key in the callback. I've added the output underneath
{
-LA-Aw6crEAV53LxdujP:{
sender:1508,
message:test s9 2,
timestamp:1523642778089
},
-LA-Arby61T1UG5URMn6:{
sender:1508,
message:test s9,
timestamp:1523642759679
},
-LA-AyC7F8KAqceZBE3j:{
sender:1509,
message:test emu 1,
timestamp:1523642786632
},
-LA22WiUfL2tbh7-OjtM:{
sender:1508,
message:Blaj,
timestamp:1523690904480
},
-LA-B29RRXbdOPT1mG7m:{
sender:1508,
message:tesy3,
timestamp:1523642806940
}
}
This is how the data should be.
I hope someone can help me with this issue. I think I might misunderstand how ordering data works with Firebase
Kind regards,
Namanix
The result you show is a JSON object with other objects in there. JSON objects are never ordered as far as I know, only retrievable by key. JSON Arrays would be, but it doesn't look like you get that. When you would change this to an array the document IDs would have to be inside the document instead of being the object key. My guess would be that 'orderBy' is meant to be used for example to limit the number of items you get for pagination. Than you can order by timestamp, limit the number of items to 20 and search from the last timestamp you got.
I think if you want to order them I would put them in a new list of objects which can be ordered.
Most likely (it's hard to be sure without seeing _messagesSubscriptionCallback) you're throwing the ordering information away when you convert the data from Firebase into a plain JSON object, which (as Rene also says) doesn't have any defined order.
But the data your request from Firebase does have ordering information, you just have to be sure to not drop it.
The usual way to do this is to listen for onChildAdded instead of onValue. With that Firebase will invoke onChildAdded for each child in turn, and it will do so in the order you requested.
Also see this example and for example what FirebaseAnimatedList does here.
I now temporarily fixed it by doing this in my callback. But this feels like a very bad way to fix it. I hope to get some thoughts on this.
static void _messagesSubscriptionCallback(Event event) {
_messagesList.clear();
_messages.clear();
_messages = event.snapshot.value;
_messagesList = _messages.keys.toList();
_messagesList.sort((a, b) {
return b.compareTo(a) ;
});
onMessagesChange();
}

Cloud Functions database event always contains empty data

I have an issue with my cloud functions where in all my database events all return empty. For example, in the following event the event.data.val() would return null. I am doing an update operation and have tested the update by testing the cloud function using the shell as well as after deploying.
export const createSubscription = functions.database.ref('/users/{userId}/subscription').onWrite( event => {
if(!event.data.val()) {
return;
}
});
But I can easily hook into the auth.user() events like the following and receive the data.
export const createStripeUser = functions.auth.user().onCreate(event => {
const user = event.data;
});
Edit: Passing data into the collection for example like the one below on the emulator console
createSubscription({
testKey: 'testValue'
})
or the following on from my frontend
db.ref(`/users/23213213213/subscription`).update({ testKey: 'testValue'});
would return null on the function.
DougStevenson is correct. For the .onCreate() you would be doing it correct wit a myDatabaseFunction('new_data').
With the .onWrite() you need to pass in the before and after like my example below.
You may have got stuck were I did. Note that I have a curly bracket around the before and after the final JSON. It didn't work properly without them.
addComment({before: {"comment":"before comment","role":"guest"}, after:{"comment":"After Comment","role":"guest"}})
Hope my example helps a bit more than a generic string parameter.
Good Luck!

Firebase trouble reading lists

The tutorial I'm following here https://www.firebase.com/tutorial/#tutorial/basic/5 says that for lists you can push a new item like this...
var myDataRef = new Firebase("https://.../items"
myDataRef.push({name: "Sally"});
The docs say here https://www.firebase.com/docs/managing-lists.html that to read list data just do something like this
myDataRef.on('value', function(snapshot) {
console.log(snapshot.val());
});
snapshot.val() gives back an object with random keys that contain my objects. Do I really have to do something like
arr =[] Object.keys(snapshot.val()).forEach(function(key) { arr.push(snapshot.val()[key])})
to get a normal looking array of objects? or am I missing something here.. Thanks a ton.
I'm not entirely sure what you're asking, but see if this helps to retrieve the data you're looking for with snapshot.val().Use child_added, this will go down the endpoint("firebase_url/items") and show each immediate object.
myDataRef.on('child_added', function(snapshot) {
console.log(snapshot.val());
});
Check out this link to get more info on reading from firebase, http://www.firebase.com/docs/reading-data.html
JavaScript objects are not ordered. So the keys may appear in different order than your data (JSON can put them anywhere it pleases). To iterate them in order, try forEach:
myDataRef.on('value', function(snapshot) {
snapshot.forEach(function(ss) {
console.log(ss.name());
});
});
For more real-time results, Ron's example using child_added is ideal.

Resources