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.
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.
When i login into my app, it works fine and retrieve data from firebase. But when i close the app and open it later the app don't retrieve it. I need to relogin to get that functioning again. Please help.
You need to listen to onAuthStateChanged listener to detect when the user is ready:
authStateListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
if(firebaseAuth.getCurrentUser() != null) {
// User signed in.
} else {
// User signed out.
}
}
};
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
}
Is it necessary to add an AuthStateListener in every activity of Android Firebase?
I have added AuthStateListener in login activity.I want to know that is it enough?
I don't think it's necessary. You need to have it in the activity that have to to the sign-in environment setup, or sign out environment clenup in a way like that:
mAuthStateListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull final FirebaseAuth firebaseAuth) {
FirebaseUser user = firebaseAuth.getCurrentUser();
if (null != user) {
onSignedInInitialize(user);
} else {
onSignedOutCleanup();
}
}
};
This is usually done by the activity that manage the sign-in process either through FirebaseUI or with the only SDK API.
Remember to do the user reload() in the signin init function so that to manage situations like the case you have deleted a user from the console, the application didn't know that because of caching:
private void onSignedInInitialize(FirebaseUser user) {
user.reload();
if (null != user) {
[...]
You need to have a listener in all the other Activities where you need to respond to events related to the user sign-in, sign-out or updates.
In all other activities all you have to check is only if the user is not null.
if (null != user) {
Where user is the user taken from the the FirebaseAuth instance
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
Usually where you use the listener you create it in the onCreate activity method and you add it in the onResume and remove it in the onPause
#Override
protected void onResume() {
super.onResume();
if (null != mAuthStateListener) {
mFirebaseAuth.addAuthStateListener(mAuthStateListener);
}
}
#Override
protected void onPause() {
super.onPause();
if (null != mAuthStateListener) {
mFirebaseAuth.removeAuthStateListener(mAuthStateListener);
}
}
It really depends on what you want to monitor. According to the docs you'll use this listener to listen to events on:
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
In the activities you need to know those things you should register the listener. But in my experience you'll need register it only in the login activity.
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)
}