Data Retrieval from Firestore is erratic

Tags: , , , ,



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 😛

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).

Answer

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);
                        }
                    }
                });
    }
}


Source: stackoverflow