Maybe I am missing something, but shouldn't the fetch() be called only if the cached values are older than the cached value?
Having this code called from activity's onCreate
firebaseRC = FirebaseRemoteConfig.getInstance()
firebaseRC.fetch(3600L). .addOnCompleteListener(this, new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
//THE TASK IS ALWAYS SUCCESSFUL
firebaseRC.activateFetched();
}
}
});
If I start the app on my emulator a few times in a row, the fetch completes successfully each time.
Shouldn't the fetch actually complete with success only if my data is older than 3600 seconds? So starting the app a second time, fetch should complete without onComplete
The docs say:
Remote Config caches values locally after the first successful fetch.
By default the cache expires after 12 hours, but you can change the
cache expiration for a specific fetch by passing the desired cache
expiration to the fetch method. If the values in the cache are older
than the desired cache expiration, Remote Config will request fresh
config values from the service. If your app requests fresh values
using fetch several times, requests are throttled and your app is
provided with cached values.
It doesn't say how onComplete will trigger... should I use activateFetched() each time as it has the same value?
The API documentation for fetch() says that the results will come from either cache or server. It doesn't say that a cached result it considered a failure. The fetch will only fail if it can't give you any results at all, which means your app is probably offline and has no previously successful fetched values in cache.
Related
I am working on an application, which uses OAuth - Token based authentication.
This is how the flow looks like, considering we have the access and refresh token.
Api call -> intercepter appends access-token -> api returns 200
Api call -> intercepter appends expired access-token -> api returns
401 -> intercepter refreshes token using refresh token ->
interceptor retries same req -> returns 200
Api call -> intercepter appends expired access-token -> api returns
401 -> intercepter refreshes token using refresh token(refresh token
is also expired) -> prompt guest to sign-in -> guest signed-in ->
retry request
This all works great and fine - I am considering to optimise it a bit i.e i don't want to call api and wait for 401 to return.
Instead check for token expiration beforehand, get the new access token and then call the api with valid token.
This approach of calculating the expiry of token using android system time might work - but can be misused sometimes when user changes the android time.
Wondering if there a better solution to avoid the expiry issue of time based on android system time.
Even if you add such a check in your code, you will still need that flow you presented in the question (so catching 401s and refreshing tokens accordingly). This is because, as you noticed, time settings on the client device can be changed, or there might be a slight clock skew between the client and the server (so no intentional tampering with time settings).
The approach where you check for expiry before the API call will work only if you have access to the expiration time of the access and refresh tokens. So either you received that information together with tokens and persisted it, or JWTs are used and you can easily check expiration.
Personally, I wouldn't add such a check unless there is some strong argument for it (e.g. you know that your app will be mainly used in remote locations with slow connections and you want to limit traffic to a minimum, etc.). The flow that you presented is a common one and works just fine.
I am using Firebase Firestore and I'm facing a problem with the read operation:
I use an onCompleteListener, and inside there, I call different callbacks if the operation was successfull or not.
The problem is, if there is a network issue, the onCompleteListener is called, but task.isSuccessfull returns true!! So I get an empty result which I can't distinguish from a REAL empty result. Is there any way to distinguish a network issue from an empty read?
Thank you very much! My function is just below:
dataBase.collection(COLLECTION)
.whereEqualTo(FIELD, searched)
.get()
.addOnCompleteListener { task: Task<QuerySnapshot> ->
if (task.isSuccessful) {
listenerSuccess(task.result)
} else {
listenerError()
}
}
If you're offline, the client will first try to connect. Once it figures out that it can't connect, it will try to complete the get() based on the data in the local database. That's a valid action on Firestore, so that's why the task is completed successfully.
You can detect if the results came from the local cache vs which came straight from the server, by checking the metadata: querySnapshot.getMetadata().isFromCache(). From the docs:
true if the snapshot was created from cached data rather than guaranteed up-to-date server data. If your listener has opted into metadata updates (via MetadataChanges.INCLUDE) you will receive another snapshot with isFomCache() equal to false once the client has received up-to-date data from the backend.
I'm building an Android application which has to work offline for weeks, but can sync immediately with a remote DB as it goes online.
My question is can Firestore be a good option for this? How long does Firestore keep its offline cache?
Firestore can be configured to persist data for such disconnected/offline usage. I recommend that you read the enable offline persistence section in the docs, which contains this sample of enabling this feature:
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
.setPersistenceEnabled(true)
.build();
db.setFirestoreSettings(settings);
This persistence is actually enabled by default on Android and iOS, so the call above is not needed.
Your Android code that interact with the database will be the same whether you're connected or not, since the SDK simply works the same. If you want to detect whether data is coming from the cache (and thus potentially stale), read the section Listen to offline data in the docs.
The data in the cache does not expire after a certain amount of time. The only two reasons data is removed from the cache:
The data has been removed from the server, in which case the client will remove it from the disk cache.
The client needs to purge its disk cache to make space for more recent data.
EDIT-25/5/2020: Francesco is correct, the docs link given in the comment does clarify that. It seems the cache size has been changed, by default it has been decreased to 40MB.
OLD: The following answer follows the official guide in the following link:
Handling Cache size
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
.setCacheSizeBytes(FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED)
.build();
db.setFirestoreSettings(settings);
The above code has a flag set for setCacheSize(), which will prevent your cache from
being cleared. You can also specify the same in size. If you do not set this by default the size is 100MB.
As per the guide, there is a method to check if the data you query came from cache or the firestore. Also the moment your device is back online the firestore refreshes the cache, and keeps the data synchronized.
To answer your question, as you have to work with offline data for weeks, i suggest every time the data is fetched to store it in json/xml formats, as storing huge amount of data in cache is not a really good approach when thought of in terms of performance.
I hope i helped you clear some things out.
If you listen to data in Cloud Firestore, you will get immediate snapshots of cached data and also updates when your app is able to connect online:
final DocumentReference docRef = db.collection("cities").document("SF");
docRef.addSnapshotListener(new EventListener<DocumentSnapshot>() {
#Override
public void onEvent(#Nullable DocumentSnapshot snapshot,
#Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.w(TAG, "Listen failed.", e);
return;
}
// Determine if the data came from the server or from cache
String source = snapshot != null && snapshot.getMetadata().hasPendingWrites()
? "Local" : "Server";
// Read the data
if (snapshot != null && snapshot.exists()) {
Log.d(TAG, source + " data: " + snapshot.getData());
} else {
Log.d(TAG, source + " data: null");
}
}
});
Persistence is enabled by default so this behavior does not require any configuration.
In the example OutlookQuickStart for Android works fine in the first request after logon().Now I want to keep connect my app to that user and continue checking for new emails.. How can I re use the access token and build the request to check for new emails? Do I have to save the access token, refresh token ?
How I can refresh the token in Android if it is expired.
According to the documentation for the auth library at https://github.com/AzureAD/azure-activedirectory-library-for-android, the library caches the token and refresh token for you. So you would just use acquireTokenSilentSync to get the token each time you need it. That function would return the current token from the cache if it is still valid, and would refresh it if it is expired.
UPDATE: I've taken a closer look at the sample you're using and the Outlook SDK that it uses. The key thing here is the DependencyResolver object. You pass that object to the OutlookClient constructor. Then anytime you make an API call with that OutlookClient, it just calls the getCredentials override that you supply when you create the DependencyResolver.
So as the sample stands, you should be able to make multiple calls through that OutlookClient without having to change it at all. However, after an hour, when the access token expires, calls will start to fail. The fix for that would be to change the getCredentials override to always call acquireTokenSilentSync. Something like:
#Override
public Credentials getCredentials() {
logger.debug("getCredentials in resolver called");
AuthenticationResult result = mAuthContext.acquireTokenSilentSync(
scopes,
getResources().getString(R.string.AADClientId),
UserIdentifier.getAnyUser());
logger.debug("AcquireTokenSilentSync SUCCESS");
logger.debug("Token expires: ", result.getExpiresOn());
logger.debug("Token: ", result.getAccessToken());
return new OAuthCredentials(result.getAccessToken());
}
Caveat: I'm unable to run this code to validate it due to problems getting the Android emulator running on my dev machine :(.
I am working on firebase for the first time, read about offline capabilities of firebase , tested two scenarios :
scenario 1 (working):
offline mode, writing data to firebase database.
press back button(closed app)
went online, data got added to the firebase database.
scenario 2 ( not working):
offline mode, writing data to firebase database
close app
remove app from background(killed the application)
went online, data not getting added
I added this line:
Firebase.getDefaultConfig().setPersistenceEnabled(true);
how to handle scenario 2 ? Do I need to handle this scenario through local database?
Are you using Firebase.getDefaultConfig().setPersistenceEnabled(true); and keepSynced(true)?
Because in Firebase documentation says that keepSynced(true) it's who make the "magic" happens (together with setPersistenceEnabled(true)):
By calling keepSynced(true) on a location, the data for that location will automatically be downloaded and kept in sync, even when no listeners are attached for that location. Additionally, while a location is kept synced, it will not be evicted from the persistent disk cache.
So, if you're not using it, you're not persisting your database locally and then when you "kill" the application, there will not be any database to query from when your app is opened again.
I guess your using some service to sync the data, It will not work for 2nd scenario. For that when user turn on data services you will receive a broadcast receiver, from that check service is not running then start the service.
No need to handle scenario 2 using local database . Use Firebase.getDefaultConfig().setPersistenceEnabled(true) in application class and make android:name="yourapplicationclass" in manifest file.
to handle sync while change network ie online/offline use transaction handler to handle local sync to firebase database since some tome data is not pushed to firebase server.Like this I used inside network change method and solved this issue:
mDatabase.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
if(mutableData.getValue() == null) {
mutableData.setValue(1);
} else {
mutableData.setValue((Long) mutableData.getValue() + 1);
}
return Transaction.success(mutableData); //we can also abort by calling Transaction.abort()
}
#Override
public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {
}
});