Problems with Firestore connections executing thre

2020-04-01 01:10发布

First of all, I would like to apologize if the title is misleading. English is not my native language and I wasn't sure how to name this post. Now the question:

I have an Activity that shows the data stored in a Firebase project about a user. The data is distributed between the Firebase user (display name, email and profile image) and a document in Cloud Firestore named as the user's UID.

When this activity starts, I make a Firebase google auth to get the user, and then the problems come. I need to know if the user has a linked document in the database with his additional data (existing user) or if he needs to create one (new user). I have created a method that checks if a document named like the user's UID exists. This is the method:

public void userExists(String uid) {
    FirebaseFirestore db = FirebaseFirestore.getInstance();
    DocumentReference docRef = db.collection("users").document(uid);
    docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
        @Override
        public void onComplete(@NonNull Task<DocumentSnapshot> task) {
            if (task.isSuccessful()) {
                DocumentSnapshot documentSnapshot = task.getResult();
                if (documentSnapshot.exists()) {
                    aa = true;
                    aa=true;
                } else {
                    aa = false;
                }
            } else {
                aa = false;
            }
        }
    });
}

(aa is a boolean variable declared in the Activity).

I call this method inside the following one in order to know if I need to start a new Activity to create the document or if I can show the user's data in the current Activity without problems.

private void updateUI(FirebaseUser user) {

    if (user != null) {
        userExists(user.getUid());
        if(aa){
            //Fill layout with the user data and the user linked document data

            //USER DATA
            txvNombre=findViewById(R.id.nombrePerfil);
            txvNombre.setText(user.getDisplayName());
            imvAvatar=findViewById(R.id.imvVistaPerfilAvatar);
            Picasso.with(VistaPerfilActivity.this)
                    .load(user.getPhotoUrl())
                    .resize(500,500)
                    .centerCrop()
                    .into(imvAvatar);


            //HERE GOES THE DOCUMENT DATA


        }else{

        }
    } else {
        finish();
    }
}

As far as I know, Firestore connections are made in a new Thread so, when UpdateUI(FirebaseUser user) starts, aa is always false, because userExists(String uid) hasn't finished yet. userExists(String uid) works correctly, I have checked it.

So I need to know how to check if the Firestore connection thread is finished, in order to continue executing the app. I have tried using the OnCompleteListener (shown in the code), but it doesn't work. I've also tried to just write the actions in the userExists(String uid) method instead of just changing the value of aa and then continue on another method, but I get the

variable is accessed from within inner class needs to be declared final

error. I tried to follow the Android Studio advice of making the variable final, but I can't work with that for obvious reasons.

Thanks in advance.

3条回答
欢心
2楼-- · 2020-04-01 01:26

Regarding the final reference, if the variable belongs to a class rather than being declared in the method, it need not be declared final.

查看更多
SAY GOODBYE
3楼-- · 2020-04-01 01:30

The problem is not so much caused by multi-thread, as by the fact that data is loaded from Firebase asynchronously. By the time your updateUI function looks at the value of aa, the onComplete hasn't run yet.

This is easiest to see by placing a few well placed logging statements:

System.out.println("Before attaching listener");
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DocumentSnapshot> task) {
        System.out.println("Got document");
    }
});
System.out.println("After attaching listener");

When you run this code it prints

Before attaching listener

After attaching listener

Got document

This is probably not what you expected, but it explains precisely why aa is unmodified when updateUI checks it. The document hasn't been read from Firestore yet, so onComplete hasn't run yet.

The solution for this is to move all code that requires data from the database into the onComplete method. The simplest way in your case is:

public void userExists(String uid) {
  FirebaseFirestore db = FirebaseFirestore.getInstance();
  DocumentReference docRef = db.collection("users").document(uid);
  docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DocumentSnapshot> task) {
        if (task.isSuccessful()) {
            DocumentSnapshot documentSnapshot = task.getResult();
            if (documentSnapshot.exists()) {
                //Fill layout with the user data and the user linked document data

                //USER DATA
                txvNombre=findViewById(R.id.nombrePerfil);
                txvNombre.setText(user.getDisplayName());
                imvAvatar=findViewById(R.id.imvVistaPerfilAvatar);
                Picasso.with(VistaPerfilActivity.this)
                        .load(user.getPhotoUrl())
                        .resize(500,500)
                        .centerCrop()
                        .into(imvAvatar);


                //HERE GOES THE DOCUMENT DATA

            }
        }
    }
  });
}

Now your code that needs the document only runs after the document is actually available. This will work, but it does make the userExists function a bit less reusable. If you want to fix that, you can pass a callback into userExists that you then call after the document is loaded.

public interface UserExistsCallback {
  void onCallback(boolean isExisting);
}

And use that in userExists as:

public void userExists(String uid, final UserExistsCallback callback) {
  FirebaseFirestore db = FirebaseFirestore.getInstance();
  DocumentReference docRef = db.collection("users").document(uid);
  docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DocumentSnapshot> task) {
        boolean userExists = false;
        if (task.isSuccessful()) {
            DocumentSnapshot documentSnapshot = task.getResult();
            userExists = documentSnapshot.exists();
        }
        callback.onCallback(userExists);
    }
  });
}

And then invoke that from updateUI with:

if (user != null) {
  userExists(user.getUid(), new UserExistsCallback() {
    public void onCallback(boolean isExisting) {
      if(isExisting){
        //Fill layout with the user data and the user linked document data

        //USER DATA
        txvNombre=findViewById(R.id.nombrePerfil);
        txvNombre.setText(user.getDisplayName());
        imvAvatar=findViewById(R.id.imvVistaPerfilAvatar);
        Picasso.with(VistaPerfilActivity.this)
                .load(user.getPhotoUrl())
                .resize(500,500)
                .centerCrop()
                .into(imvAvatar);


        //HERE GOES THE DOCUMENT DATA


      }else{

      }
    } else {
      finish();
    }
  });
}

As you can see our `` callback is quite similar to the OnCompleteListener of Firestore itself, it's just a bit more tailored to our needs.

This problem pops up a lot, so I recommend spending some time learning more about it. See:

查看更多
虎瘦雄心在
4楼-- · 2020-04-01 01:37

You're right guessing that, when using the value of your aa variable outside the onComplete() method, the data hasn't finished loading yet from the database and that's why it's not accessible. So this variable will aways hold the initial value of false.

So in order to solve this, you need to wait for it. This can be done in two ways. The first solution would be a very quick solution that implies you to use the value of your aa variable only inside the onComplete() method and it will work perfectly fine. The second one is, if you want to use it outside the onComplete() method, I recommend you to you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback. You can also take a look at this video for a better understanding.

查看更多
登录 后发表回答