Firebase - Android - fetchProvidersForEmail - Why are all the calls asynchronous? - android

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?

Related

How to log into firebase anonymously from Service or JobIntentService

I'm trying to log in in Firebase from these type of services in Android.
I'm able to successfully login anonymously from my main activity this way:
private FirebaseAuth mAuth;
(more code)
mAuth.signInAnonymously().addOnSuccessListener(this, new OnSuccessListener<AuthResult>() {
#Override
public void onSuccess(#NonNull AuthResult authResult) {
(more code to execute when the user has logged)
But if I try to use the same thing from a Service or JobIntentService it's impossible to do, as the first parameter for addOnSuccessListener needs to refer to an activity.
I tried to do a cast in the service to Activity to that "this", but, as expected, it didn't work.
I also tried with:
mAuth.signInAnonymously().addOnCompleteListener(
But the same happens with the first parameter, it needs to be an activity.
Under some circumstances, the mentioned services might run when the main activity is active, but not as a general rule, so I'd need something to allow a service to log in regardless of another circumstance.
Unless this was a restriction for firebase and cannot be done, which I would find pretty strange.
I finally realized a solution:
This code:
while (true){
try {
if (mAuth.getCurrentUser().getUid() == null) {
} else {
break;
}
}
catch (Exception e)
{
}
}
Allows to wait until the user has successfully logged in into firebase.
It can be improved by limiting the number of times the loop executes, but it serves as a general idea.

Save event type logs

We want to add a reporting feature to our existing application.
For this purpose we are sending Events in JSON via HTTPS to a server application.
We need to remember Event-objects that could not be send to the server (No internet, server not reachable...). We are considering to store the events in a SQLite database and discard all Events that are older than 24 hours to prevent flooding our storage.
Another option would be to write the JSON-objects to a file and concat each new event when it could not be send to the server. The problem with this solution is, that it would be hard for us to discard logs older than 24 hours.
We store the event sin a table with the columns:
| id | json | created_at |
Can anyone recommend best practices for this use case?
Currently we tend to use the sqlite solution but we are wondering if there are any caveats that we are not aware of.
If you don't mind using third-party lib i can recommend android-priority-jobqueue. You can easily achieve what you are trying to do. You can always create job and it will handle itself. You can set if it needs network, if it is persistent (saved into DB when no network) and even you can customize your own retry logic.
Here's little example.
public class PostTweetJob extends Job {
public static final int PRIORITY = 1;
private String text;
public PostTweetJob(String text) {
// This job requires network connectivity,
// and should be persisted in case the application exits before job is completed.
super(new Params(PRIORITY).requireNetwork().persist());
}
#Override
public void onAdded() {
// Job has been saved to disk.
// This is a good place to dispatch a UI event to indicate the job will eventually run.
}
#Override
public void onRun() throws Throwable {
// yours code here
}
#Override
protected RetryConstraint shouldReRunOnThrowable(Throwable throwable, int runCount,
int maxRunCount) {
// An error occurred in onRun.
return RetryConstraint.createExponentialBackoff(runCount, 1000);
}
}
And you call it like this.
jobManager.addJobInBackground(new PostTweetJob("It works"));
use JobService(Android 5+ - lollipop and above) and AlarmManager (for android sdk<21 - pre lollipop) with this solution u can schedule any task and it would be performed. JobService was developed rxactely for tjis purposes(schedule and perform different tasks) maybe you can try JobIntentService it is would work on kitkat(android 4+) devices
P.S.
In that case you didnt need any third party libs and other dependrncies like firebase/google play services(like for FirebaseDispatcher)

Firestore Offline Write/Add Not Working

We are able to get Firestore to download records to the local cache and can read those records. However, we are unable to update a record and not able to add a new record.
Have even tried some of the same code (https://github.com/firebase/quickstart-android) but don't seem to be able to get the offline write/insert working - only read.
When we call this function offline it gets called OnFailureListner and throws an exception
#Override
public void onRating(Rating rating) {
// In a transaction, add the new rating and update the aggregate totals
addRating(mRestaurantRef, rating)
.addOnSuccessListener(this, new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "Rating added");
// Hide keyboard and scroll to top
hideKeyboard();
mRatingsRecycler.smoothScrollToPosition(0);
}
})
.addOnFailureListener(this, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.w(TAG, "Add rating failed", e);
// Show failure message and hide keyboard
hideKeyboard();
Snackbar.make(findViewById(android.R.id.content), "Failed to add rating",
Snackbar.LENGTH_SHORT).show();
}
});
}
You can't simply write data offline because Firestore can get the data and upload it whenb you have an Internet connection (which requires a task going in the background). Firestore has improved its offline capabilities but you can always choose to create your own service to upload it when the device connects.
Android lets you create Services. From developer.android.com:
A service is a component that runs in the background to perform
long-running operations or to perform work for remote processes. A
service does not provide a user interface. For example, a service
might play music in the background while the user is in a different
application, or it might fetch data over the network without blocking
user interaction with an activity. Another component, such as an
activity, can start the service and let it run or bind to it in order
to interact with it. A service is implemented as a subclass of Service.
So you could create a Service which keeps waiting for an Internet connection and just calls the FirebaseFirestore object to upload all the data you want.
This may help you, this is the official Android information about services: https://developer.android.com/guide/components/services.html

AzureAD for Android throws ADALError.AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED

I have an app in which user authentificates in Office365 with AzureAD library for Android.
It works well, users can authentificate and work with the app. Unfortunately, after a while they start hitthing AuthenticationException with ADALError.AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED as an error code.
I checked the source code of AzurelAD. The only place, which is throughing this issue is acquireTokenAfterValidation() method:
private AuthenticationResult acquireTokenAfterValidation(CallbackHandler callbackHandle,
final IWindowComponent activity, final boolean useDialog,
final AuthenticationRequest request) {
Logger.v(TAG, "Token request started");
// BROKER flow intercepts here
// cache and refresh call happens through the authenticator service
if (mBrokerProxy.canSwitchToBroker()
&& mBrokerProxy.verifyUser(request.getLoginHint(),
request.getUserId())) {
.......
Logger.v(TAG, "Token is not returned from backgroud call");
if (!request.isSilent() && callbackHandle.callback != null && activity != null) {
....
} else {
// User does not want to launch activity
String msg = "Prompt is not allowed and failed to get token:";
Logger.e(TAG, msg, "", ADALError.AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED);
callbackHandle.onError(new AuthenticationException(
ADALError.AUTH_REFRESH_FAILED_PROMPT_NOT_ALLOWED, msg));
}
// It will start activity if callback is provided. Return null here.
return null;
} else {
return localFlow(callbackHandle, activity, useDialog, request);
}
}
My source code:
authenticator.getAccessTokenSilentSync(getMailService());
public class Authenticator {
..............
public String getAccessTokenSilentSync(ServiceInfo serviceInfo) {
throwIfNotInitialized();
return getAuthenticationResultSilentSync(serviceInfo).getAccessToken();
}
private AuthenticationResult getAuthenticationResultSilentSync(ServiceInfo serviceInfo) {
try {
return authenticationContext.acquireTokenSilentSync(
serviceInfo.ServiceResourceId,
Client.ID,
userIdentity.getAdUserId());
} catch (AuthenticationException ex) {
// HERE THE EXCEPTION IS HANDLED.
}
}
..............
}
Stacktrace I'm getting:
<package name>.data_access.error_handler.AuthenticationExceptionWithServiceInfo: Refresh token is failed and prompt is not allowed
at com.microsoft.aad.adal.AuthenticationContext.localFlow(AuthenticationContext.java:1294)
at com.microsoft.aad.adal.AuthenticationContext.acquireTokenAfterValidation(AuthenticationContext.java:1229)
at com.microsoft.aad.adal.AuthenticationContext.acquireTokenLocalCall(AuthenticationContext.java:1123)
at com.microsoft.aad.adal.AuthenticationContext.refreshToken(AuthenticationContext.java:1609)
at com.microsoft.aad.adal.AuthenticationContext.localFlow(AuthenticationContext.java:1261)
at com.microsoft.aad.adal.AuthenticationContext.acquireTokenAfterValidation(AuthenticationContext.java:1229)
at com.microsoft.aad.adal.AuthenticationContext.acquireTokenLocalCall(AuthenticationContext.java:1123)
at com.microsoft.aad.adal.AuthenticationContext.refreshToken(AuthenticationContext.java:1609)
at com.microsoft.aad.adal.AuthenticationContext.localFlow(AuthenticationContext.java:1261)
at com.microsoft.aad.adal.AuthenticationContext.acquireTokenAfterValidation(AuthenticationContext.java:1229)
at com.microsoft.aad.adal.AuthenticationContext.acquireTokenLocalCall(AuthenticationContext.java:1123)
at com.microsoft.aad.adal.AuthenticationContext.access$600(AuthenticationContext.java:58)
at com.microsoft.aad.adal.AuthenticationContext$4.call(AuthenticationContext.java:1072)
at com.microsoft.aad.adal.AuthenticationContext$4.call(AuthenticationContext.java:1067)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
Version of AzureAD library I'm using: 1.1.7 (to prevent blaming too old version - I've checked the changelist since from 1.1.7 to 1.1.11 and haven't found anything related to question)
Problem: Right now, I'm treating this error, as a signal to through the user to the login screen. In my opinion, it leads to a poor experience for the user. The fact that it happens very often and affects many users make it even worse.
Question: Is there anything I can do different to avoid this AuthenticationException or workaround it somehow (i.e. avoid user enters credentials once again).
Have you verified that AuthenticationContext.acquireTokenSilentSync() is truly the method that you wish to invoke?
The docs indicate that this method will explicitly not show a prompt. From the docs:
This is sync function. It will first look at the cache and automatically checks for the token expiration. Additionally, if no suitable access token is found in the cache, but refresh token is available, the function will use the refresh token automatically. This method will not show UI for the user. If prompt is needed, the method will return an exception.
The refresh token you are issued should last two weeks per this AAD book. After the refresh token expires users are expected to reauthenticate. Can you inspect net traffic with Fiddler or Charles and inspect the expiry of the tokens? If you can verify that the tokens are failing to refresh before their expiry it may indicate a bug in the AD library.
To clarify the difference in methods on AuthenticationContext - there are two categories of methods: "silent" methods (which will not present a dialog to user in the event that they need to reauthenticate), and non-silent. Non-silent methods will, in the event of requiring reauthentication (or consent) from the user, start a new Activity containing the AAD login. At that point the authentication flow is restarted.
Additionally, if you make changes to your application's registration in Azure such as adding new permission scopes your users will be required to re-grant consent for the application to continue to handle their data.
This is because you need to refresh your token and implement this in your code so the user won't be prompt to login every time the access token is expired. please check out how to implement refresh token here:
https://msdn.microsoft.com/en-us/library/azure/dn645538.aspx
Hope this helps.

OneDrive - How to retrieve authorization access data?

I develop an android application which use OneDrive API. When I connect to OneDrive, it asks me to authenticate and show an authorization page (with permission to access to my data on the cloud).
My problem is : Every time I upload data on the cloud, the application shows me the authorization page. I would like this page to not appear every time. How can we find that it already recorded please ?
Thank a lot !
When using the LiveSDK for Andriod, there is an assumption that the user is authenticated at all times when using the application, in order to preserve this flow, we require that you call LiveAuthClient.initialize(...) to renew the user credentials (without any user input) or call LiveAuthClient.login(...) in order to perform an interactive login (User entering username/password).
In the LiveSDK sample apps we see this implemented with a startup activity called SignInActivity.java Here are the relevant excerpts to perform the silent credentials renewal:
protected void onStart() {
super.onStart();
mAuthClient.initialize(Arrays.asList(Config.SCOPES), new LiveAuthListener() {
#Override
public void onAuthError(LiveAuthException exception, Object userState) {
mInitializeDialog.dismiss();
showSignIn();
showToast(exception.getMessage());
}
#Override
public void onAuthComplete(LiveStatus status, LiveConnectSession session, Object userState) {
mInitializeDialog.dismiss();
if (status == LiveStatus.CONNECTED) {
launchMainActivity(session);
} else {
showSignIn();
}
}
});
}
The launchMainActivity() function moves the user into the primary application code, this would be where your application starts in earnest. To see the full details of this sign in activity take a look at the sample app SignInActivity.java in Github

Categories

Resources