I am fetching Firebase remote config values both in onStart() and in onStop(). The variable I am holding in "min_version", used for in-app updates feature. So my problem is that when I am going to remote config and updating the values, they are not updated immediately but save their old values for one more app lifecycle iteration before being updated to the new value. That means that if I make the decision that I want all users from a certain version to updated, they will not get the in-app update immediately, but only after one iteration through onStop and than get it.
here are my codes for onStart() and onStop() -
#Override
protected void onStart() {
/**
* We are fetching the minimum version that we want the user to have from Firebase Remote Config, only after we have the results we can proceed to start the app.
*/
getMinAppVersion("onStart", () -> {
// navigation drawer
checkValidFacebookSession();
initDrawerMenu();
// network monitoring
registerNetworkReceiver();
// monitoring upload
LocalBroadcastManager.getInstance(this).registerReceiver(mUploadReceiver, new IntentFilter(ULBroadcastConstants.UPLOAD_STATUS_ACTION));
LocalBroadcastManager.getInstance(this).registerReceiver(mFCMReceiver, new IntentFilter(MyFirebaseMessagingService.RECEIVED_FCM_ACTION));
checkInAppUpdate();
});
super.onStart();
}
#Override
protected void onStop() {
getMinAppVersion("onStop", () -> {
mNavigationView.setNavigationItemSelectedListener(null);
mDrawerLayout.removeDrawerListener(mBadgeDrawerToggle);
// network monitor
unregisterNetworkReceiver();
// unregister upload
LocalBroadcastManager.getInstance(this).unregisterReceiver(mUploadReceiver);
LocalBroadcastManager.getInstance(this).unregisterReceiver(mFCMReceiver);
});
super.onStop();
}
here is my 'getMinAppVersion()' method -
private void getMinAppVersion(String didComeFrom, OnRemoteConfigFetchComplete listener){
//fetching the min_version parameter from 'remote config' of Firebase and saves it to our local variable.
FirebaseRemoteConfig mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder().setMinimumFetchIntervalInSeconds(200).build();
mFirebaseRemoteConfig.setConfigSettingsAsync(configSettings);
mFirebaseRemoteConfig.fetch(0);
mFirebaseRemoteConfig.activate().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
min_version = mFirebaseRemoteConfig.getLong(RemoteConfigUtil.MIN_VERSION);
Timber.tag("min_version_" + didComeFrom).d(String.valueOf(min_version));
if (listener != null)
listener.onFetchComplete();
} else {
Timber.tag("min version").d("error while fetching and activating remove config");
}
});
}
Here is why it's happening. The Remote Config caches the value in a local storage according to official docs. You can refer here and consider
"Remote Config includes a client library that handles important tasks like fetching parameter values and caching them, while still giving you control over when new values are activated so that they affect your app's user experience. This lets you safeguard your app experience by controlling the timing of any changes."
When you use the client library to fetch remote-config parameter, The cached value will be returned to you if it's there(TL;DL) for more information, you can read the official docs here. It uses minimum time interval to fetch the value to avoid app crashes, For that minimum interval time, last fetched value served as a cache
mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder()
.setMinimumFetchIntervalInSeconds(3600)
.build();
mFirebaseRemoteConfig.setConfigSettingsAsync(configSettings);
Here 3600 is time of minimum interval between new fetch, You can adjust the time according to your requirement. If you have recently called fetch() and once again you are calling it, client-library determines according to minimum-interval time that the new API call should be carried out or serve a cached value. Firebase remote config works on REST api and uses Throttling as well.
Remote Config updates in the console are not instant. They don't work like push notifications. The new values only take effect after the Task returned by fetch() completes, and you call activate() after that. I will point out that you're not using the Task returned by fetch() to find out when the new values have been received from Firebase.
Related
I am adding Firebase Remote Config to an app and I am confused about the purpose of .setMinimumFetchIntervalInSeconds(...) & .setDeveloperModeEnabled(true/false) . The docs talk about a developer mode, but I'm not sure they clearly explain what it actually does. Does it have to be used in tandem with setMinimumFetchIntervalInSeconds or can it be used on its own , and if on its own, what does it then do?
Secondly I'm testing my test boolean value in a debug build of the app, with values set to 5 minutes or hours but still I always get my value within 3 seconds. when I set setDeveloperModeEnabled to false or not add the FirebaseRemoteConfigSettings to my instance at all, I still have not observed the famed throttle exception and I get my values immediately. It basically looks like my cache settings are being ignored and I always get fresh data from the backend and I can set the cache as low as I want.
setDeveloperModeEnabled() is deprecated. They use setMinimumFetchIntervalInSeconds() instead now to set the cache expiration delay.
Check your gradle for this line and make sure it's version 19.1.4 (as of today) or newer:
implementation 'com.google.firebase:firebase-config:19.1.4'
Firebase has a quota for the number of fetch requests you can make. Developer mode is a way to greenlight your own device to be able to fetch at any time without restriction but you can't release your app with developer mode enabled (in which you still have to specify the interval)
if you are on v17.0.0, use this code by changing the cacheExpiration value to your desired one.
long cacheExpiration = 3600;
mFirebaseRemoteConfig.setConfigSettingsAsync(new FirebaseRemoteConfigSettings.Builder()
.setMinimumFetchIntervalInSeconds(cacheExpiration)
.build());
//** deprecated */
//mFirebaseRemoteConfig.setDefaults(R.xml.remote_config_defaults);
mFirebaseRemoteConfig.setDefaultsAsync(R.xml.remote_config_defaults);
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 " + updated,
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, "Fetch failed",
Toast.LENGTH_SHORT).show();
}
updateConfig();
}
});
setDeveloperModeEnabled is not supported anymore, which is probably why you didn't observe any change in its behaviour
For the past few days i've been trying to show the online/offline status of a user.. For this i have a register activity where they register and their info gets saved in firebase and if they exit an activity i have overriden its onstop method and made the value to set to offline... but if the user suddenly loses internet connection it still shows online.. i cant change it to offline because internet is needed to make a change in the database and the use doesn't have internet... SO how do i set the database value to offline... i googled quite some stuff about this but didnt find anything... Can anyone please help me out please
My code
#Override
protected void onStart() {
super.onStart();
fetchData();
// mDatabaseReference.child("UserData").child(UID).child("Online").setValue("True");
}
#Override
protected void onStop() {
super.onStop();
fetchData();
// mDatabaseReference.child("UserData").child(UID).child("Online").setValue(false);
}
What you're trying to do is known as a presence system. The Firebase Database has a special API to allow this: onDisconnect(). When you attach a handler to onDisconnect(), the write operation you specify will be executed on the server when that server detects that the client has disconnected.
From the documentation on managing presence:
Here is a simple example of writing data upon disconnection by using the onDisconnect primitive:
DatabaseRef presenceRef = FirebaseDatabase.getInstance().getReference("disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnect().setValue("I disconnected!");
In your case this could be as simple as:
protected void onStart() {
super.onStart();
fetchData();
DatabaseReference onlineRef = mDatabaseReference.child("UserData").child(UID).child("Online");
onlineRef.setValue("True");
onlineRef.onDisconnect().setValue("False");
}
Note that this will work in simple cases, but will start to have problems for example when your connection toggles rapidly. In that case it may take the server longer to detect that the client disappears (since this may depends on the socket timing out) than it takes the client to reconnect, resulting in an invalid False.
To handle these situations better, check out the sample presence system in the documentation, which has more elaborate handling of edge cases.
I'm working with firestore in android. I want to allow my user to save the data in app during the offline mode.(Data insertion during offline is also working fine) But I don't know how I can detect that data is added in offline mode, I need to get document id that is added. In the online mode I can detect the data insertion with the listener as.
Map<String, Object> data = new HashMap<>();
data.put("name", "Tokyo");
data.put("country", "Japan");
db.collection("cities")
.add(data)
.addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
#Override
public void onSuccess(DocumentReference documentReference) {
Log.d(TAG, "DocumentSnapshot written with ID: " + documentReference.getId());
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.w(TAG, "Error adding document", e);
}
});
I also need to detect that is added when the app is offline. So how I can? Because these listeners only works when the data is inserted in the server and app get the response from the server.
The addOnSuccessListener only gets called once the data is committed to the server. That's its explicit goal. If you local client also need the data after it's added locally, you'll do that with a regular addSnapshotListener.
From the documentation on events for local changes:
Local writes in your app will invoke snapshot listeners immediately. This is because of an important feature called "latency compensation." When you perform a write, your listeners will be notified with the new data before the data is sent to the backend.
Retrieved documents have a metadata.hasPendingWrites property that indicates whether the document has local changes that haven't been written to the backend yet. You can use this property to determine the source of events received by your snapshot listener.
See the linked documentation for sample code of how to process this.
Update: if you're just trying to get the ID of a new document, you can simply do:
DocumentReference newDoc = db.collection("cities").document();
System.out.println(newDoc.getId());
newDoc.set(data);
See CollectionReference.document().
Offline insert listener.
Based on my code test, it is just fine to remove both the success and snapshot listeners. simply check if a document id is not null and its length is greater than 10. that was enough for me to conclude a successful insert. code :
docRef.collection("collectioname").document("docname").set(aMap);
String id = docRef.collection("collectioname").document("docname").getId();
if( id != null & !id.isEmpty() & id.length() > 10) {
//Action here
}
Thank you Frank for your earlier clue. The add() will always write the document to the local .....in my case the set() method.
There are quite a few questions considering infinite loop of android's SyncAdapter: [1]
[2]
[3], but none described the problem I encountered.
I am setting up my sync as:
ContentResolver.setIsSyncable(account, AppConstants.AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, AppConstants.AUTHORITY, true);
ContentResolver.addPeriodicSync(account, AppConstants.AUTHORITY, Bundle.EMPTY, 60);
My sync adapter supports uploading (android:supportsUploading="true"), which means that in my ContentProvider I have to check whether the data change comes from my SyncAdapter, and if it does, then I notify change without requesting sync to network.
boolean syncToNetwork = false;
getContext().getContentResolver().notifyChange(uri, null, syncToNetwork);
Still my sync adapter runs in a constant loop, what another reason could there be for triggering another sync?
In each sync I request the server for data. For each request I get an access token from my custom Account Authenticator. Instead of saving a password in my account, I decided to save the Oauth2 refresh token, which can then be use to refresh the access token. With each refreshed access token the server also send a new refresh token, which I then update to my account:
accountManager.setPassword(account, refreshToken);
And THAT was the problem. Going through the AOSP codes I discovered the following BroadcastReceiver in the SyncManager:
private BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
updateRunningAccounts();
// Kick off sync for everyone, since this was a radical account change
scheduleSync(null, UserHandle.USER_ALL, null, null, 0 /* no delay */, false);
}
};
So what it does, on each account change (adding, deleting, setting password) a broadcast in send to trigger sync for all SyncAdapters, not just your own!
I honestly don't know what what the reasoning for that, but I can see it as exploitable - I let my phone (with my app stuck in infinite loop) run over night, in the morning the battery was drained, but also my FUP - only the Google's Docs, Slides and Sheets apps consumed 143MB each.
We are trying to use the library on Android for TimedEviction. The items in the cache are expiring as soon as we overwrite an existing item.
We are building the cache as follows:
private Cache rssiMap;
RemovalListener removalListener = new RemovalListener() {
#Override
public void onRemoval(RemovalNotification removal) {
}
};
rssiMap = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.removalListener(removalListener)
.build();
rssiMap.put(device, rssi);
Is there something wrong we are doing with the code or is this a known issue?
This is correct behavior.
Actually, client code doesn't care WHEN the elements are expired, right? Client code does care about WHAT final cache values are.
The RemovalListener focuses on WHAT is evicted instead of WHEN.
JavaDoc of RemovalListener.onRemoval():
Notifies the listener that a removal occurred at some point in the
past.
By the way, I recommend use Cache.get() instead of Cache.put().
From JavaDoc of Cache.put():
Prefer get(Object, Callable) when using the conventional "if cached,
return; otherwise create, cache and return" pattern.