How to update the UI on firebase realtime database "push" in offline mode - firebase

I'm using react-native-firebase in my app. The problem i'm facing is how to handle the UI updates when user tries to push data when offline.
If the user is online we can use the on() method to get realtime updates but what to do when they are offline. We know that the pushed data is stored in the cache and pushed when user is online again. Can this cached data be used to do what i aim at achieving?
Here's the code i used to receive realtime updates:
var ref333 = firebase.database().ref(`/user-posts/${uid}/`)
ref333.on('value',function (snap) {
var s = snap.val();
console.log("NEW POSTS "+JSON.stringify(s))
})
The code i use to push the data.
var postData = {
uid: uid,
body: 'body',
title: 'title',
starCount: 0
};
// Get a key for a new Post.
var newPostKey = firebase.database().ref().child('posts').push().key;
var ref222 = firebase.database().ref(`/posts/${newPostKey}`)
var ref333 = firebase.database().ref(`/user-posts/${uid}/${newPostKey}`)
ref222.push(postData, function (onComplete) {
console.log("COMPLETED")
ref333.push(postData,function (onComplete) {
console.log("NEXT COMPLETED")
}, function (error) {
console.log("ERROR IN ",error)
})
}, function (error) {
console.log("error == "+error)
})

The .on snspashot listener should be triggered even if in offline mode.
According to the docs:
https://firebase.google.com/docs/database/web/read-and-write
You can use the value event to read a static snapshot of the contents
at a given path, as they existed at the time of the event. This method
is triggered once when the listener is attached and again every time
the data, including children, changes.
This should work in offline mode as well. If you are not receiving updates - something else is wrong.

This problem was solved by adding this lines of code to your native code:
https://rnfirebase.io/docs/v5.x.x/core/default-app#Enable-Database-Persistence

Related

Firebase Functions failing Run the latest Code

I have a firebase function that runs based on a trigger to create a chat room. The function was running for a long time correctly until I updated it to add a new field on the document it is creating. That is archived: false.
After adding the field on the function, it sometimes adds the field when it runs but at times fails to add the field. So I think that firebase at times runs the updated code and sometimes runs the only code when the function is triggered because all other fields are created apart from the archived: false which is created and sometimes not created.
So I fail to understand why this happens yet in the code, the field archived is not dependent on any other variable.
Below is the function.
// firestore trigger to crete a chat room
exports.createChatRooms = functions.firestore.document("/jobs/{id}").onUpdate((change, context) => {
const job = change.after.data();
if (job.status === "Accepted") {
const roomId = context.params.id;
const room = admin.firestore().collection("rooms").doc(roomId);
return room.set({
name: job.title,
jobId: roomId,
createdAt: new Date().getTime(),
agent: job.agent,
archived: false,
user: job.user,
members: [job.user.id, job.agent.id],
});
} else {
return null;
}
});
Kindly help me understand why this is happening.

Decrease response time in Firebase Vue app when liking a post

I have an app with different 'procedures' (think posts or pages), which one can like. Currently the process works: Tap like => run method "likeProcedure" => run dispatch action "likeProcedure" => update UI. It usually happens almost immediately, but sometimes there's a lag that gives this a "non-native" feel. Is there some sort of way that I could return feedback immediately, while stile holding single origin of truth on the firebase database?
Thank you!
Page Code:
<v-icon
v-if="!userProfile.likedProcedures || !userProfile.likedProcedures[procedure.id]"
color="grey lighten-1"
#click="likeProcedure({ id: procedure.id })"
>
mdi-star-outline
</v-icon>
and
computed: {
...mapState(["userProfile"]),
procedures() {
return this.$store.getters.getFilteredProcedures();
},
},
Vuex code:
async likeProcedure({ dispatch }, postId) {
const userId = fb.auth.currentUser.uid;
// update user object
await fb.usersCollection.doc(userId).update({
[`likedProcedures.${postId.id}`]: true,
});
dispatch("fetchUserProfile", { uid: userId });
},
Side note: I'm trying to remove the dispatch("fetchUserProfile") command, but this doesn't work, because then I'm calling dispatch without using it. And I cannot remove dispatch because then the object calling it is empty. And I cannot remove the object, because then the argument ('postId') isn't working. So if anyone knows how to deal with that, that would be extremely helpful.
Thank you :)
So this is the best solution I've come up yet. It kind of destroys the idea of a single source of truth, but at least it provides an immediate UI update:
async likeProcedure({ dispatch, state }, postId) {
console.log("likeProcedure");
const userId = fb.auth.currentUser.uid;
// line below provides immediate update to state and hence to the UI
state.userProfile.likedProcedures[postId.id] = true;
// line below updates Firebase database
await fb.usersCollection.doc(userId).update({
[`likedProcedures.${postId.id}`]: state.userProfile.likedProcedures[
postId.id
],
});
// line below then fetches the updated profile from Firebase and updates
// the profile in state. Kind of useless, but ensures that client and
// Firebase are in-sync
dispatch("fetchUserProfile", { uid: userId });
},
async fetchUserProfile({ commit }, user) {
// fetch user profile
const userProfile = await fb.usersCollection.doc(user.uid).get();
// set user profile in state
commit("setUserProfile", userProfile.data());
// change route to dashboard
if (router.currentRoute.path === "/login") {
router.push("/");
}
},

How do I make a firestore query to be used with both get() and valueChanges()?

I am using Angular 8 and have a form where a user can choose what he wants to query the database for and then click either of two buttons - one to view data in realtime on the website, and the other to download the data.
I thought I could make use of one function to make a query and then call different functions depending on what button the user clicked, using get() for the download and valueChanges() for the realtime data view. But when I try this, I get the following errors in the browser console. (This is with query as type any - if I specify the type as AngularFirestoreCollection I get errors regarding my type for the get() part in VSCode)
ERROR Error: "Uncaught (in promise): TypeError: this.query.get is not
a function
I can add that I previously had two completely separate (working) functions for downloading and viewing in realtime. And for downloading I used the below query. I gather this is actually a Firestore Query, whereas the "query" I'm trying to use in my updated code is an AngularFirestoreCollection. But is there a way I can make some kind of Query/Collection that will work for both get() and valueChanges()?
Old (working) query:
var query = this.afs.collection(collection).ref.where('module', 'in', array_part);
Trying a common function makeQuery():
onSubmit(value, buttonType): void {
if (buttonType=='realtime') {
this.getRealTimeData(value);
}
if (buttonType=='download') {
this.downloadCsv(value);
}
}
async downloadCsv(value) {
this.query = this.makeQuery(value);
this.dataForDownload = await this.getDataForDownload();
this.dataForDownload = JSON.stringify(this.dataForDownload['data']);
console.log('Data: ', this.dataForDownload);
var date = new Date();
var date_str = this.datePipe.transform(date, 'yyyy-MM-ddTHH-mm');
this.makeFileService.downloadFile(this.dataForDownload, 'OPdata-' + date_str);
}
getDataForDownload() {
return this.query.get()
.then(function (querySnapshot) {
var jsonStr = '{"data":[]}';
var dataObj = JSON.parse(jsonStr); //making object we can push to
querySnapshot.forEach(function (doc) {
JSON.stringify(doc.data()), ', id: ', doc.id);
dataObj['data'].push(doc.data());
});
return dataObj;
})
.catch(function (error) {
console.log("Error getting documents: ", error);
});
}
async getRealTimeData(value) {
this.query = await this.makeQuery(value);
this.data = this.query.valueChanges();
}
async makeQuery(value) {
var collection: string;
return this.query = this.afs.collection<DataItem>('CollectionName', ref => ref.where('datetime', '>=', '2020-01-15T09:51:00.000Z').orderBy('datetime', 'desc').limit(100));
}
The valueChanges() is a method used in angularfire to retrieve data from firestore, while the get() method is used to retrieve from firestore but using the vanilla javascript.
Mixing both methods will return an error as you have seen in your code. Therefore, since angularfire was created above the javascript firebase code, then you should be able to use valueChanges() to view data in realtime on the website, and to download the data.

Flutter : how to get newly added record from firebase real time database?

I'm using below code to retrieve all the data from my firebase chat room
void readDataFromFireBase() {
var databaseReference =
FirebaseDatabase.instance.reference().child("messages");
databaseReference
.child(chatroom.chatId)
.once()
.then((DataSnapshot snapshot) {
Map<dynamic, dynamic> values = snapshot.value;
values.forEach((key, value) {
setState(() {
chatMessageList.add(ChatMessage(
value["message"], value["fromUser"], value["timestamp"]));
chatMessageList.sort((a, b) {
var formatter = new DateFormat('MM/dd/yyyy, kk:mm');
var dateOne = formatter.parse(a.timestamp);
var selectedDate = formatter.parse(b.timestamp);
return dateOne.compareTo(selectedDate);
});
});
});
}
now how can i get notify my chat room when the new message has arrived
currently i'm using below code to listen child added event
listenDataFromFireBase() {
var db = FirebaseDatabase.instance.reference().child("messages");
db.child(chatroom.chatId).onChildAdded.listen((data) {
print("GET_NEW_DATA");
print(data.snapshot.value["message"] ?? '');
print(data.snapshot.value["fromUser"] ?? false);
print(data.snapshot.value["timestamp"] ?? '');
});
}
but there is one issue i'm facing this listenDataFromFireBase() load all the the data from particular room
My requirement is whenever new message added in chatroom i want to animate my message layout
How can i get notify my screen whenever new message will add in my chat room.
If need more information please do let me know. Thanks in advance. Your efforts will be appreciated.
As you've discovered onChildAdded fires immediately for any existing data, and after that also when any data is added. If you want to distinguish between these two cases, you'll need to combine an onChild listener and an onValue listener.
In the onValue listener, all you do is flag whether that event has happened:
databaseReference.onValue.listen((event) {
setState(() {
isInitialDataLoaded = true
});
});
Now do all your data processing in the onChildAdded listener, getting the message from the snapshot and adding it to the list. Then use the isInitialDataLoaded to detect whether this is initial data, or an update:
var db = FirebaseDatabase.instance.reference().child("messages");
db.child(chatroom.chatId).onChildAdded.listen((data) {
// TODO: get message from snapshot and add to list
if (isInitialDataLoaded) {
// TODO: alert the view about the new data
}
});
So you'll have two listeners on the same node. The Firebase SDK actually detects this situation and only reads the data from the server once, so there is no extra data being transferred in this case.
You can use onValue:
/// Fires when the data at this location is updated. `previousChildKey` is null.
Stream<Event> get onValue => _observe(_EventType.value);
But if you use onValue or onChildAdded, it will retrieve all the data under this chatroom.chatId, then when you data is added the onValue event will be fired again and will give you the new data.

How to emit data only to one client in Meteor streams

I am building a realtime game with Meteor streams. I need to update only one client - send a room ID from server. Users are not logged in so Meteor.userId() is null and therefore I can't use this: http://arunoda.github.io/meteor-streams/communication-patterns.html#streaming_private_page
There is only one URL (homepage) where all things happen. So I don't use any URL parameters for room. Everything is on the server.
I have tried to use Meteor.uuid() instead of Meteor.userId() but uuid is changed after each emit (which is strange).
In socket.io I would do this:
//clients is an array of connected socket ids
var clientIndex = clients.indexOf(socket.id);
io.sockets.socket(clients[clientIndex]).emit('message', 'hi client');
Is there any way to do this in Meteor streams or Meteor itself?
Well, this can be easily done if you decided to use database, but I guess it is not the best option if you have a large number of clients.
So another way to achieve this - without database - is to make a good use of the Meteor's publish/subscribe mechanism. Basically the way it could work is the following:
1. client asks server for a communication token (use Meteor.methods)
2. client subscribes to some (abstract) data set using that token
3. server publishes the required data based on the received token
So you will need to define a method - say getToken - on the server that generates tokens for new users (since you don't want to use accounts). This could be something more or less like this:
var clients = {}
Meteor.methods({
getToken: function () {
var token;
do {
token = Random.id();
} while (clients[token]);
clients[token] = {
dependency: new Deps.Dependency(),
messages: [],
};
return token;
},
});
A new client will need to ask for token and subscribe to the data stream:
Meteor.startup(function () {
Meteor.call('getToken', function (error, myToken) {
// possibly use local storage to save the token for further use
if (!error) {
Meteor.subscribe('messages', myToken);
}
});
});
On the server you will need to define a custom publish method:
Meteor.publish('messages', function (token) {
var self = this;
if (!clients[token]) {
throw new Meteor.Error(403, 'Access deniend.');
}
send(token, 'hello my new client');
var handle = Deps.autorun(function () {
clients[token].dependency.depend();
while (clients[token].messages.length) {
self.added('messages', Random.id(), {
message: clients[token].messages.shift()
});
}
});
self.ready();
self.onStop(function () {
handle.stop();
});
});
and the send function could defined as follows:
var send = function (token, message) {
if (clients[token]) {
clients[token].messages.push(message);
clients[token].dependency.changed();
}
}
That's a method I would use. Please check if it works for you.
I think using Meteor.onConnection() like a login would enable you to do what you want pretty easily in a publish function.
Something like this:
Messages = new Meteor.Collection( 'messages' );
if ( Meteor.isServer ){
var Connections = new Meteor.Collection( 'connections' );
Meteor.onConnection( function( connection ){
var connectionMongoId = Connections.insert( connection );
//example Message
Message.insert( {connectionId: connection.id, msg: "Welcome"});
//remove users when they disconnect
connection.onClose = function(){
Connections.remove( connectionMongoId );
};
});
Meteor.publish( 'messages', function(){
var self = this;
var connectionId = self.connection.id;
return Messages.find( {connectionId: connectionId});
});
}
if ( Meteor.isClient ){
Meteor.subscribe('messages');
Template.myTemplate.messages = function(){
//show all user messages in template
return Messages.find();
};
}
I have used database backed collections here since they are the default but the database is not necessary. Making Messages a collection makes the reactive publishing easy whenever a new message is inserted.
One way that this is different from streams is that all the messages sent to all clients will end up being kept in server memory as it tries to keeps track of all data sent. If that is really undesirable then you could use a Meteor.method so send data instead and just use publish to notify a user a new message is available so call the method and get it.
Anyway this is how I would start.

Resources