3

I use Firestore Beta in my app and I'd like to check if there is a working online connection to the Firestore Database. The documentation says that currently there is no direct way to query the connection state, but one can use the Firebase connection state as a workaround. The code snippet for that can also be found in this part of the documentation: Build presence in Cloud Firestore.

    connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected");
    connectionStateListener = connectedRef.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot snapshot) {
            Boolean connected = snapshot.getValue(Boolean.class);

            if (connected == null) {
                changeState(FirebaseConnectionState.error);
                return;
            }

            if (connected) {
                changeState(FirebaseConnectionState.connected);
            } else {
                changeState(FirebaseConnectionState.not_connected);
            }
        }

        @Override
        public void onCancelled(@NonNull DatabaseError error) {
            L.i("FirebaseConnectionStateListener was cancelled");
        }
    });

The ValueEventListener itself is working and I get status updates. But for some reason the status switches from "connected" to "not connected" after 1 minute. Besides this being incorrect, I am not able to get it back to "connected" except restarting the app.

Here's some debug output:

2018-11-15 13:38:20.340: [FirebaseHeartbeatConnector] FirebaseHeartbeatConnector() called with: firebaseApp = [FirebaseApp{name=development, options=FirebaseOptions{applicationId=1:xxxxx:android:xxxxx, apiKey=xxxxx, databaseUrl=null, gcmSenderId=null, storageBucket=xxxx.appspot.com, projectId=xxxxx}}]
2018-11-15 13:38:20.480: [FirebaseHeartbeatConnector] startConnecting() called
2018-11-15 13:38:20.482: [FirebaseHeartbeatConnector] doSendFirestoreHeartbeatRequest() called
2018-11-15 13:38:20.840: [FirebaseHeartbeatConnector] change State called
2018-11-15 13:38:20.840: [FirebaseHeartbeatConnector] State changed initial -> not_connected
2018-11-15 13:38:21.380: [FirebaseHeartbeatConnector] change State called
2018-11-15 13:38:21.381: [FirebaseHeartbeatConnector] State changed not_connected -> connected
2018-11-15 13:39:20.514: [FirebaseHeartbeatConnector] change State called
2018-11-15 13:39:20.515: [FirebaseHeartbeatConnector] State changed connected -> not_connected

Why is that and how can I get a correct status update?

12
  • 1
    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. Read more on the Detecting Connection State documentation. Commented Nov 15, 2018 at 13:52
  • 1
    If you're not using the Firebase Realtime Database for anything else but connection state, you might need to add an empty connection listener. Can you try if adding FirebaseDatabase.getInstance().keepSynced(true); makes a difference? Commented Nov 15, 2018 at 14:01
  • @FrankvanPuffelen good idea, but this throws an DatabaseException: Can't call keepSynced() on .info paths. Commented Nov 15, 2018 at 14:07
  • 2
    @Grimthorr If what I asked fixes the problem, then indeed it needs to be updated in the documentation. Commented Nov 15, 2018 at 14:29
  • 2
    @FrankvanPuffelen Aha, I didn't realise keepSynced(true) created an empty listener, that's a neat little trick and saves having to create one manually. I just tested this and using just FirebaseDatabase.getInstance().getReference().keepSynced(true); does indeed stop it from disconnecting after 60 seconds. Commented Nov 15, 2018 at 14:46

1 Answer 1

2

This might be caused by Firebase closing the connection due to inactivity, which only happens on Android. From the Detecting Connection State documentation:

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.

Because your app isn't making real use of the Realtime Database (you're using Cloud Firestore instead), there won't be any other write operations or other listeners attached, so the connection is closed automatically. You'll likely see something like the below in logcat:

V/FA: Inactivity, disconnecting from the service

As a possible workaround, you could try attaching a listener to any node in the Realtime Database to keep the connection alive (anything will do, even if it doesn't actually exist):

FirebaseDatabase.getInstance().getReference("keepalive").addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot snapshot) {
        // Do nothing - this listener just keeps the connection alive
    }

    @Override
    public void onCancelled(@NonNull DatabaseError error) {
        Log.i("Keepalive listener was cancelled");
    }
});

Alternatively, as suggested by Frank van Puffelen, you can trick Firebase into hanging onto the connection by telling it to keep the entire Realtime Database synced, using keepSynced() on an empty reference:

FirebaseDatabase.getInstance().getReference().keepSynced(true);

You'll need to remember to remove the keepalive listener or undo keepSynced() when you actually want to mark the user's presence as disconnected, though.

Finally, with both of these examples, you still need to attach and make use of the .info/connected connection state listener to handle changes in connection state:

For many presence-related features, it is useful for your app to know when it is online or offline. Firebase Realtime Database provides a special location at /.info/connected which is updated every time the Firebase Realtime Database client's connection state changes. Here is an example:

DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected");
connectedRef.addValueEventListener(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot snapshot) {
    boolean connected = snapshot.getValue(Boolean.class);
    if (connected) {
      System.out.println("connected");
    } else {
      System.out.println("not connected");
    }
  }

  @Override
  public void onCancelled(DatabaseError error) {
    System.err.println("Listener was cancelled");
  }
});
Sign up to request clarification or add additional context in comments.

6 Comments

I tried this, but the valueeventlistener does not receive any callback. I made a gist for further discussion: gist.github.com/fm-eb/d629a7e236a0066624696c9561aaf8ab
That's because nothing exists in your Realtime Database; this listener is just needed to keep the connection alive. You'll still need to use the .info/connected listener in your original question.
The best method is to use FirebaseDatabase.getInstance().getReference().keepSynced(true); which stops the connection from timing out. Again, you'll still need to attach the .info/connected listener to actually monitor the connection status.
hi, i commented on the gist. Event without this change, I still get the 60 seconds timeout.
(1/2) That' really strange. I copy/pasted your code and it worked once. Then I added my firebaseapp-instance as paramter for FirebaseDatabase.getInstance() and it didn't work anymore (1 minute timeout). Then I removed the firebaseapp-instance and it also didn't work anymore... Then I completely uninstalled the app and went back to your code again => does not work anymore....
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.