Firstly, if you haven't already, read through the offline capabilities documentation so you have a general grasp of how Firebase behaves while offline.
Next, we'll clean up your write operations so that they are a single atomic operation rather than a few separate write operations.
HashMap<String, Object> tripData = new HashMap<>();
tripData.put("phone", phone_val);
tripData.put("comment", comment_val);
tripData.put("type", 1);
DatabaseReference newTrip = mDatabase.push();
newTrip.setValue(tripData);
As stated in the offline capabilities documentation, you can check whether your app is offline by checking the special database location /.info/connected
which returns the current connection state. This value will be either true
or false
.
While you could check this value before posting your trips, the connection state may change while you are sending the data.
Even if you add data after this, the offline post will still be the last.
This is the trickier part to manage. I think the easiest way to deal with this is to have a "staging" section of your database and then move data as it is created at this location to the main storage location using a Cloud Function for Firebase.
Client Side
Let's say you are storing these trips in /trips/someTripId
.
private DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();
private DatabaseReference mAllTripsRef = mDatabase.child('trips');
To add a new trip, you would use:
HashMap<String, Object> tripData = new HashMap<>();
tripData.put("phone", phone_val);
tripData.put("comment", comment_val);
tripData.put("type", 1);
DatabaseReference mNewTripRef = mAllTripsRef.push();
mNewTripRef.setValue(tripData);
Because references created by push()
are based on the estimated time of the Firebase servers, they will be ordered by when they were created rather than when they are received by the Firebase servers. But if you wanted to preserve that offline trips are always last, instead of writing new trips to /trips
, you would instead write to /trips-staging
.
private DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();
// trips that have been posted to "online" database
private DatabaseReference mAllTripsRef = mDatabase.child('trips');
// trips yet to be posted online
private DatabaseReference mStagingTripsRef = mDatabase.child('trips-staging');
New data would be added using:
HashMap<String, Object> tripData = new HashMap<>();
tripData.put("phone", phone_val);
tripData.put("comment", comment_val);
tripData.put("type", 1);
DatabaseReference mNewTripRef = stagingTripsRef.push();
mNewTripRef.setValue(tripData);
Now that we have the reference to the trip waiting to be posted, mNewTripRef
, we can add a listener to it to see when it has been posted.
In the cloud side below, we are going to make it so that if there is data at /trips-staging/someTripId
and it is just a string, then the trip has been received and posted by the server to the location /trips/<string-value>
.
ValueEventListener stagingTripListener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// Get trip data
Object tripData = dataSnapshot.getValue();
if (tripData == null) {
// Data has been deleted!
// Disconnect this listener
mNewTripRef.removeEventListener(this);
// TODO: What now?
} else if (tripData instanceof String) {
// Data has been moved!
DatabaseReference postedTripRef = mAllTripsRef.child((String) tripData);
// Disconnect this listener
mNewTripRef.removeEventListener(this);
Log.i(TAG, "stagingTripListener:onDataChange", "New trip has been successfully posted as trip '" + mNewTripRef.getKey() + "'");
// TODO: do something with postedTripRef
} else {
// ignore - the trip hasn't been moved yet, continue waiting
// tripData is a Map<string, Object> with our original data
}
}
@Override
public void onCancelled(DatabaseError databaseError) {
// Getting this trip failed, log a message
Log.w(TAG, "stagingTripListener:onCancelled", databaseError.toException());
}
};
mNewTripRef.addValueEventListener(stagingTripListener);
Cloud Side
Now, we need to move these new trips over to /trips
once they are received on the server. For this we can use a Cloud Function for Firebase that listens to Realtime Database events. For our use case, we want to exclusively listen to data creation events that happen on our /trips-staging
location.
When the Cloud Function is triggered, it should take the data at /trips-staging/someId
and move it to /trips/someNewId
. It is probably also a good idea to store where we moved the data to at the old location if it is ever needed but also so we can tell when the trip has been received by the server.
After following the Getting Started documentation up to Step 4, you can use the following code as your index.js
or index.ts
file and then deploy it using firebase deploy --only functions
.
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp(); // use defaults
export const moveTripsFromStaging = functions.database.ref('/trips-staging/{stagingTripId}')
.onCreate((snapshot, context) => {
const stagingTripId = snapshot.key;
const tripData = snapshot.val();
// get reference to the root of the database
let dbRootRef = admin.database().ref();
let allTripsRef = dbRootRef.child('trips');
let newTripRef = allTripsRef.push();
return dbRootRef.update({
// move data to it's new home
['trips/' + newTripRef.key]: tripData,
// save where we moved data to as a simple string containing the new key in /trips
['trips-staging/' + stagingTripId]: newTripRef.key // or set to null to delete it
});
})
Once deployed, you should see new trips that are uploaded to /trips-staging
be received by the server and then moved across to /trips
in the order that server receives them.