【问题标题】:Problems with Firestore connections executing threadsFirestore 连接执行线程的问题
【发布时间】:2019-02-03 01:18:24
【问题描述】:

首先,如果标题具有误导性,我想道歉。英语不是我的母语,我不知道如何命名这篇文章。现在的问题:

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

当这个活动开始时,我做了一个 Firebase google auth 来获取用户,然后问题就来了。我需要知道用户是否在数据库中有一个带有附加数据的链接文档(现有用户),或者他是否需要创建一个(新用户)。我创建了一个方法来检查是否存在名为用户 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来创建文档,或者我是否可以在当前的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 的值,然后继续使用另一个方法,但我得到了

从内部类中访问的变量需要声明为final

错误。我尝试按照 Android Studio 的建议将变量设置为 final,但由于显而易见的原因,我无法使用它。

提前致谢。

【问题讨论】:

    标签: java android firebase google-cloud-firestore


    【解决方案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");
    

    当你运行这段代码时,它会打印出来

    附加监听器之前

    附加监听器后

    得到文件

    这可能不是你所期望的,但它准确地解释了为什么aaupdateUI 检查时未被修改。该文档尚未从 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();
        }
      });
    }
    

    如您所见,我们的 UserExistsCallback 与 Firestore 本身的 OnCompleteListener 非常相似,只是更符合我们的需求。

    这个问题经常出现,所以我建议花一些时间来了解更多。见:

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        【解决方案3】:

        您猜对了,当在onComplete() 方法之外使用aa 变量的值时,数据还没有从数据库中完成加载,这就是它无法访问的原因。所以这个变量会保留false的初始值。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-11-14
          • 1970-01-01
          • 1970-01-01
          • 2023-03-28
          • 2023-03-26
          • 2021-05-19
          • 2021-12-24
          • 2017-10-13
          相关资源
          最近更新 更多