13

In my app, I simply try to retrieve a reading passage from my Firebase database by adding a ListenerForSingleValueEvent in the following code:

myRef.child("passages").child(passageNum).addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                System.out.println("ON DATA CHANGE");
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {
                System.out.println("DATABASE ERROR");
                FirebaseErrorHandler.handleDatabaseError(databaseError.getCode(), ReadingActivity.this);
            }

        });

It works perfectly fine when there is internet connection. However, when I purposely turn off internet connection, neither onDataChange nor onCancelled are being called. This is very frustrating since two of the error codes in databaseError.getCode() have to do with network connectivity issues.

If I can't get this data due to no internet, I want to at least let the user know that instead of having this listener hanging with the screen constantly loading. Is there a way to solve this? Would I have to just resort to Firebase's REST API? At least with RESTful network requests, they let you know if the connection failed or not.

Rafi
  • 1,902
  • 3
  • 24
  • 46

2 Answers2

10

Firebase separates the flow of data events (such as onDataChange()) from other things that might happen. It will only call onCancelled when there is a server-side reason to do so (currently only when the client doesn't have permission to access the data). There is no reason to cancel a listener, just because there is no network connection.

What you seem to be looking for is a way to detect whether there is a network connection (which is not a Firebase-specific task) or whether the user is connected to the Firebase Database back-end. The latter you can do by attaching a listener to .info/connected, an implicit boolean value that is true when you're connected to the Firebase Database back-end and is false otherwise. See the section in the document on detecting connection state for full details.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • 12
    This is a solution but not the one desired. I can check for internet availability before making any query to firebase database, but that won't be good as I want to keep my app 100% offline. Suppose, when I start the app for the first time without internet connection, the firebase database has no data and gets stuck in the callback. The problem disappears in subsequent runs if the internet connection is provided at least for the first time. How to tackle this scenario? It would have really been better if Firebase provided this info in onCancelled() at least. – Aritra Roy Aug 20 '16 at 17:36
  • @AritraRoy how did you handle this u=issue ? did u find a solution for this ? – Mohamed Hatem Abdu Mar 11 '17 at 14:14
  • 10
    This is really unfortunate that I have to check a flag before I run any query. For a **single value** event listener, firebase should return an error if it fails to get the value. The current behavior just pushes all this complexity up for the app to deal with. – Greg Ennis Mar 31 '17 at 13:04
  • 1
    Unfortunately this also occurs in the situation when there __is__ an internet connection, but database __security rules__ prevent app from accessing. How am I suppose to process this? – Alex Semeniuk Jan 28 '20 at 13:56
2

Hope that my solution can help somebody else (I assume that you already did something else)

Besides to set the keepSynced to true in my database reference:

databaseRef.keepSynced(true)

And add to my Application class:

FirebaseDatabase.getInstance().setPersistenceEnabled(true)

I've added two listeners, one for offline mode and other one for online:

override fun create(data: BaseObject): Observable<String> {
    return Observable.create { observer ->
        val objectId = databaseRef.push().key
        objectId?.let { it ->
            data.id = it

            val valueEvent = object : ValueEventListener {
                override fun onCancelled(e: DatabaseError) {
                    observer.onError(e.toException())
                }

                override fun onDataChange(dataSnapshot: DataSnapshot) {
                    observer.onComplete()
                }
            }
            // This listener will be triggered if we try to push data without an internet connection
            databaseRef.addListenerForSingleValueEvent(valueEvent)

            // The following listeners will be triggered if we try to push data with an internet connection
            databaseRef.child(it)
                    .setValue(data)
                    .addOnCompleteListener {
                        observer.onComplete()
                        // If we are at this point we don't need the previous listener
                        databaseRef.removeEventListener(valueEvent)
                    }
                    .addOnFailureListener { e ->
                        if (!observer.isDisposed) {
                            observer.onError(e)
                        }
                        databaseRef.removeEventListener(valueEvent)
                    }
        } ?: observer.onError(FirebaseApiNotAvailableException("Cannot create new $reference Id"))
    }
}

To be honest, I don't like the idea to attach two listeners very much, if somebody else has a better and elegant solution, please let me know, I have this code temporarily until I find something better.

Filnor
  • 1,290
  • 2
  • 23
  • 28