I have tested this on my Moto G5+ (works) and Nexus 6 (doesn't work), and my firebase authentication only works on one of them:
mAuth = FirebaseAuth.getInstance();
if(mAuth.getCurrentUser() == null) { //No existing user
mAuth.signInAnonymously().addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.v("myTag", "Cannot authenticate user" + e);
}
});
}
if(mAuth.getCurrentUser() != null) {
Log.v("myTag", "Boutta take photos");
takePhoto(this, 0);//back camera
}else{
Log.v("myTag", "Cannot take photos, user not authenticated");
}
What's odd is that I only get the log Cannot take photos, the user not authenticated, but not the log Cannot authenticate user. This means I am able to authenticate the user, but for some reason, it does not work
How come this only works on some devices?
Assuming your user starts off unauthenticated, once the first if statement is called and it attempts the anonymous sign in, that anonymous sign in happens asynchronously as it is waiting on a callback. So in that state the user isn't authenticated until the call back completes. The code then jumps to your second set of if/else statements where you check
mAuth.getCurrentUser() != null
but the callback for authentication still may have not returned, and your mAuth.getCurrentUser() is still null therefore jumping to the else statement and logging the Log.v("myTag", "Cannot take photos, user not authenticated");
Your callback may then return authenticating the user but at this point, its too late. You already run logic assuming the use wasn't authenticated.
#martinomburajr brought me to the right answer! Just to elaborate on his solution, in case anyone else has the same problem in the future, I needed to wait until the authentication was successful:
mAuth = FirebaseAuth.getInstance();
if(mAuth.getCurrentUser() == null) { //No existing user
Log.v("myTag", "Boutta authenticate");
mAuth.signInAnonymously().addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
}
}).addOnCompleteListener(new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if(mAuth.getCurrentUser() != null) {
//TAKE PHOTO HERE INSTEAD
takePhoto(c, 0);//back camera
}else{
Log.v("myTag", "Cannot take photos, user not authenticated");
}
}
});
}else {
Log.v("myTag", "aalready authenticated!");
takePhoto(c, 0);//back camera
frontOrBack = 0;
}
}
When I put my authentication-dependent code (in this case, to take a picture) I wasn't giving my app enough time to get a response from Firebase. Instead of just assuming that the user was authenticated, however, I fixed this issue by putting the authentication-dependent code in an OnCompleteListener. Many thanks to #martinomburajr for his helpful answer!
Related
Is it a good idea to call Firebase reload() inside OnAuthStateChanged? Initially I would call reload() during app initialization, but it's not guaranteed the FirebaseUser object has been loaded yet. Seems a clean way to do it is call inside OnAuthStateChanged() if the user is not null. The logic being that the returned user profile would match what's cached and no subsequent calls to OnAuthStateChanged will be issued. It works - but I'm a bit worried about a run-away loop for reasons that may not be obvious to me.
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth)
{
final FirebaseUser user = mAuth.getCurrentUser();
//call reload here???
if (user != null)
{
user.reload().addOnFailureListener(new OnFailureListener()
{
#Override
public void onFailure(#NonNull Exception e)
{
if (e instanceof FirebaseAuthInvalidUserException)
{
Log.e(TAG, "INVALID USER EXCEPTION: " + e);
Disconnect();
SignOut();
}
}
});
}
}
I would not expect reload() to do anything significant at all on a newly signed in user. Since onAuthStateChanged indicates that the user has just signed in, the profile information should have just been loaded from the backend service. Reloading at that point isn't likely to be helpful.
Reloading is intended for times when a user has been signed in for a while, and your code is trying to check if something changed with its profile since it was last signed in.
I am making an app where if I delete users from the console user still can access some information from the database. so I reload the user every time users open the apps on the splash screen.
My concern is if it is a good idea or not to reload user every time opening the apps?
this is my current code is,
void reloadUserInfo() {
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null)
user.reload().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
goToHome();
} else {
Helper.customToast(Splash.this, task.getException().getMessage());
goToLogin();
}
}
});
else
goToLogin();
}
I don't see that there is a problem here. If you're observing a specific issue, please post a question that describes what's not working the way you expect.
I have got an activity EmailSignUpActivity, it has two buttons, one to create a user and another one to verify the email. After a user is created, the Verify Email button would be pressed by the user which would send the verification email to the registered email.
What I am doing here is keeping the user to the EmailSignUpActivity till he verifies the email and then send him to the MainActivity. To achieve that I am using the following code:
// sending email verification
emailVerificationButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
progressDialog1.show();
if(mAuth.getCurrentUser() != null) {
if (emailEditText.length() != 0 && passwordEditText.length() != 0 && reEnterPasswordEditText.length() != 0) {
mAuth.getCurrentUser().sendEmailVerification().addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
progressDialog1.dismiss();
Log.d(TAG, "onSuccess: email sent");
Toast.makeText(EmailSignUpActivity.this, "Email verification sent", Toast.LENGTH_SHORT).show();
/**
* Making the app unresponsive
*/
while(!mAuth.getCurrentUser().isEmailVerified()){
mAuth.getCurrentUser().reload();
}
if(mAuth.getCurrentUser().isEmailVerified()){
Toast.makeText(EmailSignUpActivity.this, "Email verified", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(EmailSignUpActivity.this, MainActivity.class);
startActivity(intent);
finish();
}
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
progressDialog1.dismiss();
Log.w(TAG, "onFailure: Email verification failed ==> ", e);
}
});
}else{
Toast.makeText(EmailSignUpActivity.this, "Fill out the details", Toast.LENGTH_SHORT).show();
}
}else{
Toast.makeText(EmailSignUpActivity.this, "Create a user first!", Toast.LENGTH_SHORT).show();
}
}
});
The above code has a while loop after the documentation comments that would run infinitely till the email is verified but this is making the app unresponsive like:
I tried to achieve the same thing with the help of AuthStateListener but failed as AuthStateListener would only get triggered when a user is created, signed in, signed out.
authStateListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
Log.d(TAG, "onAuthStateChanged: method called");
if(firebaseAuth.getCurrentUser() != null)
isEmailVerified = firebaseAuth.getCurrentUser().isEmailVerified();
if(isEmailVerified){
Toast.makeText(EmailSignUpActivity.this, "Email verified", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(EmailSignUpActivity.this, MainActivity.class);
startActivity(intent);
finish();
}
}
};
mAuth.addAuthStateListener(authStateListener);
What I want here is to listen to isEmailVerified() through a listener or any equivalent to that which shouldn't make the app unresponsive. How can I achieve this?
As you've discovered, using a so-called tight infinite loop will stop your app from responding to other user input. So we can easily agree that is a bad idea. If it was suggested that you do so in the Firebase documentation, can you give me a link to that specific piece of the documentation?
You will need to find another event to respond to, to verify that the user has verified their email address. Common options are:
Give the user a UI element to indicate they've verified their email address. This might not be possible in your situation, but it is the most common approach that I know off.
Check whether the email address is verified on startup or when your main activity show. This typically goes into your sign-in flow: sign in the user, check if their email address is verified. If so, you let them into the app. If not, you give them the option to (re)send the verification email. Given that the user will need to toggle to their mail app to get the verification email, they're already toggling out of (and thus back into) your app anyway.
Specify a so-called continue URL in with the verification email. If you use this option to send a Firebase Dynamic Link, you can automatically get the user back into your application where they left off, after they click the verification link in the email (as long as they do so on the same mobile device).
Check the periodically. This is most similar to what you do now, but then without the tight loop. See this question for some options for this: How to run a method every X seconds
I am implementing google smart lock on my app, for now, we are only implementing on the app side, to log in the user automatically once he allows saving the credentials, on a reinstall for example.
But when I removed the password from password.google.com OR when I run the app on a device where the google account doesn't have credentials stored for that app, the library shows a dialog suggesting others sites and apps emails. I need to disable this behavior, I just want to suggest credentials and emails if they belong to my app.
I'm requesting credentials with the following code:
private void requestCredentials() {
CredentialRequest request = new CredentialRequest.Builder()
.setPasswordLoginSupported(true)
.setIdTokenRequested(true)
.build();
mProgressSmartLock.show();
credentialsClient.request(request).addOnCompleteListener(credentialsApiRequestCompleteListener());
}
and the listener:
public OnCompleteListener<CredentialRequestResponse> credentialsApiRequestCompleteListener(){
return new OnCompleteListener<CredentialRequestResponse>() {
#Override
public void onComplete(#NonNull Task<CredentialRequestResponse> task) {
// Successfully read the credential without any user interaction, this
// means there was only a single credential and the user has auto
// sign-in enabled.
mProgressSmartLock.dismiss();
if (task.isSuccessful()) {
processRetrievedCredential(task.getResult().getCredential());
return;
}
// This is most likely the case where the user has multiple saved
// credentials and needs to pick one. This requires showing UI to
// resolve the read request.
Exception e = task.getException();
if (e instanceof ResolvableApiException) {
ResolvableApiException rae = (ResolvableApiException) e;
resolveResult(rae, RC_READ);
return;
}
// This means only a hint is available
if (e instanceof ApiException) {
Crashlytics.logException(e);
}
}
};
}
saving credentials :
private void saveCredentials(String email, String password) {
final Credential credential = new Credential.Builder(email)
.setPassword(password)
.build();
mProgress.show();
credentialsClient.save(credential).addOnCompleteListener(credentialsApiSaveCompleteListener());
}
listener:
public OnCompleteListener<Void> credentialsApiSaveCompleteListener(){
return new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
mProgress.dismiss();
return;
}
Exception e = task.getException();
if (e instanceof ResolvableApiException) {
// The first time a credential is saved, the user is shown UI
// to confirm the action. This requires resolution.
ResolvableApiException rae = (ResolvableApiException) e;
resolveResult(rae, RC_SAVE);
} else {
// Save failure cannot be resolved.
mProgress.dismiss();
}
}
};
}
To avoid this dialog (which lists all email addresses in order to help fill a form, even if there is no passwords saved), do not resolve if the task's getStatusCode() returns SIGN_IN_REQUIRED.
Sorry, this detail was lost in a recent doc change, thanks for reporting. Will get that updated ASAP, sorry for the confusion.
If the credential is not from the app or the app did not save any credential, thestatusCode will be the SIGN_IN_REQUIRED. But if you had saved any credential before, you will receive another INT value from statusCode. You can judge in the Resolveable Exception.
I'm using Firebase SimpleLogin to enable Email / Password authentication. Creation of users and subsequent login is all working fine. However, whenever I leave the app (even if only for a few seconds) the user is never logged in on my return i.e...
authClient.checkAuthStatus(new SimpleLoginAuthenticatedHandler())...
Always returns a null user.
I am not logging out the user via the API. Also I have set the number of days the user is logged in to 21 in the Firebase console.
I have seen mention of a remember-me param in the JS docs, but I can't see any equivalent for Android / Java.
Wondering if I'm missing anything in the docs or if it's not possible for Android?
Thanks for your help,
Neil.
Edit: Added code sample.
User creation....
public void registerUserForChat(final MyApplication application, String email, String password) {
Firebase ref = new Firebase(FIREBASE_URL);
SimpleLogin authClient = new SimpleLogin(ref);
authClient.createUser(email, password, new SimpleLoginAuthenticatedHandler() {
#Override
public void authenticated(com.firebase.simplelogin.enums.Error error, User user) {
if(error != null) {
Log.e(TAG, "Error attempting to create new Firebase User: " + error);
}
else {
Log.d(TAG, "User successfully registered for Firebase");
application.setLoggedIntoChat(true);
}
}
});
}
User login....
public void loginUserForChat(final MyApplication application, String email, String password) {
Log.d(TAG, "Attempting to login Firebase user...");
Firebase ref = new Firebase(FirebaseService.FIREBASE_URL);
final SimpleLogin authClient = new SimpleLogin(ref);
authClient.checkAuthStatus(new SimpleLoginAuthenticatedHandler() {
#Override
public void authenticated(com.firebase.simplelogin.enums.Error error, User user) {
if (error != null) {
Log.d(TAG, "error performing check: " + error);
} else if (user == null) {
Log.d(TAG, "no user logged in. Will login...");
authClient.loginWithEmail(email, password, new SimpleLoginAuthenticatedHandler() {
#Override
public void authenticated(com.firebase.simplelogin.enums.Error error, User user) {
if(error != null) {
if(com.firebase.simplelogin.enums.Error.UserDoesNotExist == error) {
Log.e(TAG, "UserDoesNotExist!");
} else {
Log.e(TAG, "Error attempting to login Firebase User: " + error);
}
}
else {
Log.d(TAG, "User successfully logged into Firebase");
application.setLoggedIntoChat(true);
}
}
});
} else {
Log.d(TAG, "user is logged in");
}
}
});
}
So loginUserForChat method first checks to see if there is a logged in user and, if not, performs the login. Note that every time I start the app, the logging I see is....
Attempting to login Firebase user...
no user logged in. Will login...
User successfully logged into Firebase
If I exit the app, even for a few seconds, and return - I see the same logging.
One thing I noticed is that the call to checkAuthStatus does not take any user credentials - I assume it just checks for any locally logged in user?
Much appreciated.
Another way - try this code in your onCreate:
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null) {
// User is signed in
Intent i = new Intent(LoginActivity.this, MainActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(i);
} else {
// User is signed out
Log.d(TAG, "onAuthStateChanged:signed_out");
}
This will keep the user logged in by taking the user to the Main activity directly without stopping at registration activity. so the user will be logged in unless the user click on signout.
[Engineer at Firebase] In order to transparently handle persistent sessions in the Firebase Simple Login Java client, you need to use the two-argument constructor which accepts an Android context, i.e. SimpleLogin(com.firebase.client.Firebase ref, android.content.Context context) every time you instantiate the Simple Login Java client.
See https://www.firebase.com/docs/java-simple-login-api/javadoc/com/firebase/simplelogin/SimpleLogin.html for the full API reference.
The proper way to do it is to use oAuth authentication:
1. The user logs in.
2. You generate an access token(oAuth2).
3. Android app saves the token locally.
4. Each time the comes back to the auth, he can use the token to to log in, unless the token has been revoked by you, or he changed his
password.
Luckily, firebase has an out of the box support for that, docs:
https://www.firebase.com/docs/security/custom-login.html
https://www.firebase.com/docs/security/authentication.html
You can do this by Using this Approach to escape logi page if User already logged in.
private FirebaseAuth auth;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
auth = FirebaseAuth.getInstance();
if (auth.getCurrentUser() != null) {
startActivity(new Intent(Login_Activity.this, Home.class));
finish();
}
setContentView(R.layout.activity_login_);
for those using Kotlin, to keep the user logged in just add in the onCreate function
if (auth.currentUser != null)
{
startActivity(Intent(this#Login, SellingPageHolderActivity::class.java))
finish()
}