Firebase: retriving friend's data (Ionic Framework) - firebase

I have an app where I need to fetch the user's friends geolocation data. What I need to do is:
1) loop the current user's node under "friends" node to get all of the user's friends
2) with that data, go loop "users/[[friend uid]]" for each friend and pull the lat and long from geolocation along with the friend's display name
This should be a subscribed event, as in if the geolocation updates in the server it should also show it in the app/update the variables.
Friends node structure is like this:
friends
current user UID
random id (not needed)
friend 1 UID (needed)
random id (not needed)
friend 2 UID (needed)
etc
Here's also a pic of the whole database structure:
Thank you!
Edit: here is the current code for retrieving friends, how could I implement getting the geolocation of each friend retrived?
friendsNode = firebase.database().ref('/friends');
....
getFriendsList() {
let friendsUID = [];
this.friendsNode.child(firebase.auth().currentUser.uid).on('value', (snapshot) => {
let allFriends = snapshot.val();
this.myFriends = [];
for (var i in allFriends)
friendsUID.push(allFriends[i].uid);
this.userService.getallusers().then((users) => {
this.myFriends = [];
for (var j in friendsUID)
for (var key in users) {
if (friendsUID[j] === users[key].uid) {
this.myFriends.push(users[key]);
}
}
this.events.publish('friends');
}).catch((err) => {
alert(err);
})
})
}

Related

How to filter list of id on firebase cloud firestore?

I have two collections. (applyJobs and Jobs and users). When users apply for a job, I store that record inside applyJobs collection. Like this:
applyId:****,
jobId:*****,
userId:*****
Now, I want to show all apply for jobs by a user.
First: Get logged user id, I store locally logged user id. So, I can get loggged user id.
Second: I filter Apply Jobs by that id. like this, var ref = _db.collection('applyJobs').where('userId',isEqualTo: uid);. I here I didn't call users collection to get uid. because I already store uid on locally. Is it best practice?
Third: I store result here List<ApplyJobsModelClass>. I want to get all jobs by a list of id. How do I filter it?
This is way I tried it. But this is not list of IDs. only one id.
streamApplyJob(List<String> jobId) {
Collection('jobs').document(jobId);
}
And I tried this way too.
Stream<List<JobModel>> streamApplyJob(List<String> jobId) {
var ref = _db.collection('jobs').where('jobId',isEqualTo: jobId);
return ref.snapshots().map((list) =>
list.documents.map((doc) => JobModel.fromFirestore(doc)).toList());
}
tried to get length, but result is 0
db.streamApplyJob(jobIds).listen((v)=>{
print(v.length)
});
Full Code
Database side
///Get a stream of apply jobs
Stream<List<ApplyJobModel>> streamApplyJobs(String uid) {
var ref = _db.collection('applyJobs').where('userId',isEqualTo: uid);
return ref.snapshots().map((list) =>
list.documents.map((doc) => ApplyJobModel.fromFirestore(doc)).toList());
}
///Get a stream of a single document
Stream<List<JobModel>> streamApplyJob(List<String> jobId) {
var ref = _db.collection('jobs').where('jobId',isEqualTo: jobId);
return ref.snapshots().map((list) =>
list.documents.map((doc) => JobModel.fromFirestore(doc)).toList());
}
calling
List<String> jobIds = [];
void getData() {
db.streamApplyJobs(widget.uid).listen((listApplies) => {
for (int i = 0; i < listApplies.length; i++)
{jobIds.add(listApplies[i].jobId)},
});
db.streamApplyJob(jobIds).listen((v)=>{
print(v.length)
});
}
Solution(It's working now)- Is it best practice or are there other best way to do this?
Future<List<JobModel>> getJobs() async {
await db.streamJobs(true).listen((jobs) {
setState(() {
jobModel = jobs;
});
});
return jobModel;
}
Future getData() async {
await getJobs();
db.streamApplyJobs(widget.uid).listen((apply) => {
for (int j = 0; j < jobModel.length; j++)
{
for (int i = 0; i < apply.length; i++)
{
if (apply[i].jobId == jobModel[j].jobId)
{
jobModelNew.add(jobModel[j]),
}
}
}
});
}
I want to get all jobs by a list of id. How do I filter it?
There currently is no way to pass in a list of IDs to a Firestore query and get documents matching all those IDs. See Google Firestore - how to get document by multiple ids in one round trip? (which talks about doing this with document IDs), and Firebase Firestore - OR query (which talks about filtering for multiple values on a single field).
Unless your use-case happens to match the workaround mentioned in that second answer, you'll have to perform a separate query for each value, and merge the results in your application code.
Not sure if it is documented anywhere officially, but this is possible now!
.where(admin.firestore.FieldPath.documentId(), "in", [array, of, ids])
Found here: https://stackoverflow.com/a/52252264/10562805
Please take a look at this example. It binds a CollectionReference to a List.
Let me know if this is helpful.

Unable to observe node (update not receiving) in firebase

I am creating a chat listing with react native and firebase. My firebase database is like this.
I am able to list the chats but it is not updating when a new chat adds or when I send a new message in old chat. My code is like this.
// find chat tread ids for a user.
firebase.database().ref('user/64/threads')
.on('value').then(snapshot => {
// find chat thread details for a id
const chatIds = snapshot.val();
for (let chatId of chatIds.keys()) {
firebase.database().ref('threads/'+chatId).once('value')
.then(snapshot1 => {
// find members from snapshot.val().members
// find id of other users.
if (chatId.split('-')[0] == '64') {
// find name of chatId.split('-')[1]
} else {
// find name of chatId.split('-')[0]
}
})
}
});
I am stuck in this forever. I am able to fetch chat listing one time but I am not getting whenever a new chat is added or when a new message is added in existing chat. Please help.

Firebase get value from database

The question is: How to take the uid row when you only know the user his username? for example, you only know 'senneken' and you want to know the uid of 'senneken'
Extra information:
My user database looks like this
I want to add friends to users. I can check if the user exists for them by doing
searchButton.addEventListener('click', function (event) {
event.preventDefault();
username = searchUsername.value;
var ref = firebase.database().ref('users').orderByChild("username").equalTo(username).once("value", snapshot => {
const userData = snapshot.val();
if (userData) {
console.log("Username " + username + " was found");
} else {
console.log("No user found");
}
But now I want to add the users UID in my database
And I can add the friends username by doing
addFriendButton.addEventListener('click', function (event) {
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
var ref = firebase.database().ref("users").child(user.uid).child("friends").push({
username: username
})
}
});
});
Because I use push there is always a random ID generated under friends but I would like to take the UID from the user that I want to add and put that under my friends (instead of the random UID)
In this case the collection of friends seems like a set: each specific UID can either be in there, or it cannot be in there. It cannot be in there more than once, and order seems to not matter. The solution is to not use a push ID, but model it as a set like this:
friends
uid1
uid2: true
uid3: true
This way you can simply set a user as a friend with:
firebase.database().ref("friends").child(user.uid).child(username).set(true)
You might notice that I also turned the collections of friends into a top-level collection. Nesting information about friends under other profile information about a user is an anti-pattern, which makes it hard to secure data, leads to downloading more data than is needed, and in general is not recommended by Firebase experts.

Firebase denormalization data consistency issue

I'm currently using Ionic CLI 3.19 with Cordova CLI 7.1.0 (#ionic-app-script 3.1.4)
The problem that I’m currently facing with is, I should update friends node values simultaneously every time the related data get changed from elsewhere. I’d like to clarify my objective with some screenshots to make it more clear.
As you can see from the image below, each child node consists of a user array that has a user id as a key of friends node. The reason why I store as an array is because each user could have many friends.
In this example, Jeff Kim has one friend which is John Doe vice versa.
When data in users node gets changed for some reason, I want the related data in friends node also want them to be updated too.
For example, when Jeff Kim changed his profile photo or statusMessage all the same uid that reside in friends node which matches with Jeff Kim’s uid need to be updated based on what user has changed.
user-service.ts
constructor(private afAuth: AngularFireAuth, private afDB: AngularFireDatabase,){
this.afAuth.authState.do(user => {
this.authState = user;
if (user) {
this.updateOnConnect();
this.updateOnDisconnect();
}
}).subscribe();
}
sendFriendRequest(recipient: string, sender: User) {
let senderInfo = {
uid: sender.uid,
displayName: sender.displayName,
photoURL: sender.photoURL,
statusMessage: sender.statusMessage,
currentActiveStatus: sender.currentActiveStatus,
username: sender.username,
email: sender.email,
timestamp: Date.now(),
message: 'wants to be friend with you.'
}
return new Promise((resolve, reject) => {
this.afDB.list(`friend-requests/${recipient}`).push(senderInfo).then(() => {
resolve({'status': true, 'message': 'Friend request has sent.'});
}, error => reject({'status': false, 'message': error}));
});
}
fetchFriendRequest() {
return this.afDB.list(`friend-requests/${this.currentUserId}`).valueChanges();
}
acceptFriendRequest(sender: User, user: User) {
let acceptedUserInfo = {
uid: sender.uid,
displayName: sender.displayName,
photoURL: sender.photoURL,
statusMessage: sender.statusMessage,
currentActiveStatus: sender.currentActiveStatus,
username: sender.username,
email: sender.email
}
this.afDB.list(`friends/${sender.uid}`).push(user);
this.afDB.list(`friends/${this.currentUserId}`).push(acceptedUserI
this.removeCompletedFriendRequest(sender.uid);
}
According to this clip that I've just watched, it looks like I did something called Denormalization and the solution might be using Multi-path updates to change data with consistency. Data consistency with Multi-path updates. However, it's kinda tricky to fully understand and start writing some code.
I've done some sort of practice to make sure update data in multiple locations without calling .update method twice.
// I have changed updateUsername method from the code A to code B
// Code A
updateUsername(username: string) {
let data = {};
data[username] = this.currentUserId;
this.afDB.object(`users/${this.currentUserId}`).update({'username': username});
this.afDB.object(`usernames`).update(data);
}
// Code B
updateUsername(username: string) {
const ref = firebase.database().ref();
let updateUsername = {};
updateUsername[`usernames/${username}`] = this.currentUserId;
updateUsername[`users/${this.currentUserId}/username`] = username;
ref.update(updateUsername);
}
I'm not trying to say this is a perfect code. But I've tried to figure this out on my own and here's what I've done so far.
Assume that I'm currently signed in as Jeff.
When I run this code all the associated data with Jeff in friends node gets changed, as well as Jeff's data in users node gets updated simultaneously.
The code needs to be improved by other firebase experts and also should be tested on a real test code.
According to the following thread, once('value' (which is, in general, a bad idea for optimal performance with Firebase). I should find out why this is bad.
friend.ts
getFriendList() {
const subscription = this.userService.getMyFriendList().subscribe((users: any) => {
users.map(u => {
this.userService.testMultiPathStatusMessageUpdate({uid: u.uid, statusMessage: 'Learning Firebase:)'});
});
this.friends = users;
console.log("FRIEND LIST#", users);
});
this.subscription.add(subscription);
}
user-service.ts
testMultiPathStatusMessageUpdate({uid, statusMessage}) {
if (uid === null || uid === undefined)
return;
const rootRef = firebase.database().ref();
const query = rootRef.child(`friends/${uid}`).orderByChild('uid').equalTo(this.currentUserId);
return query.once('value').then(snapshot => {
let key = Object.keys(snapshot.val());
let updates = {};
console.log("key:", key);
key.forEach(key => {
console.log("checking..", key);
updates[`friends/${uid}/${key}/statusMessage`] = statusMessage;
});
updates[`users/${this.currentUserId}/statusMessage`] = statusMessage;
return rootRef.update(updates);
});
}
The code below works fine when updating status to online but not offline.
I don't think it's the correct approach.
updateOnConnect() {
return this.afDB.object('.info/connected').valueChanges()
.do(connected => {
let status = connected ? 'online' : 'offline'
this.updateCurrentUserActiveStatusTo(status)
this.testMultiPathStatusUpdate(status)
})
.subscribe()
}
updateOnDisconnect() {
firebase.database().ref().child(`users/${this.currentUserId}`)
.onDisconnect()
.update({currentActiveStatus: 'offline'});
this.testMultiPathStatusUpdate('offline');
}
private statusUpdate(uid, status) {
if (uid === null || uid === undefined)
return;
let rootRef = firebase.database().ref();
let query = rootRef.child(`friends/${uid}`).orderByChild('uid').equalTo(this.currentUserId);
return query.once('value').then(snapshot => {
let key = Object.keys(snapshot.val());
let updates = {};
key.forEach(key => {
console.log("checking..", key);
console.log("STATUS:", status);
updates[`friends/${uid}/${key}/currentActiveStatus`] = status;
});
return rootRef.update(updates);
});
}
testMultiPathStatusUpdate(status: string) {
this.afDB.list(`friends/${this.currentUserId}`).valueChanges()
.subscribe((users: any) => {
users.map(u => {
console.log("service U", u.uid);
this.statusUpdate(u.uid, status);
})
})
}
It does show offline in the console, but the changes do not appear in Firebase database.
Is there anyone who could help me? :(
I think you are right doing this denormalization, and your multi-path updates is in the right direction. But assuming several users can have several friends, I miss a loop in friends' table.
You should have tables users, friends and a userFriend. The last table is like a shortcut to find user inside friends, whitout it you need to iterate every friend to find which the user that needs to be updated.
I did a different approach in my first_app_example [angular 4 + firebase]. I removed the process from client and added it into server via onUpdate() in Cloud functions.
In the code bellow when user changes his name cloud function executes and update name in every review that the user already wrote. In my case client-side does not know about denormalization.
//Executed when user.name changes
exports.changeUserNameEvent = functions.database.ref('/users/{userID}/name').onUpdate(event =>{
let eventSnapshot = event.data;
let userID = event.params.userID;
let newValue = eventSnapshot.val();
let previousValue = eventSnapshot.previous.exists() ? eventSnapshot.previous.val() : '';
console.log(`[changeUserNameEvent] ${userID} |from: ${previousValue} to: ${newValue}`);
let userReviews = eventSnapshot.ref.root.child(`/users/${userID}/reviews/`);
let updateTask = userReviews.once('value', snap => {
let reviewIDs = Object.keys(snap.val());
let updates = {};
reviewIDs.forEach(key => { // <---- note that I loop in review. You should loop in your userFriend table
updates[`/reviews/${key}/ownerName`] = newValue;
});
return eventSnapshot.ref.root.update(updates);
});
return updateTask;
});
EDIT
Q: I structured friends node correctly or not
I prefer to replicate (denormalize) only the information that I need more often. Following this idea, you should just replicate 'userName' and 'photoURL' for example. You can aways access all friends' information in two steps:
let friends: string[];
for each friend in usrService.getFriend(userID)
friends.push(usrService.getUser(friend))
Q: you mean I should create a Lookup table?
The clip mentioned in your question, David East gave us an example how to denormalize. Originaly he has users and events. And in denormalization he creates eventAttendees that is like a vlookup (like you sad).
Q: Could you please give me an example?
Sure. I removed some user's information and add an extra field friendshipTypes
users
xxsxaxacdadID1
currentActiveStatus: online
email: zinzzkak#gmail.com
gender: Male
displayName: Jeff Kim
photoURL: https://firebase....
...
trteretteteeID2
currentActiveStatus: online
email: hahehahaheha#gmail.com
gender: Male
displayName: Joeh Doe
photoURL: https://firebase....
...
friends
xxsxaxacdadID1
trteretteteeID2
friendshipTypes: bestFriend //<--- extra information
displayName: Jeff Kim
photoURL: https://firebase....
trteretteteeID2
xxsxaxacdadID1
friendshipTypes: justAfriend //<--- extra information
displayName: John Doe
photoURL: https://firebase....
userfriends
xxsxaxacdadID1
trteretteteeID2: true
hgjkhgkhgjhgID3: true
trteretteteeID2
trteretteteeID2: true

Firebase - How to permanently save user profile?

Firebase Console only allows to set email address and password, there is no option to save user's profile but this can be done using code:
user.updateProfile({
displayName: "Chinmay Sarupria"
}).then(function() {
console.log(user.displayName);
}, function(error) {
console.log(error);
});
If this is the way to save user data permanently then it is impossible to write code for every user just to save their displayName like this or is doing via code permanent, at the moment it is working for me but I'm not sure if it will remain like that forever.
Ofcourse, I could save the user data in realtime database and then fetch it based on user's uid but if saving user data in the user variable is possible then that is much better than getting the data from database.
After you reference your user object you can update values under the UID for that user.
var rootRef = new Firebase('https://yourapp.firebaseio.com');
// Check the current user login status and redirect if not logged in
var user = rootRef.getAuth();
if (user) {
var user = rootRef.getAuth();
var userRef = rootRef.child('users').child(user.uid);
... do something with the logged in user...
}
function writeData () {
var user = rootRef.getAuth();
var userRef = rootRef.child('users').child(user.uid);
var profileRef=userRef.child('profile').push();
profileRef.update ({
name: "Tony",
position: "Developer"
});
};
This should give your user profile a structure something like this:
}
"users" : {
"067f75bf-4a07-473e-82e5-d9a5ee11be17" : {
"profile" : {
"-KN2dG5X4lLpp0fwfsXK" : {
"name" : "Tony",
"position" : "Developer"
}
}
}
}
Note the push() function gives you the randomly generated key. You may not need it.
Hope this helps.

Resources