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.
Related
After some back and forth I finally got this to work but I had to use version 0.2.0 because I followed the google guide presented in the Readme.
Anyway, Im struggling with handling what will happen when the oAuth token times out. Then it needs to open the browser again to log in or is there a background process available for this as it automatically redirects back to the app because the server remembers the user so there is no need for a new username/password input?
Im getting a refresh token like this :
if(mAuthService == null){
mAuthService = new AuthorizationService(context);
}
mAuthState.performActionWithFreshTokens(mAuthService, new AuthState.AuthStateAction() {
#Override public void execute(
String accessToken,
String idToken,
AuthorizationException ex) {
if (ex != null) {
return;
}
// Getting the access token...
}
});
Thats working fine but after the user is idle for some time it wont work. How to handle this properly?
Solution for my problem was this:
I changed to using offline_access for the token in the scope. Depending on the site/service you're login into if they accept it or not. For me it was accepted and will keep the user logged in for a long time and removes the need to re-login.
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'
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.
I am currently trying to integrate a live search functionality in android. I use a customized Autocomplete widget. The widget itself provides me with a threshold to only start a query after a certain amount of characters have been typed in. But what I also want is that a query only starts, say if a user has stopped typing for 2 seconds.
As I load my contents with a AsyncTask I tried blocking the doInBackground function by calling Thread.sleep() right at the beginning. If the user would then continue typing the program would look after an existing task, cancel it and start a new one. This was my idea. But it doesn't quite work the way I expected. Sometimes it sends out queries even if the user is still typing, sometimes queries are canceled after the users stopped typing.
Do you have any ideas on this or maybe a better way to solve this?
Here are the code snippets:
1. The function that is called on text changes:
public void afterTextChanged(Editable s) {
if(mWidget.enoughToFilter()) {
if(mTask != null && mTask.getStatus() != Status.FINISHED) {
mTask.cancel(true);
}
mTask = new KeywordSearchLoader(mActivity,
mItems);
mTask.execute(s.toString());
}
}
2. doInBrackground
try {
Thread.sleep(Properties.ASYNC_SEARCH_DELAY);
} catch (InterruptedException e) {
Log.e(TAG, "the process was interrupted while sleeping");
Log.w(TAG, e);
}
Thanks
Phil
Create a Handler and use .postDelayed(..) to run a background task after some delay.
If user presses the key then call handler.removeCallback(..) and then again .postDelayed(..) to add a new delayed callback.