How to join data from multiple locations in your Firebase DB? - firebase

I have an index of notification ids associated with a user X. When I want to display these notifications I need to fetch them from the user X's notifications index which just contains the
a) id of the notification (nid)
b) status: "read/unread"
But I also need to go to
a) /notifcations/nid to get my notification data: text and fromUser
b) /users/fromUser to get the user data for that user
My question is about structure.
Did i structure my data properly and if so, how can I fetch all this information inside the Notification and when ready, present it to angular for displaying on the screen.
Here is my take on that below. Any critique (code/structure/anything) is welcome. Thanks.
P.S.
Also if I am replacing POJOs in firebaseArray with instances of Notification, how would that affect my DB when I say edit the notification and save it back to Firebase. How does AngularFire know what to save back?
/* Notifications */
.factory('Notifications', function(firebaseUrl, NotificationsFactory){
var ref = new Firebase(firebaseUrl);
return {
getNotificationsForUser : function(uid){
var userNotifRef = ref.child('users').child(uid).child('notifications');
return new NotificationsFactory(userNotifRef);
}
}
})
.factory('NotificationsFactory', function($firebaseArray, Notification) {
return $firebaseArray.extend({
$$added : function(snapshot, prevChild){ //called anytime a 'child_added' event occurs
return new Notification(snapshot);
}
})
})
.factory('Notification', function(firebaseUrl, $q, Users) {
var notifRef = (new Firebase(firebaseUrl)).child('notifications');
var usersRef = (new Firebase(firebaseUrl)).child('users');
var mainDeferred = $q.defer();
var notifDeferred = $q.defer();
var userDeferred = $q.defer();
var Notification = function(snapshot){
this.$id = snapshot.key();
this.status = snapshot.val().status;
this.message = null;
this.from = null;
this.to = null;
notifRef.child(this.$id).once('value', function(snap){ //change to on if expect notification to keep updating
var data = snap.val();
this.message = data.message;
this.from = data.from; //user id
this.to = data.to; //user id
notifDeferred.resolve();
});
notifDeferred.promise.then(function(){
this.from = Users.getUser(this.from);
this.to = Users.getUser(this.to);
this.from.$loaded().then(function(){
this.to.$loaded().then(function(){
mainDeferred.resolve();
})
})
})
};
Notification.prototype.ready = function(){
return mainDeferred.promise;
}
return Notification;
})

Related

flutter: Warning database has been locked for 0:00:10.000000. on Android

I stuck at this error for a month I cannot find any solution via google.
I build a chat app which need to work both online and offline, backend is NodeJS
Below code is send any message that still in pending to target devices
async function _worker(socket) {
console.log('worker running...')
await connect.then(async (db) => {
// When user A sent message to user B but user B is offline so server need to sync this message to user B
Message.find({ isDeliver: 0 }).then(async (m) => {
if (m.length > 0) {
for (let i = 0; i < m.length; i++) {
let sender = m[i].sender;
let receiver = m[i].receiver;
let online = await checkUserOnline(socket, receiver);
if (online) {
let to_user_socket_id = getSocketIDfromMapForthisUser(`${receiver}`)
sendToConnectedSocket(socket, to_user_socket_id, "send_message_to_device", m[i]);
}
}
}
});
// When user A sent a message to user B and user B is offline but when user B is online and message delivered to user B and user A is offline server need to sync this message status to update for user A
Message.find({ isDeliver: 1 }).then(async (m) => {
if (m.length > 0) {
for (let i = 0; i < m.length; i++) {
let sender = m[i].sender;
let receiver = m[i].receiver;
let online = await checkUserOnline(socket, sender);
if (online) {
let to_user_socket_id = getSocketIDfromMapForthisUser(`${sender}`)
sendToConnectedSocket(socket, to_user_socket_id, "send_message_deliver", m[i]);
}
}
}
});
// When user A sent a message to user B and user B is offline but when user B is online and read the message and user A is offline server need to sync this message status to update for user A
Message.find({ isRead: 1 }).then(async (m) => {
if (m.length > 0) {
for (let i = 0; i < m.length; i++) {
let sender = m[i].sender;
let receiver = m[i].receiver;
let online = await checkUserOnline(socket, sender);
if (online) {
let to_user_socket_id = getSocketIDfromMapForthisUser(`${sender}`)
sendToConnectedSocket(socket, to_user_socket_id, "send_message_read", m[i]);
}
}
}
});
});
}
and below is a method to handle event from server:
Socket
setOnServerSendDeliver(Function onServerSendDeliver) {
_socket.on('send_message_deliver', (data) {
onServerSendDeliver(data);
});
}
setOnServerSendRead(Function onServerSendRead) {
_socket.on('send_message_read', (data) {
onServerSendRead(data);
});
}
setOnServerSendToDevice(Function onServerSendToDevice) {
_socket.on('send_message_to_device', (data) {
onServerSendToDevice(data);
});
}
Method
onServerSendDeliver(data) async {
MessageModel message = MessageModel.fromJson(jsonDecode(data));
await G.dbService.updateDeliver(message);
G.socketUtils.sendDeliveryDone(message, new UserModel(id: message.sender));
refreshMessage();
}
onServerSendRead(data) async {
MessageModel message = MessageModel.fromJson(jsonDecode(data));
await G.dbService.updateRead(message.chatId);
G.socketUtils.sendReadDone(message, new UserModel(id: message.sender));
refreshMessage();
}
onServerSendToDevice(data) async {
MessageModel message = MessageModel.fromJson(jsonDecode(data));
ChatModel chat = new ChatModel();
chat.id = message.chatId;
chat.fromId = message.sender;
chat.toId = message.receiver;
chat.message = message.content;
await G.dbService.chatOperation(chat);
await G.dbService.insertMessage(message);
await G.dbService.updateDeliver(message);
G.socketUtils.sendDelivery(message, new UserModel(id: message.sender));
refreshMessage();
}
in server I set
setInterval(_worker, 1500, socket);
to load check message and send to end devices
and my database function
Future<String> updateRead(String chatId) async {
Database db = await database;
try {
await db.transaction((txn) async {
return await txn.rawUpdate(
"UPDATE messages SET isRead = 1, isSend = 1, isDeliver = 1 WHERE chatId = ? AND status = 0",
[chatId]);
});
await db.transaction((txn) async {
return await txn.rawUpdate(
"UPDATE chats SET isRead = 1, isSend = 1, isDeliver = 1 WHERE id = ? AND status = 0",
[chatId]);
});
} catch (e) {
print(e.toString());
}
return chatId;
}
Future<String> updateDeliver(MessageModel message) async {
Database db = await database;
String id;
try {
id = message.id;
await db.transaction((txn) async {
return await txn.rawUpdate(
"UPDATE messages SET isDeliver = 1 WHERE id = ? AND status = 0",
[message.id]);
});
await db.transaction((txn) async {
return await txn.rawUpdate(
"UPDATE chats SET isDeliver = 1 WHERE id = ? AND status = 0",
[message.chatId]);
});
} catch (e) {
print(e.toString());
}
return id;
}
iOS is working fine but on android I always got this error and my app stuck:
flutter: Warning database has been locked for 0:00:10.000000. Make sure you always use the transaction object for database operations during a transaction
Any help would be appropriate
Update
I tried to modify the database function to:
Future<String> updateDeliver(MessageModel message) async {
Database db = await database;
String id;
try {
id = message.id;
await db.transaction((txn) async {
var batch = txn.batch();
batch.rawUpdate(
"UPDATE messages SET isDeliver = 1 WHERE id = ? AND status = 0",
[message.id]);
batch.rawUpdate(
"UPDATE chats SET isDeliver = 1 WHERE id = ? AND status = 0",
[message.chatId]);
batch.commit();
});
} catch (e) {
print(e.toString());
}
return id;
}
but I still faced the same issue
Make sure to await any async method in a transaction:
await batch.commit();
You should use pedantic (or the proper lints) to get a warning when you forget such await.
And yes, don't create a transaction for a single operation and try to group actions in transaction (and batches if you can).
You could also turn on logging to make sure you are not doing too many sqlite queries.

Receive push notification only when new mail get in Inbox in Gmail

I am successfully receiving push notifications with service account for a gmail user (impersonate) following official documentation.
But when calling watch I would like to be notified only when new messages arrive, not when any change happens. (For example: message read, message deleted ...)
I try filtering by INBOX and UNREAD, but I get notification even when messages is marked as read.
My code:
this._service.Users.Watch(new WatchRequest
{
TopicName = "projects/my-project-id/topics/gmail",
LabelFilterAction = "include",
//LabelIds = new [] { "UNREAD" }
LabelIds = new[] { "INBOX" }
}, "me").Execute();
Is it possible to receive notification only when new message arrives?
And here is code for subscribing:
var projectId = "my-project-id";
var topicId = "gmail";
var subscriptionId = "unread-gmail";
TopicName topicName = new TopicName(projectId, topicId);
SubscriptionName subscriptionName = new SubscriptionName(projectId, subscriptionId);
PublisherClient publisher = await PublisherClient.CreateAsync(topicName);
SubscriberClient subscriber = await SubscriberClient.CreateAsync(subscriptionName);
await subscriber.StartAsync((msg, cancellationToken) =>
{
Console.WriteLine($"Received message {msg.MessageId} published at {msg.PublishTime.ToDateTime()}");
Console.WriteLine($"Text: '{msg.Data.ToStringUtf8()}'");
return Task.FromResult(SubscriberClient.Reply.Ack);
});

Firestore add social login to database

Anyone have a suggestion on how to make this work. I have a Facebook login, and if there is no data for this user write data.
let users = firebase.usersCollection;
signUserInFacebook () {
const self = this;
var provider = new Firebase.auth.FacebookAuthProvider();
Firebase.auth().signInWithPopup(provider)
.then(function(user) {
usersRef.orderByChild('email').equalTo(user.email).once('value',
function(snapshot){
var exist = (snapshot.val() !== null);
if(!exist){
users.add(user)
}
}
})
},
There are several ways to do this
If you have the user's email (must be unique) as part of their info, you can do this
//check if this is first sign in, if yes save new user to db
userRef.orderByChild('email').equalTo(user.email).once('value', function(snapshot){
var exist = (snapshot.val() !== null);
if(!exist){
//add new user here
}
I usually like to set the uid of a newly created user as their key, so I can use them whenever I need to query for a user instead of using a unique value like email.

I wanted to convert firebase timestamp to UTC timeview in react native

i have been searching for solution for 2 days now
here is my code
saveData = () =>{
let servertime = Firebase.database.ServerValue.TIMESTAMP;
this.itemsRef.push({name:this.state.text,date:servertime});
var s = new Date(parseInt(servertime)).toUTCString();
alert(s);
}
but alert return me with invalid value
i try to put an unix interger directly like this
saveData = () =>{
let servertime = Firebase.database.ServerValue.TIMESTAMP;
this.itemsRef.push({name:this.state.text,date:servertime});
var s = new Date(1509004701954).toUTCString();
alert(s);
}
and it work well
why cant i get the same result?
Firebase.database.ServerValue.TIMESTAMP is a constant that represents the object { ".sv": "timestamp" }. Firebase uses it to automatically put in the current timestamp.
You need to push the data, then grab the snapshot and read the 'date' back:
saveData = () => {
this.itemsRef.push(
{
name:this.state.text,
date:Firebase.database.ServerValue.TIMESTAMP
}).then((snapshot) => {
var s = new Date(parseInt(snapshot.val().date)).toUTCString();
alert(s);
}
}

$firebase with userid reference, init after user login best practice?

Just like firefeed, i'm storing user-meta under /users/userid.
I only need the meta for the currently logged in user, so my thinking is to grab a reference only for the logged in user. So instead of
usersRef = new Firebase(firebase/users/) && users = $firebase(usersRef)
i'm waiting until the login service sets the current user, and then created the reference based on that user's id. This is inside of a service.
var userRef = undefined;
var user = undefined;
var _setCurrentUser = function (passedUser) {
console.log(passedUser);
currentUser = passedUser;
if (!currentUser) {
userRef = new Firebase(FIREBASE_URI + 'users/' + currentUser.id);
user = $firebase(userRef);
}
};
My question is: Is this a good idea? If i don't need a reference to the entire users object, does it make sense performance-wise to specify a specific user. How so/in what way? Is there a better way to wait until we have the current user's id to create the firebase instance for the user?
Ideally, if you don't need all users, you would fetch the specific reference. Something like the following:
var app = angular.module('myApp', ['firebase']);
// a little abstraction to manage establishing a $firebaseSimpleLogin instance
app.factory('loginService', function($firebaseSimpleLogin, Firebase) {
var fb = new Firebase(URL);
var auth = $firebaseSimpleLogin(fb);
return auth;
});
// a little abstraction to reduce the deps involved in creating a $firebase object
app.factory('syncData', function($firebase, Firebase) {
return function(pathToData) {
return $firebase(new Firebase(URL).child(pathToData));
}
});
app.factory('logInAndReturnUser', function(loginService, syncData) {
return function(provider) {
// call the login service
return loginService.$login(provider)
.then(function(user) {
// resolve to a $firebase object for the specific user
return syncData('users/'+user.uid);
});
}
});
Angular-ui's ui-router is ideal for this sort of use case and I highly recommend this approach for dealing with auth. Simply set up a resolve that returns the user:
var app = angular.module('myApp', ['firebase']);
app.factory('loginService', function($firebaseSimpleLogin, Firebase) {
var fb = new Firebase(URL);
var auth = $firebaseSimpleLogin(fb);
return auth;
});
app.configure(function($stateProvider) {
$stateProvider.state('home', {
url: '/',
resolve: {
'user': function(loginService) {
// getCurrentUser returns a promise that resolves to the user object
// or null if not logged in
return loginService.$getCurrentUser();
}
},
controller: 'homeCtrl'
})
});
app.controller('homeCtrl', function(user) {
// assumes we've logged in already, that can be part of router
// processing or we could check for user === null here and send to login page
console.log('user is ' + user.uid);
});

Resources