Android Firebase - "onDataChange" And "onCancelled" Not Being Called With No Internet Connection - android

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.

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.

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.

Related

Firebase onDataChange returns null value after internet reconnection

I have this code to read data and write the value into an object
if(SpecialFunction.isNetworkAvailable()) {
databaseContent.loadAccountFromDatabase(account -> {
this.account = account;
binding.textView3.setText(account.toString());
});
} else {
startActivity(new Intent(this, InternetTroubleActivity.class));
}
Where databaseContent is a class which contains Firebase logic which I need.
In loadAccountFromDatabase I have next code which works with one problem.
public void loadAccountFromDatabase(FirebaseCallbackAccount accountFirebaseCallback) {
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
if (snapshot.exists()) {
account = snapshot.getValue(Account.class);
} else {
account = new Account();
setDefaultAccount();
}
accountFirebaseCallback.onCallback(account);
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
Log.e("loadAccountFromDatabase", "Error: " + error.toString());
}
};
database.addValueEventListener(valueEventListener);
}
When I try to read data after internet reconnection or when I turn on internet after onStart() and run loadAccountFromDatabase I get null value of my snapshot.
I have a method (isNetworkAvailable()) which works well and when I don't have internet connection it returns false.
The next part of code doesn't work properly. The snapshot.exists returns true and snapshot.getValue returns null although the value is other.
if (snapshot.exists()) {
account = snapshot.getValue(Account.class);
}
If I run an application with internet connection everything works well while I don't turn off internet and try to read again.
I also tried to use database.get().addOnCompleteListener(onCompleteListener)... but I got the same result. Everything works well while I don't try to read data after internet reconnection.
database.keepSynced(true) also doesn't help.
UPDATE##
Database init:
private final String USER_KEY = "Account", PURCHASES = "purchases";
public void init() {
mAuth = FirebaseAuth.getInstance();
firebaseDatabase = FirebaseDatabase.getInstance();
database = firebaseDatabase.getReference(String.format("%s/%s", USER_KEY, mAuth.getUid()));
}
Structure:
console
UPDATE 2##
To help you understand the problem I add screenshot of log. GREEN box: I run my app with internet connection. The data was read and wrote correct. RED box: I closed my app, destroyed activity, turned off internet. Then I run my app, but method isNetworkAvailable didn't let me run MainActivity. Then I turned on internet and clicked on button. The data was being tried to read but I got object with null variables althrough they are not null. If you think that something is wrong with isNetworkAvailable I tell you that this problem was before I have added it. I was hoping that it would help me to read correct but it didn't help.
logcat
UPDATE 3##
JSON:
{
"Account" : {
"wBOZsnGGywYIpap3cLZodPOWcpt2" : {
"budget" : 100,
"budgetLastMonth" : 0,
"budgetLeft" : 100,
"currencyType" : "USD",
"email" : "bibishka117#gmail.com",
"id" : "wBOZsnGGywYIpap3cLZodPOWcpt2",
"personName" : "новый пользователь"
}
}
}
I found a problem. 4 hours of reading my code and stackoverflow. I don't know how, but in onPause () I had code that writes null object to a database.
So when I tested and turned on / off the Internet, I run it. So I wrote a null object that was not instantly updated on the console, and when I turned on the Internet, the object was updated to zero in the database and then read in my app. :]

how to know if a valid FirebaseAuth is going to expire soon

I've a weird behavior in my app related to the firebase database.
I got some unexpected access denied when trying to perform some stuff at database... That could be bug in my code but is a pretty simple code.
I want to ilustrate the sitation with a real scenario, please read the requirements
1- i've only 1 app accessing the database (android)
2- i've only 1 method in the whole app trying to access the specific node which is causing deny of access
3- in my firebase rules the only rule applied to this node is:
".read":"auth != null",
".write":"auth != null"
4- i DO explicity check FirebaseAuth.getInstance().getCurrentUser() != null right before calling the method
this is a pseudo snippet of how my code is (i wont pust the real code simple because is too long)
public void onResume() {
if(FirebaseAuth.getInstance().getCurrentUser() == null)
doLogin();
else
checkFirebaseStuff();
}
As google doesn't provide any info about why the access was rejected the only possible explanation i found is:
the user was authenticated but long time before, so when it checked on if was still valid, but short time later when the function really ran its token (or some other firebase auth check) was no longer valid, so it caused the access denied
this error doesn't happen a lot, i have 5k daily users and it happens around 20 or 50 times a day, but still shouldn't happen even once
does it make sense? can anyone help me with any aditional info?
Hmm, I think you can fix this by checking if the user is disconnected from the firebase database, but I'm not really sure if that will affect the Auth too, you can give it a try
DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected");
connectedRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
boolean connected = snapshot.getValue(Boolean.class);
if (connected) {
Log.d(TAG, "connected");
//Here you can update your mAuth state
} else {
Log.d(TAG, "not connected");
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
Log.w(TAG, "Listener was cancelled");
}
});

Firebase Listener slow after network switch

I have an Android application that uses Firebase. Database listeners onDataChange methods are called quickly at first.
I leave the app, switch the network connection (wifi -> data or data -> wifi). I make sure there is still internet access, and return to the app, listeners now take a long time to respond (more than a minute) and as a result my app hangs.
reference.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// Takes forever
}
#Override
public void onCancelled(DatabaseError databaseError) {
// Takes forever
}
});
Any idea what could be going on here?
EDIT TO CLARIFY: I switched networks, verified internet connectivity and then returned to the app and executed the code above. It's like Firebase was somehow disconnected during the switch.

Firebase database OnDataChanged is not called in Android app

I realize similar questions have been asked here, but previous solutions do not seem to apply. I'm developing an android app using Firebase auth, and I'm trying to keep track of the user handles that are registered by means of a Firebase database. This is very similar to Tanishq Sharma's answer to firebase duplicate username and custom sign-in.
My problem is that the onDataChange method of the ValueEventListener is never called. As suggested by Frank van Puffelen in Firebase with java (non-android) retrive information, I tried to add some wait time, but this did not solve the problem. After the wait time has passed, the onCancelled method is always called.
The code below shows the function that should return true if a user handle is listed in the Firebase database at node "takenHandles". I know that the connection to the database is working, because I'm able to add a new handle to the database by another part of my code without any issues. Any suggestions on what could be causing this problem?
EDIT It seems I don't have permission to comment on my own question. Thanks Frank, for your quick reply! That indeed explains why writing to the database works, because I do that just after a new user has been authenticated. I should probably consider changing the database rules.
public boolean handleTaken(final String mHandle) {
final boolean[] isTaken = new boolean[]{true};
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference takenHandles = database.getReference("takenHandles");
takenHandles.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Log.d(TAG, "ValueEventListener: onDataChange");
isTaken[0] = dataSnapshot.hasChild(mHandle);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.d(TAG, "ValueEventListener: onCancelled");
Toast.makeText(MyAccountActivity.this, "Connection error, please try again", Toast.LENGTH_SHORT).show();
}
});
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return isTaken[0];
}

Firebase Remote Config: Can't read any values, but fetch is successful

I'm trying to have a remote config parameter using the new Remote Config feature of Firebase, and I'm having an issue.
Here's my Remote Config console:
I'm doing a fetch and update in my Application's onCreate():
final FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.getInstance();
remoteConfig.fetch().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
remoteConfig.activateFetched();
}
}
});
And here's how I'm reading it:
FirebaseRemoteConfig remoteConfig = FirebaseRemoteConfig.getInstance();
String value = remoteConfig.getString("active_subscriptions");
Value is returning null.
If I call remoteConfig.getInfo().getLastFetchStatus(), it returns LAST_FETCH_STATUS_SUCCESS, so it seems the fetch is going through successfully.
Any idea why my value is blank?
Workaround found! See below
I'm running into the "silent completion" thing - I call "fetch" but onComplete, onSuccess, or onFailure listeners never fire. I tried moving it to an activity onCreate, and still nothing happened, and therefore, the config items never get loaded from the server. I've got Developer Mode enabled, and am calling fetch with a cache value of 0.
I was able to (once) put a breakpoint on the line "public void onComplete(#NonNull Task task) {", which got hit, and then I was able to step through and the onComplete fired. I was then unable to reproduce this same result any other way, including doing the same thing (I think) a second time.
Seems like a timing or concurrency issue, but that makes little sense, given this is an asynchronous call.
Workaround
If you fetch from Activity#onResume (or, I presume, Activity#onStart), it works perfectly. Calling fetch from Activity#onCreate or Application#onCreate results in a call that seemingly never gets handled, and in fact, performance of the app degrades noticeably after the fetch begins, so I think there's a looper running or something.*
Workaround #2
If you really want this to run from Application#onCreate (which I do), this seems to work as well:
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
// Run mFirebaseRemoteConfig.fetch(timeout) here, and it works
}
}, 0);
You're likely hitting the caching in Remote Config. The way it works is that Config will cache incoming items locally, and return them. So your last (cached) fetch status was probably before the value was defined, and we get a cached blank value.
You can control the cache expiry, but if you fetch too often you risk getting throttled.
Because this is a common development problem though, there is a developer mode that lets you request more rapidly (for small groups of users):
FirebaseRemoteConfigSettings configSettings =
new FirebaseRemoteConfigSettings.Builder()
.setDeveloperModeEnabled(BuildConfig.DEBUG)
.build();
FirebaseRemoteConfig.getInstance().setConfigSettings(configSettings);
When you call fetch you can then pass a short cache expiration time
long cacheExpiration = 3600;
FirebaseRemoteConfig mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
if (mFirebaseRemoteConfig.getInfo().getConfigSettings().isDeveloperModeEnabled()) {
cacheExpiration = 0;
}
mFirebaseRemoteConfig.fetch(cacheExpiration)
.addOnCompleteListener(new OnCompleteListener<Void>() {
// ...
});
That's how its done in the quickstart sample if you want a full reference.
Found the problem.
After adding some logging, I found that the fetch job's onComplete() was never being called. I moved the fetch from my Application's onCreate to a fragment's, and now it works properly!
(Ian Barber, this might be something to look into or clarify, as the logs indicated that Firebase was initialized without an issue when it was in the Application, and the fetches were silent failures.)
I also encountered this problem. Turns out I hadn't seen the 'Publish' button in the the Firebase console. :facepalm:
I had the same problem and no workarounds were helpful in my case. The problem was in the testing device. I used emulator without installing Google Mobile Services, because of this the Complete event was not fired. I tried my phone with GMS and everything worked great. Good luck.
First thing in such case is check if you have the correct firebase config and you are connected to firebase .If you have android studio 2.2 got to Tools->Firebase->RemoteConfig - Connect to Firebase and see if you get a notification saying connected.Once Connected do the following in your code:
mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
/** NOTE: At this point, your app can use in-app default parameter values.To use in-app
* default values,skip the next section. You can deploy your app without setting
* parameter values on the server,and then later set values on the server to
* override the default behavior and appearance of your app.
*/
mFirebaseRemoteConfig.setDefaults(R.xml.remote_config_defaults);
FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder()
.setDeveloperModeEnabled(true)
.build();
mFirebaseRemoteConfig.setConfigSettings(configSettings);
And then for fetching config do the following
long cacheExpiration = 2000; // Can increase this usually 12hrs is what is recommended
/** If in developer mode cacheExpiration is set to 0 so each fetch will retrieve values from
* the server.*/
if (mFirebaseRemoteConfig.getInfo().getConfigSettings().isDeveloperModeEnabled()) {
cacheExpiration = 0;
}
/** cacheExpirationSeconds is set to cacheExpiration here, indicating that any previously
* fetched and cached config would be considered expired because it would have been fetched
* more than cacheExpiration seconds ago. Thus the next fetch would go to the server unless
* throttling is in progress. The default expiration duration is 43200 (12 hours).
*/
mFirebaseRemoteConfig.fetch(cacheExpiration)//TODO Bring this from a config file
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
Log.d(TAG, "Firebase Remote config Fetch Succeeded");
// Once the config is successfully fetched it must be activated before newly fetched
// values are returned.
mFirebaseRemoteConfig.activateFetched();
} else {
Log.d(TAG, "Firebase Remote config Fetch failed");
}
showRemoteConfig();
}
});
Run your App and check in logs " Firebase Remote config Fetch Succeeded ". If you see the same your remote configs are loaded and activated.
I've used a similar code like #Ian Barber (copy):
FirebaseRemoteConfigSettings configSettings =
new FirebaseRemoteConfigSettings.Builder()
.setDeveloperModeEnabled(BuildConfig.DEBUG)
.build();
FirebaseRemoteConfig.getInstance().setConfigSettings(configSettings);
My problem was the "BuildConfig.DEBUG", it returns false. So it takes the value 1h in cache until it was fetched again!
I had a problem that Firebase Remote Config didn't fire OnCompleteListener with fetch(0), but with fetch() did.
Looking at FirebaseRemoteConfig.fetch() does not trigger OnCompleteListener every time, I found that the first answer was working sometimes even with fetch(0). Then I again set 3600 seconds for interval, as errors continued to appear:
override fun onPostResume() {
super.onPostResume()
// Initialize FirebaseRemoteConfig here.
...
firebaseRemoteConfig.fetch(3600).addOnCompleteListener { task ->
if (task.isSuccessful) {
firebaseRemoteConfig.activateFetched()
//calling function to check if new version is available or not
checkForUpdate(currentVersionCode, firebaseRemoteConfig.getString(VERSION_CODE_KEY))
} else
Toast.makeText(this#MainActivity, "Someting went wrong please try again",
Toast.LENGTH_SHORT).show()
}
}
Well in my case, I am able to receive control in addOnCompleteListener for fetch method but I have fetched values firebaseRemoteConfig just after I called firebaseRemoteConfig.activate(), so when I have tried to get the values from firebaseRemoteConfig it returns me previously saved values because firebaseRemoteConfig.activate() runs asynchronously and new values didn't saved before I am getting them from firebaseRemoteConfig, so I have added complete listener for activate() method also, Here:
firebaseRemoteConfig.fetch()
.addOnCompleteListener(activity, OnCompleteListener {
if (it.isSuccessful)
{
Log.d("task","success")
firebaseRemoteConfig.activate().addOnCompleteListener { // here I have added a listener
val base_url=firebaseRemoteConfig.getString("base_url")
Log.d("base url",base_url)
Toast.makeText(activity, "Base url: $base_url",Toast.LENGTH_SHORT).show()
}
}
else
{
Log.d("task","failure")
}
})
Does Android Studio's Build Variant match your intended Firebase project?
I work on a big project and the problem was buried in an unexpected place.
Long story short: the firebase application id(normally set through google-services.json) was changed through code:
FirebaseOptions.Builder builder = new FirebaseOptions.Builder();
builder.setApplicationId(applicationId);
builder.setApiKey(apiKey);
FirebaseOptions options = builder.build();
FirebaseApp.initializeApp(context, options);
The solution was to remove that code and let firebase use the info from "google-services.json".
Use fetchAndActivate instead of fetch
I was facing the same problem. After fetching, no listener get call for first time only. I try fetchAndActivate in single line and it works for me. Use below code
mFirebaseRemoteConfig.fetchAndActivate()
.addOnCompleteListener(this, new OnCompleteListener<Boolean>() {
#Override
public void onComplete(#NonNull Task<Boolean> task) {
if (task.isSuccessful()) {
boolean updated = task.getResult();
Log.d(TAG, "Config params updated: " + updated);
Toast.makeText(MainActivity.this, "Fetch and activate succeeded",
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "Fetch failed",
Toast.LENGTH_SHORT).show();
}
displayWelcomeMessage();
}
});
It will fetch and activate immediately. You can find this way in official documentation here

Categories

Resources