0

I'm working on an Android app that does the following:

  1. Upon app start-up, it checks if a user is logged in, using AuthStateListener.
  2. If there is a user logged in, it retrieves data from Firestore. The user data is stored in a document that I named with the following nomenclature: "User " + user's_email_ID. For example, if a user has an email ID xyz@gmail.com, his data will be stored in the document named: User xyz@gmail.com. All documents are within the collection named "Users".
  3. If all the fields are null/ empty in the user's data document, the app opens an Activity that asks him/her to fill all the details. Else, it takes the user to the main page (StudentMainActivity if the user is a student, or ProfessorMainActivity if the user is a professor).

Coming to my problem:

The block of code which checks whether the fields are empty has some erratic and unpredictable behavior. I'm not sure if this is a problem based on Firestore, or on the fact that data retrieval happens on a different thread.

I checked the Firestore database and saw that all fields were filled. However, when a user (who's already logged in) starts the app, the app knows that it is the same user (i.e. he's not prompted to sign in, because AuthStateListener does its job), but instead of being redirected to either StudentMainActivity or ProfessorMainActivity (the main screens), he's asked to fill his details again.

What's more confusing is that this bug doesn't always occur. There are times when the app does what is expected, i.e. take the user to the main screen, but the next time he starts the app, he's again taken to the activity that asks him to enter his details.

Source Code:

LoginActivity.java (Only the relevant parts)

    //AuthStateListener is in onCreate
    authStateListener = new FirebaseAuth.AuthStateListener() {
        @Override
        public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
            FirebaseUser user = firebaseAuth.getCurrentUser();
            if (user != null){
                UIDEmailID = user.getEmail();
                updateUI(user);
            }
            else{
                updateUI(null);
            }
        }
    };

private void updateUI(FirebaseUser user){
    // Update UI after login
    if (user != null) {
        Toast.makeText(LoginActivity.this, "User " + UIDEmailID, Toast.LENGTH_LONG).show();
        db.collection("Users").document("User " + UIDEmailID).get()
                .addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
                    @Override
                    public void onSuccess(DocumentSnapshot documentSnapshot) {
                        if (documentSnapshot.get("department") != null ||       // if any
                        documentSnapshot.get("phoneNumber") != null ||          // field in
                        documentSnapshot.get("name") != null ||                 // Firestore is
                        documentSnapshot.get("studentSemester") != null ||      // non-null then
                        documentSnapshot.get("dateOfBirth") != null ||          // proceed to
                        documentSnapshot.get("university") != null) {           // further activities
                            if (documentSnapshot.get("userType") == "Lecturer/ Professor") {
                                Intent intent = new Intent(LoginActivity.this, ProfessorMainActivity.class);
                                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
                                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                startActivity(intent);
                            }
                            else {
                                Intent intent = new Intent(LoginActivity.this, StudentMainActivity.class);
                                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
                                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                startActivity(intent);
                            }
                        } else {
                            Toast.makeText(LoginActivity.this, "We need some additional details before we go ahead.", Toast.LENGTH_SHORT).show();
                            Intent intent = new Intent(LoginActivity.this, GFBDetailsActivity.class);
                            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            startActivity(intent);
                        }
                    }
                }).addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        Toast.makeText(LoginActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
                    }
                });
    }

}

I'm sorry for the long question; I just tried to make it super descriptive. Some help would be greatly appreciated.

P.S. The reason I think this is a problem involving the usage of multiple threads is because whenever the app runs as expected (i.e. takes the user to the main screen), the toast "We need some additional details before we go ahead." appears as well. If you look at the code (the last "else" block) you will realise that it is in a seperate conditional block altogether, and thus isn't even supposed to show up if the main screen (which is in another conditional block) shows up.

EDIT 1:

I'm enclosing screenshots pertaining to the problem. Ignore the bland UI :P

This is what's expected (Comes under the second 'else' block). It is supposed to show up only if the user is logging in for the first time, i.e. does not have his data stored in a Firestore document.

The background is StudentMainActivity (inside the nested 'else'). However, even the Toast is displayed (it belongs to a seperate block altogether).

Yash Umale
  • 53
  • 6
  • 1
    Are you able to reach to the first if of the method onSucess()? Or it directly jumps to the else? Please do a print before if of the variable user and share what the variable user has? – Nibrass H Jul 15 '20 at 07:31
  • It's unpredictable; most of the time it directly goes to "else", and sometimes if everything goes well it'll go to the "if". I'm also given to believe that it goes to the "else", and somehow opens up the activity that's within the "if" block. I know this because the StudentMainActivity opens up, but the toast "We need some additional details before we go ahead." also gets displayed. Also, I printed the value of the User variable. It is nothing but a randomly generated FirebaseAuth Instance of the form "com.google.firebase.auth.internal.xxx@12345". – Yash Umale Jul 15 '20 at 08:45
  • 1
    Looking at your code, I found a possible code mistake, to get your current user data, you have to use the following code: FirebaseUser currentUser = FirebaseAuth.getInstance().getCurrentUser(); Could you please put the previous code and try again if you are still getting the same issue? Please let me know if it worked. – Nibrass H Jul 15 '20 at 19:20
  • I tried as you suggested, but it still has the problem. The variable "firebaseAuth" is basically FirebaseAuth.getInstance().getCurrentUser(); I just forgot to add that bit of code there. Nevertheless, I tried whatever you asked and it didn't work out :( – Yash Umale Jul 16 '20 at 10:53
  • 1
    I have analyzed your code and it seems to be correct, for further troubleshooting could you please System.out.prinln(documentSnapshot) before the first if in the onSucess method to see if the data from firestore is been retrieved correctly? Could you also please share your manifest.xml file and also could you please share the files (code) for your Student, Professor and GFBDetailsActivity ? It seems an issue with the activities opening up wrong. – Nibrass H Jul 16 '20 at 20:55
  • I have made some relevant edits to the question to help you understand the problem better. Please do check them out. Also, I think you can look at SplashScreenActivity instead of LoginActivity on GitHub. It is the same thing, but cleaner and contains only the code relevant to the problem, whereas LoginActivity has a ton of other messy stuff as well. – Yash Umale Jul 17 '20 at 05:40
  • 1
    Thanks for sharing your code, I have been looking at your code. I think I am able to find the issue. Maybe you are querying wrong that's why documentSnapshot is empty and nothing shows up. Could you please tell me the structure of your firestore, I mean here I would like to see how are your documents ID named? The query is empty because maybe UIDEmailID is empty, please make a toast or print in onAuthStateChanged, the UIDEmailID and look what is it? – Nibrass H Jul 17 '20 at 22:01
  • 1
    You can also query a document as following: db.collection(COLLECTION_NAME) .whereEqualTo(DOCUMENT_ID, documentId) .get() .addOnCompleteListener(task -> { if (task.isSuccessful()) { if (task.getResult().getDocuments().size() > 0) // Here is your document with id } }); – Nibrass H Jul 17 '20 at 22:02
  • 1
    Please share the previous asked to look further to solve your issue as soon as possible as I think it can be solved quickly because it seems like a code error. And regarding the Toast appearing always is a very common Issue with Android, because Toast are treated in another thread so even though your app crashes the toast will be there until you close or you cancel it. – Nibrass H Jul 17 '20 at 22:05
  • 1
    You can cancel your toast by using the cancel. [Here](https://stackoverflow.com/a/30602105) is an example of how we can do that. – Nibrass H Jul 17 '20 at 22:09
  • 1
    Thanks for sharing the code on GitHub which was really helpful. Looking forward to your reply to look further on your issue. – Nibrass H Jul 17 '20 at 22:09
  • My Firestore structure is as follows: Collection name - Users Inside the Users collection, each FirebaseUser is allotted one document each. The document is named as "User " + firebaseAuth.getCurrentUser().getEmail(). So if I am a user with my email ID: xyz@gmail.com, my document will be called "User xyz@gmail.com". Stored in path: "Users/ User xyz@gmail.com" – Yash Umale Jul 18 '20 at 08:15
  • I checked UIDEmailID for all possible paths that the code can take, and UIDEmailID is always up to date with the user's email ID. I'll now try the syntax you've mentioned to query the doc. – Yash Umale Jul 18 '20 at 08:18
  • Unfortunately, still no luck. I'd totally understand if you chose to drop the question at this point, but if in case you're really inclined to solve the problem then please feel free to fork my GitHub repo and send a pull request if you find a solution. Many thanks! P.S. I don't mind if you wanna continue here too. – Yash Umale Jul 18 '20 at 14:33
  • 1
    Hey, it worked! I gave it a try again today with your syntax for querying and also double-checked the code; turns out I had updated UIDEmailID everywhere, but not in onStart(). Now it runs as I want it to. Thank you! – Yash Umale Jul 19 '20 at 09:16
  • 1
    Oh great, as this is out of weekdays, I was not able to answer you, sorry, I am happy that now it's working correctly. Do you want any further help? – Nibrass H Jul 19 '20 at 20:55
  • No, I guess that's the only major problem I was facing. Cannot thank you enough :D – Yash Umale Jul 20 '20 at 03:48
  • 1
    Hello Yash could you please kindly post the solution of this issue as an answer? – Chris32 Jul 20 '20 at 10:52
  • Yeah sure, coming right up. – Yash Umale Jul 20 '20 at 10:56

1 Answers1

1

So it turns out Firestore wasn't (entirely) at fault.

Every activity in an Android application has a life span, and every time an activity is run, it goes through an elaborate sequence of lifecycle functions.

An activity's lifecycle is as follows:

Launched --> onCreate() --> onStart() --> onResume() --> Running --> onPause() --> onStop() --> onDestroy() --> Finished

I won't be digressing by going into the details of each function, because the function names are quite intuitive and self-explanatory.

As you can see in the code snippet in the question, onAuthStateChanged() is inside onCreate(). My Document ID on Firebase is of the form "User UIDEmailID", where UIDEmailID is the email ID of the user. And UIDEmailID gets updated only in onAuthStateChanged() (which, in turn, is inside onCreate()), i.e. only when the activity starts afresh, after the app has been closed and opened again.

Therefore, I updated UIDEmailID in onStart() as well, which means that every time an app is resumed, it will retrieve the email ID of the user, which can subsequently be used to retrieve the document from Firestore.

Also, I slightly tweaked my Firestore data retrieval bit of code upon advice from Nibrass H. The solution is as follows:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    running = true;
    if (savedInstanceState != null){
        running = savedInstanceState.getBoolean("running");
        wasrunning = savedInstanceState.getBoolean("wasrunning");
    }

    setContentView(R.layout.splash_screen);

    firebaseAuth = FirebaseAuth.getInstance();
    db = FirebaseFirestore.getInstance();

    authStateListener = new FirebaseAuth.AuthStateListener() {
        @Override
        public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth1) {
            FirebaseUser user = firebaseAuth1.getCurrentUser();
            if (user != null){
                UIDEmailID = user.getEmail();
                updateUI(user);
            } else {
                updateUI(null);
            }
        }
    };
}

@Override
protected void onStart() {
    super.onStart();
    firebaseAuth.addAuthStateListener(authStateListener);
    if (firebaseAuth.getCurrentUser() != null) {
        UIDEmailID = firebaseAuth.getCurrentUser().getEmail();
        updateUI(firebaseAuth.getCurrentUser());
    } else {
        updateUI(null);
    }
}

@Override
protected void onRestart() {
    super.onRestart();
    authStateListener = new FirebaseAuth.AuthStateListener() {
        @Override
        public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth1) {
            FirebaseUser user = firebaseAuth1.getCurrentUser();
            if (user != null) {
                UIDEmailID = user.getEmail();
                updateUI(user);
            } else {
                updateUI(null);
            }
        }
    };
}

@Override
protected void onPause() {
    super.onPause();
    wasrunning = running;
    running = false;
}

@Override
protected void onResume() {
    super.onResume();
    if (wasrunning){
        running = true;
    }
}

@Override
protected void onStop() {
    super.onStop();
    if (authStateListener != null) {
        firebaseAuth.removeAuthStateListener(authStateListener);
    }
}

private void updateUI(FirebaseUser firebaseUser){
    if (firebaseUser != null){
        Toast.makeText(this, "User " + firebaseUser.getEmail(), Toast.LENGTH_SHORT).show();
        db.collection("Users").document("User " + UIDEmailID).get()
                .addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
                    @Override
                    public void onSuccess(DocumentSnapshot documentSnapshot) {
                        if (documentSnapshot.get("userType") != null) {
                            if (documentSnapshot.get("userType").equals("Lecturer/ Professor")){
                                Intent intent = new Intent(SplashScreenActivity.this, ProfessorMainActivity.class);
                                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
                                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                finish();
                                startActivity(intent);
                            } else {
                                Intent intent = new Intent(SplashScreenActivity.this, StudentMainActivity.class);
                                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
                                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                finish();
                                startActivity(intent);
                            }
                        } else {
                            Toast.makeText(SplashScreenActivity.this, "We need some additional details before we go ahead.", Toast.LENGTH_SHORT).show();
                            Intent intent = new Intent(SplashScreenActivity.this, GFBDetailsActivity.class);
                            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            finish();
                            startActivity(intent);
                        }
                    }
                });
    }
}
Yash Umale
  • 53
  • 6