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
Related
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.
My Android app is using the Android Facebook SDK's LoginManager and AccessToken classes. We use LoginManager.getInstance().logInWithReadPermissions() to initially link a Facebook account to our service. This method works fine and has no issues.
The issue we are having is when we're trying to refresh a Facebook access token upon our app starting (in the background you could say). For this, we use AccessToken.refreshCurrentAccessTokenAsync(), which from Facebook's docs states that it extends an access token's expiration. I'll note that the callback provided in this case does fire the OnTokenRefreshed() method, so it at least appears to be functioning. However, upon checking (from our backend services) we can see that the expiration date for that access token has not been extended since refreshCurrentAccessTokenAsync was called.
Here's a snippet:
if (AccessToken.getCurrentAccessToken() == null)
{
return;
}
AccessToken.refreshCurrentAccessTokenAsync(new AccessToken.AccessTokenRefreshCallback()
{
#Override
public void OnTokenRefreshed(AccessToken accessToken)
{
Log.d(TAG, "OnTokenRefreshed >> " + accessToken);
}
#Override
public void OnTokenRefreshFailed(FacebookException exception)
{
Log.w(TAG, "OnTokenRefreshFailed >> " + exception.getClass().getSimpleName() + ": " + exception.getMessage(), exception);
}
});
However, again I'll mention that we notice on the backend that the expiration date is still set to ~60 days from when it was initially linked using FacebookManager.getInstance().loginWithReadPermissions().
Any ideas why the expiration date isn't changing? Any help would be hugely appreciated.
The caveat is in the fine print in the doc:
extends the expiration date, if extension is possible
Extending access tokens by virtue of just wishing to extend expiration is no longer offered. See here: https://developers.facebook.com/docs/facebook-login/access-tokens/refreshing The 'refresh' function really now just refreshes permissions. Of course, there is this sort of puzzling note
These tokens will be refreshed once per day, for up to 90 days, when
the person using your app makes a request to Facebook's servers.
I take it to mean that if the app performs one of the graph API requests then the expiration may be extended still only to a maximum of 90 days.
I'm new to Firebase and I'm having a lot of problems with the fact that all the tasks are called asynchronously.
For example, I am trying to use fetchProvidersForEmail to know if I should direct the user to sign up or log in. However, by the time the task finishes, it's too late.
I am not sure if it's clear but here is my current code (which works) and below is the method I would want to create. How can I get that done?
public static void printProviders(String email) {
FirebaseAuth auth = FirebaseAuth.getInstance();
auth.fetchProvidersForEmail(email).addOnCompleteListener(new OnCompleteListener<ProviderQueryResult>() {
#Override
public void onComplete(#NonNull Task<ProviderQueryResult> task) {
Log.d(TAG, "We have " + task.getResult().getProviders().size() + " results.");
for (int i = 0; i < task.getResult().getProviders().size(); i++) {
Log.d(TAG, "Provider " + (i+1) + ": " + task.getResult().getProviders().get(i));
}
}
}
);
}
Here is the pseudo-code of the method I would want to create (of course, this doesn't work)...
public static boolean emailIsRegistered(String email) {
FirebaseAuth auth = FirebaseAuth.getInstance();
auth.fetchProvidersForEmail(email).addOnCompleteListener(new OnCompleteListener<ProviderQueryResult>() {
#Override
public void onComplete(#NonNull Task<ProviderQueryResult> task) {
if (task.getResult().getProviders().size() > 0) {
return true;
}
return false;
}
});
}
However, this does not work because the return statement is void for onComplete() and because the task is executed asynchronously...
I am new to this. I tried to search through StackOverflow but couldn't find anything that helped me. Hopefully someone can help.
Thank you!
When you call fetchProvidersForEmail that information is not available in the APK of your app. The Firebase client has to call out to the servers to get this information.
Given the nature of the internet, this means that it will take an undetermined amount of time before the result comes back from those servers.
The client has a few options on what to do in the meantime:
wait until the data is available
continue executing and calling you back when the data is available
Waiting for the data would mean that your code stays simple. But it also means that your app is blocked while the data is being looked up. So: no spinner animation would run, the user can't do anything else (which may be fine for your app, but not for others), etc. This is considered a bad user experience. So bad in fact, that Android will show an Application Not Responding dialog if your app is in this state for 5 seconds.
So instead, the Firebase SDKs choose the other option: they let your code continue, while they're retrieveing the data from the servers. Then when the data is retrieved, they call back into a code block you provided. Most modern web APIs are built this way, so the sooner you come to grips with it, the sooner you can efficiently use those APIs.
The easiest way I found to grasps asynchronous programming is by reframing your problems. Right now you're trying to "first determine if the email is already used, then sign the user up or in".
if (emailIsRegistered(email)) {
signInUser(email);
}
else {
signUpUser(email);
}
This approach leads to a emailIsRegistered method that returns a boolean, something that is impossible with asynchronous methods.
Now let's reframe the problem to "determine if the email is already used. When we know this, sign the user up or in".
This leads to a different piece of code:
public static boolean emailIsRegistered(String email) {
FirebaseAuth auth = FirebaseAuth.getInstance();
auth.fetchProvidersForEmail(email).addOnCompleteListener(new OnCompleteListener<ProviderQueryResult>() {
#Override
public void onComplete(#NonNull Task<ProviderQueryResult> task) {
if (task.getResult().getProviders().size() > 0) {
signUserIn(email);
}
signUserUp(email);
}
});
We've moved the calls to sign the user up or in into the emailIsRegistered method and invoke then when the result becomes available.
Now this of course hard-coded the follow up action into the emailIsRegistered method, which makes it harder to re-use. That's why you quite often see a callback being passed into these functions. A great example of that is the OnCompleteListener that you're already using. Once the Firebase client gets the result from the servers, it calls the onComplete method that you passed in.
Learning to deal with asynchronous calls is both hard and important. I'm not sure if this is my best explanation of the concepts ever. So I'll include some previous explanations (from both me and others):
Setting Singleton property value in Firebase Listener
Firebase Android: How to read from different references sequentially
Is it possible to synchronously load data from Firebase?
Knowing when Firebase has completed API call?
Gathering data from Firebase asynchronously: when is the data-set complete?
What is callback in Android?
I'm trying to implement Firebase Remote Config :
override fun onCreate(savedInstanceState: Bundle?) {
val configSettings = FirebaseRemoteConfigSettings.Builder().setDeveloperModeEnabled(BuildConfig.DEBUG).build()
mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance()
mFirebaseRemoteConfig.setConfigSettings(configSettings)
mFirebaseRemoteConfig.setDefaults(R.xml.remote_config_defaults)
fetchRemoteConfig()
}
private fun fetchRemoteConfig() {
var cacheExpiration = 3600L
if (mFirebaseRemoteConfig.info.configSettings.isDeveloperModeEnabled) {
cacheExpiration = 0L
}
mFirebaseRemoteConfig.fetch(cacheExpiration)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d(TAG, "Remote config fetch succeeded")
mFirebaseRemoteConfig.activateFetched()
} else {
Log.d(TAG, "Remote config fetch failed - ${task.exception?.message}")
}
setupView()
}
}
private fun setupView() {
val text = mFirebaseRemoteConfig.getString("my_text")
//...
}
My problem is that the OnCompleteListener is not always called.
If I close/open my app several times, the setupView() is not always triggered.
The OnCompleteListener should always be called right? Even if I'm hitting cache?
EDIT: Even if I disable the developper mode the behavior is the same. Sometimes the callback is triggered, sometimes not.
I was facing the same issue and contacted the firebase support. They replied the following:
There currently is a bug that has been reported where onComplete, onSuccess, and onFailure listeners doesn't get called if fetch() is called too early. [...]
Currently there is a work around where you can put the fetch() inside a postResume. You can try using this in the meantime before a solution has been released.
I implemented the workaround accordingly
protected void onPostResume() {
super.onPostResume();
mFirebaseRemoteConfig.fetch(cacheExpiration)
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "Fetch Succeeded");
// Once the config is successfully fetched it must be activated before newly fetched values are returned.
mFirebaseRemoteConfig.activateFetched();
// Do whatever should be done on success
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception exception) {
Log.d(TAG, "Fetch failed");
// Do whatever should be done on failure
}
});
}
So far it seems their proposed workaround has resolved the issue.
UPDATE:
I just got notice from the firebase support. According to them the issue is resolved with the latest Google Play Services update.
A fix to Remote Config not calling listeners after fetching has been released in the newest Google play services update.
I'll be closing this case for now. However if you are still experiencing issues, feel free to reach out and let me know.
If your device run an old Google Play Service and incompatible version, you should see in logs:
GooglePlayServicesUtil: Google Play services out of date. Requires 11020000 but found 10930470
One solution is to upgrade your device Google Play services, but if you cannot, you can also simply downgrade firebase version to match the expected version (here change 11.0.2 to 10.9.3).
Not ideal, but still a solution if you cannot upgrade your device (for instance the simulator is running 10.9.3 as of today):
compile 'com.google.firebase:firebase-core:10.2.6'
compile 'com.google.firebase:firebase-messaging:10.2.6'
compile 'com.google.firebase:firebase-config:10.2.6'
For those of you who cannot make it by simply calling fetch() onPostResume (and really willing to make this work better), you may try calling fetch method inside Handler.postDelayed() to delay your fetch timing. For our team it increased the chance of fetch method working correctly. Of course this solution does not work reliably just like calling fetch onPostResume though.
#Override
public void onPostResume() {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mFirebaseRemoteConfig.fetch(cacheExpiration)...
...
}
}, 500L);
}
UPDATE version 9.2.0 of firebase works as one would expect and this hack is no longer needed.
I got this "working" reliably... but you may not like my solution. In order to get the config fetch to happen when firebase is ready I had to do this:
FirebaseAuth.getInstance()
// I don't actually want to or need to sign in..(and this actually throws an error for us.. but we ignore it)
.signInAnonymously()
// when it completes (error or no error) we can do our business
.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
// do the remote config fetch you were doing before
remoteConfig.fetch(...).addOnComplete(...);
}
});
This ensures that the firebase internals are ready to do that initial config fetch... on first app open this seems to take about 6-10 seconds on my crappy test device (the entire thing including the auth and config fetch). On subsequent opens the entire thing takes like 2-5 seconds. Obviously that's all arbitrary depending on device/network and YMMV.
I would love to know why this is required.. seems like remote config should be able to manage this internally and not expose this to us.
p.s. you will need this dependency in addition to the firebase-config
compile 'com.google.firebase:firebase-auth:9.0.1'
Running the code below, I create a folder with Google Drive Android API on a tablet. After a few seconds, delete that folder from a remote location on a PC. When I re-run the code, the API still thinks 'MyFolder' exists, even though it was deleted and not visible in the Google Drive app on the tablet. The folder persistance finally disappears after a while and the code works as expected. Is this expected behavior for Cloud drives?
Query query = new Query.Builder()
.addFilter(Filters.and(Filters.eq(
SearchableField.TITLE, "MyFolder"),
Filters.eq(SearchableField.TRASHED, false)))
.build();
Drive.DriveApi.query(getGoogleApiClient(), query)
.setResultCallback(new ResultCallback<DriveApi.MetadataBufferResult>() {
#Override
public void onResult(DriveApi.MetadataBufferResult result) {
if (!result.getStatus().isSuccess()) {
showMessage("Cannot create folder in the root.");
} else {
boolean isFound = false;
for(Metadata m : result.getMetadataBuffer()) {
if(!isFound) {
if (m.getTitle().equals("MyFolder")) {
showMessage("Folder exists");
isFound = true;
}
}
}
if(!isFound) {
showMessage("Folder not found; creating it.");
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle("MyFolder")
.build();
Drive.DriveApi.getRootFolder(getGoogleApiClient())
.createFolder(getGoogleApiClient(), changeSet)
.setResultCallback(new ResultCallback<DriveFolder.DriveFolderResult>() {
#Override
public void onResult(DriveFolder.DriveFolderResult result) {
if (!result.getStatus().isSuccess()) {
showMessage("Error while trying to create the folder");
} else {
mThwingAlbertFolderId = result.getDriveFolder().getDriveId();
showMessage("Created a folder: " + mThwingAlbertFolderId);
}
}
});
}
}
}
});
What you are seeing, is a 'normal' behavior of the GDAA, that can be explained if you look closer at the 'Lifecycle of a Drive file' diagram (warning: I've never seen the source code, just assuming from what I observed).
See, the GDAA, unlike the REST Api, creates a layer that does its best to create caching and network traffic optimization. So, when you manipulate the file/folder from the 'outside' (like the web app), the GDAA layer has no knowledge of the fact until it initiates synchronization, controlled by it's own logic. I myself originally assumed that GooDrive has this under control by dispatching some kind of notification back to the GDAA, but it apparently is not the case. Also, some Googlers mentioned 'requestSync()' as a cure, but I never succeeded to make it work.
What you think you're doing, is polling the GooDrive. But effectively, you're polling the GDAA (local GooPlaySvcs) whose DriveId is still valid (not updated), unlike the real GooDrive object that is already gone.
This is one thing that is not clearly stated in the docs. GDAA is not the best Api for EVERY application. It's caching mechanism is great for transparently managing online/offline states, network traffic optimization. battery life, ... But in your situation, you may be better off by using the REST Api, since the response you get reflects the current GooDrive state.
I myself faced a similar situation and had to switch from the GDAA back to the REST (and replaced polling with a private GCM based notification system). Needless to say, by using the REST Api, your app gets more complex, usually requiring sync adapter / service to do the data synchronization, managing network states, ... all the stuff GDAA gives you for free).
In case you want to play with the 2 apis side-by side, there are two identical CRUD implementation you can use (GDAA, REST) on Github.
Good Luck
Google drive api does not sync immediately, That is why the deleted folders are still showing, so you have to force google drive to sync using requestSync()
Drive.DriveApi.requestSync(mGoogleApiClient).await();
I fount an example snippet here:
http://wiki.workassis.com/android-google-drive-api-deleted-folder-still-exists-in-query/
As Sean mentioned, the Drive Android API caches metadata locally to reduce bandwidth and battery usage.
When you perform an action on the device, e.g. creating a folder, we attempt to apply that action on the server as soon as possible. Though there can be delays due to action dependencies and content transfers, you will generally see the results reflected on the server very quickly.
When an action is performed on the server, e.g. by deleting a folder via the web client, this action is reflected on the device the next time the Drive Android API syncs. In order to conserve battery and bandwidth, sync frequency depends on how the API is being used as this is a priority for users.
If you need to guarantee that a sync has occurred, you can explicitly request a sync using DriveApi.requestSync() and wait on the result. This is currently rate limited to 1 per minute, which is frequently hit during testing, but should have a much smaller impact on real world usage.
Please let us know on our issue tracker if this sync behavior is causing issues for your use case so we can investigate solutions.
Google drive uses its own lifecycle for Drive api and manage all things in cache that's why if you delete some file or folder and try to access using google drive apis it is still available because it is stored in cache so you need to explicitly call requestSync() method for that then after that cache will be updated and gives you that folder or file not found.
below is code for that:
Drive.DriveApi.requestSync(mGoogleApiClient).setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(#NonNull Status status) {
Log.e("sync_status", status.toString());
if (status.getStatus().isSuccess()) {
setRootFolderDriveId();
}
}
});
and don't call Drive.DriveApi.requestSync(mGoogleApiClient).await() because your main thread will block so it will crash. use above one and after get successful callback you can do your operation on google drive because it's updated.
You can do it in main thread:
Drive.DriveApi.requestSync(mGoogleApiClient).setResultCallback(new ResultCallback<com.google.android.gms.common.api.Status>() {
#Override
public void onResult(com.google.android.gms.common.api.Status status) {
if (!status.getStatus().isSuccess()) {
Log.e("SYNCING", "ERROR" + status.getStatusMessage());
} else {
Log.e("SYNCING", "SUCCESS");
// execute your code to interact with Google Drive
}
}
});
I was having the same issue and using "Drive.DriveApi.requestSync" did the trick.
Also I suggest taking a look at https://github.com/francescocervone/RxDrive because you can concatenate the sync to other drive operations using rxandroid.
For example, this becomes a delete-and-sync operation:
Observable<Boolean> deleteFile = rxDrive.delete(file);
Observable<Void> syncDrive = rxDrive.sync();
Observable.concat(deleteFile, syncDrive);
The reason why you get listed deleted files from your query is that Google Drive has a "Trash" folder that is "searchable". You need to empty your trash first.