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.
Related
I've a weird behavior in my app related to the firebase database.
I got some unexpected access denied when trying to perform some stuff at database... That could be bug in my code but is a pretty simple code.
I want to ilustrate the sitation with a real scenario, please read the requirements
1- i've only 1 app accessing the database (android)
2- i've only 1 method in the whole app trying to access the specific node which is causing deny of access
3- in my firebase rules the only rule applied to this node is:
".read":"auth != null",
".write":"auth != null"
4- i DO explicity check FirebaseAuth.getInstance().getCurrentUser() != null right before calling the method
this is a pseudo snippet of how my code is (i wont pust the real code simple because is too long)
public void onResume() {
if(FirebaseAuth.getInstance().getCurrentUser() == null)
doLogin();
else
checkFirebaseStuff();
}
As google doesn't provide any info about why the access was rejected the only possible explanation i found is:
the user was authenticated but long time before, so when it checked on if was still valid, but short time later when the function really ran its token (or some other firebase auth check) was no longer valid, so it caused the access denied
this error doesn't happen a lot, i have 5k daily users and it happens around 20 or 50 times a day, but still shouldn't happen even once
does it make sense? can anyone help me with any aditional info?
Hmm, I think you can fix this by checking if the user is disconnected from the firebase database, but I'm not really sure if that will affect the Auth too, you can give it a try
DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected");
connectedRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
boolean connected = snapshot.getValue(Boolean.class);
if (connected) {
Log.d(TAG, "connected");
//Here you can update your mAuth state
} else {
Log.d(TAG, "not connected");
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
Log.w(TAG, "Listener was cancelled");
}
});
I get old (previously registered) user Uid in onAuthStateChanged after delete firebase account and then immediately creating a new one. But it is happen not every time, and I don't know what it is depends of. Any suggestions?
Also FirebaseAuth.getInstance().getCurrentUser() is null into this callback, which is strange too.
Might deletion process depends of completion of functions.auth.user().onDelete in Firebase Functions, but I'm not sure.
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
// re-auth need for delete account flow
if (reAuthFirebase){
Log.d(TAG, "onAuthStateChanged reAuthFirebase");
reAuthFirebase = false;
return;
}
// ignore 1st callback http://stackoverflow.com/a/40436769/1621111
if (isFirstAuthCallback){
Log.d(TAG, "onAuthStateChanged subscribe 1st callback");
isFirstAuthCallback = false;
return;
}
String firebaseUserUid = firebaseAuth.getUid(); // getting old Uid
}
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!
I'm starting a new Android project and decided to use Kotlin and Firebase within, right now I'm able to create users successfully using createUserWithEmailAndPassword on my SignupActivity and my users are logged in successfully when createUserWithEmailAndPassword is finished.
Now I'm trying to get it further using the callback event that is triggered on FirebaseAuth.AuthStateListener using onAuthStateChanged(FirebaseAuth auth) but the listener that I'm creating inside my onCreate(savedInstanceState: Bundle?)function isn't get triggered and my lack of experience converting Java code to Kotlin isn't helping me to identify the root problem.
I have some Java example code to base on that goes like this:
Java example
onCreate(...//
mAuthListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
FirebaseUser user = firebaseAuth.getCurrentUser();
if (user != null) {
// User is signed in
// NOTE: this Activity should get onpen only when the user is not signed in, otherwise
// the user will receive another verification email.
sendVerificationEmail();
} else {
// User is signed out
}
// ...
}
};
My Kotlin code
FirebaseAuth.AuthStateListener { auth ->
val user = auth.currentUser
if(user != null){
// User is signed in
Log.d(TAG, "Signed in")
Toast.makeText(this, "User", Toast.LENGTH_LONG).show();
sendVerificationEmail()
}else{
// User is signed out
Log.d(TAG, "Signed out")
Toast.makeText(this, "Null", Toast.LENGTH_LONG).show();
}
}
I put some log and toast elements for debugging purpose but neither of them are getting triggered, I'm thinking that the onAuthStateChanged is missing inside the FirebaseAuth.AuthStateListener but I don't know how to fix it.
If anyone can give me some advice on what I'm doing wrong it'll be much appreciate.
Thanks in advance.
This is what has helped me, pay attention on the parenthesis when calling addAuthStateListener - being new to kotlin I used { } curly ones:
public override fun onStart() {
super.onStart()
firebaseAuth.addAuthStateListener(authStateListener)
}
public override fun onPause() {
super.onPause()
firebaseAuth.removeAuthStateListener(authStateListener)
}
I've start working with new Firebase SDK.
When I'm doing user login, I'm onAuthStateChanged method is being called twice with same state (etc. user sign in).
I'm sure I'm adding the AuthStateListener only once to the FirebaseAuth reference.
Any help?
Yes, and this is very annoying. This is due a registration call. Not only that, onAuthStateChanged is going to be called many times in many different states, with no possibility of knowing which state it is.
Documentation says:
onAuthStateChanged(FirebaseAuth auth)
This method gets invoked in the UI thread on changes in the authentication state:
Right after the listener has been registered
When a user is signed in
When the current user is signed out
When the current user changes
When there is a change in the current user's token
Here some tips to discover the current state:
Registration call: skip the first call with a flag.
User signed in: user from parameter is != null.
User signed out: user from parameter is == null.
Current user changes: user from parameter is != null and last user id is != user id from parameter
User token refresh: user from parameter is != null and last user id is == user id from parameter
This listener is a mess and very bugprone. Firebase team should look into it.
While the other answers provided here might do the job, I find managing a flag cumbersome and error-prone.
I prefer debouncing the event within short periods of time. It is very unlikely, maybe even impossible, for a user to login then logout within a period of 200ms let's say.
TLDR
Debouncing means that before handling an event, you wait to see if the same event is gonna fire again within a predefined period of time.
If it did, you reset the timer and wait again.
If it didn't, you handle the event.
This is an Android question, which is not my field, but I'm sure android provides some kind of tool that can help with the task.
If not, you can make one using a simple timer.
Here's how a Javascript implementation might look like:
var debounceTimeout;
const DebounceDueTime = 200; // 200ms
function onAuthStateChanged(auth)
{
if (debounceTimeout)
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(() =>
{
debounceTimeout = null;
handleAuthStateChanged(auth);
}, DebounceDueTime);
}
function handleAuthStateChanged(auth)
{
// ... process event
}
My workaround is to use a Boolean declared globally to flag if onAuthStateChanged has need called before.
private Boolean authFlag = false;
mAuthListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull final FirebaseAuth firebaseAuth) {
if (firebaseAuth.getCurrentUser() != null) {
if(authFlag== false) {
// Task to perform once
authFlag=true;
}
}
}
};
Usually I want to setup the UI before adding the listener and repeat the setup any time the auth state changes (avoiding the initial double call). My solution is to enhance the boolean flag solution and keep track of the uid (not the token) of the last user, which may be null.
private FirebaseAuth firebaseAuth;
private String lastUid; // keeps track of login status and changes thereof
In onCreate, I get the auth instance and set the UI accordingly, before adding the listener in onStart
#Override
protected void onCreate(Bundle savedInstanceState){
...
firebaseAuth = FirebaseAuth.getInstance();
getUserSetUI();
...
}
where getUserSetUI sets lastUid according to the auth instance
private void getUserSetUI(){
lastUid = (firebaseAuth == null || firebaseAuth.getCurrentUser() == null) ?
null : firebaseAuth.getUid();
setUI(!(lastUid == null));
}
The listener checks to see if the state has actually changed
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth auth){
String uid = auth.getUid(); // could be null
if( (uid == null && lastUid != null) || // loggedout
(uid != null && lastUid == null) || // loggedin
(uid != null && lastUid != null && // switched accounts (unlikely)
!uid.equals(lastUid))){
getUserSetUI();
}
}
if (authStateListener == null) {
authStateListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
if (firebaseAuth.getCurrentUser() == null) {
//Do anything here which needs to be done after signout is complete
FirebaseAuth.getInstance().removeAuthStateListener(this);
Log.d(TAG_, "logout");
finish();
} else {
}
}
};
}
FirebaseAuth.getInstance().removeAuthStateListener(this) should be called
try{
FirebaseAuth.getInstance().signOut();
FirebaseInstanceId.getInstance().deleteInstanceId();
}catch (Exception ex){
ex.printStackTrace();
}
When user logout from gmail then the user should also be logout from firebase. This is how I resolved this issue.