2

I would like to force Firebase to give me cached data and not go get the latest from the server. Is this possible?

The idea was to enable persistence, call keepSynced(true) on a particular location, add a listener to be notified when the data is available, then call keepSynced(false) to ensure that future listeners get the cached data.

However, in a test app this doesn't seem to work. Calling keepSynced(false) doesn't prevent a listener from getting the latest from the server. (Code below)

What is the correct way to work with a temporarily-unchanging set of Firebase data?

public class MainActivity extends AppCompatActivity {
  private static String TAG = "tag";
  Button keepSyncedTrue;
  Button keepSyncedFalse;
  Button getData;
  TextView data;
  private DatabaseReference myRef;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    FirebaseDatabase.getInstance().setPersistenceEnabled(true);
    myRef = getDatabaseReference();
    setContentView(R.layout.activity_main);
    keepSyncedTrue = (Button)findViewById(R.id.keepSyncTrue);
    keepSyncedFalse = (Button)findViewById(R.id.keepSyncFalse);
    getData = (Button)findViewById(R.id.getData);
    data = (TextView)findViewById(R.id.data);

    keepSyncedTrue.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View view) {
        myRef.keepSynced(true);
      }
    });

    keepSyncedFalse.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View view) {
        myRef.keepSynced(false);
      }
    });

    getData.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View view) {
        // Read from the database
        myRef.addListenerForSingleValueEvent(new ValueEventListener() {
          @Override
          public void onDataChange(DataSnapshot dataSnapshot) {
            // This method is called once with the initial value and again
            // whenever data at this location is updated.
            String value = dataSnapshot.getValue(String.class);
            Log.d(TAG, "Value is: " + value);
            data.setText(value);
          }

          @Override
          public void onCancelled(DatabaseError error) {
            // Failed to read value
            Log.w(TAG, "Failed to read value.", error.toException());
          }
        });
      }
    });

  }

  private DatabaseReference getDatabaseReference() {
    return FirebaseDatabase.getInstance().getReference("message");
  }
}

and

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:id="@+id/activity_main"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:orientation="vertical"
    android:paddingTop="@dimen/activity_vertical_margin">


    <Button
        android:id="@+id/keepSyncTrue"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="keepSynced(true)" />

    <Button
        android:id="@+id/keepSyncFalse"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="keepSynced(false)" />

    <Button
        android:id="@+id/getData"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="get data" />

    <TextView
        android:id="@+id/data"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>
Michiyo
  • 1,161
  • 1
  • 14
  • 33
  • Why do you want to specifically get the cached data? Firebase will sync everything for you, based on the availability of internet.. – Basanth Feb 27 '17 at 07:40
  • 2
    Note that Firebase Realtime Database doesn't really want you to think about whether or not data is cached or not. It wants you to assume that it's doing the best it can to keep data synchronized, if you're actively listening to it. The fact that it could be cached (but not necessarily) is an implementation detail hidden from the APIs. So, any solution you try to come up with will be "hacky" at best. – Doug Stevenson Feb 27 '17 at 18:01
  • @Basanth I'm using firebase to populate the content/structure of the app. I'm trying to avoid the case where the user is actively using the app when a firebase update occurs, and the section the user is looking at suddenly no longer exists. – Michiyo Feb 27 '17 at 18:46
  • @DougStevenson That makes sense, but I hoped there might be a way. – Michiyo Feb 27 '17 at 19:40
  • I think the only 'non-hacky' solution would be to change the way you are getting the Content/structure of the app. We can suggest a few solutions if only you can share the structure and how you intend to use it. – Basanth Feb 28 '17 at 06:02
  • Did you find any solution for this ? @Michiyo . Like as we know firebase store data locally in sqllite so in some cases instead of calling server we can fetch the data locally. – Abubakker Moallim Feb 10 '18 at 10:48

2 Answers2

8

It depends by your final goal.

If you want to avoid any kind of synchronization you can use

DatabaseReference.goOffine();

As you can see in the documentation:

Manually disconnect the Firebase Database client from the server and disable automatic reconnection.

Note: Invoking this method will impact all Firebase Database connections.

Otherwise:

FirebaseDatabase.goOffline();

As described in doc:

Shuts down our connection to the Firebase Database backend until goOnline() is called

Morover if you want to keep alive your synchronization and work with local cached data you can use:

FirebaseDatabase.getInstance().setPersistenceEnabled(true);

As reported in doc:

By enabling persistence, any data that the Firebase Realtime Database client would sync while online persists to disk and is available offline, even when the user or operating system restarts the app. This means your app works as it would online by using the local data stored in the cache. Listener callbacks will continue to fire for local updates.

In this case you app work with cached data but the client continues to sync with server data.
More info here.

Also in this case to keep specific locations in sync use:

yourRef.keepSynced(true);
Community
  • 1
  • 1
Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
  • 2
    Going offline would be perfect if it could be applied to particular path; not solely the entire database. – Michiyo Feb 27 '17 at 19:19
  • I enabled persistence (will update post), but unfortunately, I find that while the first result comes from the cache, results for a subsequent request is the latest from firebase. I think it's explained [here](http://stackoverflow.com/a/34487195/4455466) where Frank says "The Firebase client will (like in the previous situation) immediately invoke onDataChange() for the value from the local disk cache...Do note that updated data still will be requested and returned on subsequent requests." Is there some other step to making sure it always returns data from cache? – Michiyo Feb 27 '17 at 19:20
  • 1
    but, Docs also says that "On Android, Firebase automatically manages connection state to reduce bandwidth and battery usage. When a client has no active listeners, no pending write or onDisconnect operations, and is not explicitly disconnected by the goOffline method, Firebase closes the connection after 60 seconds of inactivity." at https://firebase.google.com/docs/database/android/offline-capabilities#section-connection-state – mehmet Feb 08 '18 at 09:03
0

You can use Firebase Offline Capabilities by enabling Disk Persistence in your Application. This is how it's done:

public class MyApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        if (!FirebaseApp.getApps(this).isEmpty()) {
            FirebaseDatabase.getInstance().setPersistenceEnabled(true);
        }
    }
}

After you enable disk persistence, Firebase listener will look up to cache data first to be given in DataSnapshot. So, to prevent it fetching data from server, you should use Firebase addListenerForSingleValueEvent everytime you want to read data, because it will only read once and remove itself after that, which means it will read from cache data.

I think you don't need to use keepSynced(boolean value) for this case.

Janice Kartika
  • 502
  • 1
  • 3
  • 14
  • Thank you for the suggestion. Unfortunately, I find that while the first result comes from the cache, results for a subsequent request is the latest from firebase. I think it's explained [here](http://stackoverflow.com/a/34487195/4455466) where Frank says "The Firebase client will (like in the previous situation) immediately invoke onDataChange() for the value from the local disk cache. It will not invoke the onDataChange() any more times, even if the value on the server turns out to be different. Do note that updated data still will be requested and returned on subsequent requests." – Michiyo Feb 27 '17 at 19:16
  • If you really want to make sure it comes from cache data, then you might have to give some effort to save the data yourself. You can use `gson` if you want, by saving the json string from Firebase in `SharedPreferences`, then retrieve it by converting to Java objects using `gson`. Sorry, I can't find a way to do that using just Firebase API. – Janice Kartika Feb 28 '17 at 04:15
  • That makes sense. Thank you. – Michiyo Feb 28 '17 at 15:24