0

If I try to send data to FirebaseDatabase in the absence of an Internet connection, then, as expected, nothing happens. If you turn on the Internet, then this data is added themselves, and even if I restarted the application. And they are added to the very end of the database. Even if you add data after this, the offline post will still be the last.

I think to solve the problem by checking the Internet before sending. Maybe there are any other solution methods?

I send data in a simple way:

    final String phone_val = etPhone.getText().toString().trim();
    final String comment_val = etComment.getText().toString().trim();

    DatabaseReference newTrip = mDatabase.push();

    newTrip.child("phone").setValue(phone_val);
    newTrip.child("comment").setValue(comment_val);
    newTrip.child("type").setValue(1);

    startActivity(new Intent(AddDriverActivity.this, TripActivity.class));
    finishAffinity();
Hemant N. Karmur
  • 840
  • 1
  • 7
  • 21
  • Probably this can help you: [Android - Firebase Offline Best Practices](https://stackoverflow.com/questions/37507425/android-firebase-offline-best-practices) – Md. Asaduzzaman Jan 19 '20 at 10:27
  • When you call `push()` the Firebase SDK determines a key based on the local time of the device, and the last known time difference between the device and the Firebase servers. This means that in most cases, the key of the new node should be pretty close to the moment you called `push()` even when offline and relative to other clients. If this isn't the case, the most common problem is that the client's timestamp is unreliable, which isn't something Firebase can correct for without an internet connection. – Frank van Puffelen Jan 19 '20 at 16:02

1 Answers1

1

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.

samthecodingman
  • 23,122
  • 4
  • 30
  • 54