Firestore执行线程的连接问题

时间:2018-08-28 11:57:37

标签: java android firebase google-cloud-firestore

首先,如果标题误导我,我深表歉意。英语不是我的母语,所以我不确定该如何命名。现在的问题:

我有一个活动,可显示存储在Firebase项目中有关用户的数据。数据在Firebase用户(显示名称,电子邮件和个人资料图像)和Cloud Firestore中名为用户UID的文档之间分配。

此活动开始时,我进行了Firebase谷歌身份验证以获取用户,然后出现了问题。我需要知道用户是否在数据库中有一个链接文档及其附加数据(现有用户),或者是否需要创建一个链接文档(新用户)。我创建了一种方法来检查是否存在像用户的UID这样的名称的文档。这是方法:

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是在活动中声明的布尔变量)。

我在下一个方法中调用此方法,以了解是否需要启动新的Activity来创建文档,或者是否可以在当前Activity中显示用户的数据而没有问题。

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

据我所知,Firestore连接是在新线程中建立的,因此,当UpdateUI(FirebaseUser user)启动时,aa始终为false,因为userExists(String uid)尚未完成。 userExists(String uid)正常工作,我已经检查了它。

所以我需要知道如何检查Firestore连接线程是否完成,以便继续执行该应用程序。我已经尝试使用OnCompleteListener(如代码所示),但是它不起作用。我还尝试过在userExists(String uid)方法中编写操作,而不是仅更改aa的值,然后继续使用另一种方法,但是我得到了

  从内部类内部访问

变量需要声明为最终变量

错误。我尝试遵循Android Studio的建议,将变量定为final,但是出于明显的原因,我无法使用它。

谢谢。

3 个答案:

答案 0 :(得分:1)

问题不是由多线程引起的,而是由于从Firebase异步加载数据这一事实。到您的updateUI函数查看aa的值时,onComplete尚未运行。

最简单的方法是放置一些放置良好的日志记录语句:

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

运行此代码时,它将打印

  

在附加侦听器之前

     

附加监听器后

     

获得文档

这可能不是您所期望的,但恰恰说明了aa检查updateUI时为什么未修改的原因。尚未从Firestore中读取该文档,因此onComplete尚未运行。

解决方案是将所有需要数据的代码从数据库移入onComplete方法中。这种情况下最简单的方法是:

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

            }
        }
    }
  });
}

现在,需要文档的代码仅在文档实际可用后才能运行。这将起作用,但是确实会使userExists函数的可重用性降低。如果要解决此问题,可以将回调传入 userExists,然后在加载文档后调用该回调。

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

并在userExists中将其用作:

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

然后使用以下命令从updateUI中调用它:

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

您可以看到我们的``回调与Firestore本身的OnCompleteListener非常相似,它只是更适合我们的需求。

这个问题经常出现,所以我建议花一些时间来学习更多有关它的信息。参见:

答案 1 :(得分:0)

关于最终引用,如果变量属于类而不是在方法中声明,则无需声明为最终变量。

答案 2 :(得分:0)

您是正确的猜测,当在aa方法之外使用onComplete()变量的值时,数据尚未从数据库中加载完毕,因此无法访问。因此,此变量将保留false的初始值。

因此,为了解决此问题,您需要等待。这可以通过两种方式完成。第一个解决方案将是一个非常快速的解决方案,它意味着您仅在aa方法内使用onComplete()变量的值,并且可以很好地工作。第二个是,如果您想在onComplete()方法之外使用它,我建议您从我拥有的 post 中查看我的答案的最后一部分。解释了如何使用自定义回调来完成。您也可以查看此 video 以获得更好的理解。