java - Android - 如何等待 SnapshotListener 完成?

标签 java android multithreading firebase google-cloud-firestore

我在 Android 应用中使用 Firestore。我创建了一个 User 类,它代表 Firestore 中 users 集合下的一个文档。这是类(class):

public class User
{
    private String documentID;
    private String email;
    private String firstName;
    private String lastName;
    private String mobilePhone;

    private static final String USERS_COLLECTION_NAME = "users";

    private static class FieldNames
    {
        private static final String EMAIL = "email";
        private static final String FIRST_NAME = "firstName";
        private static final String LAST_NAME = "lastName";
        private static final String MOBILE_PHONE = "mobilePhone";
    }

    private User(String documentID)
    {
        this.documentID = documentID;
    }

    private static void checkRequiredStringField(DocumentSnapshot documentSnapshot, String fieldName) throws IllegalArgumentException
    {
        checkArgument(documentSnapshot.contains(fieldName), "The given documentSnapshot does not contain the required field '%s'.", fieldName);
    }

    private void attachUpdateEventListeners()
    {
        FirebaseFirestore.getInstance()
            .collection(USERS_COLLECTION_NAME)
            .document(documentID)
            .addSnapshotListener((documentSnapshot, exception) -> {
                if (exception == null)
                {
                    email = documentSnapshot.getString(FieldNames.EMAIL);
                    firstName = documentSnapshot.getString(FieldNames.FIRST_NAME);
                    lastName = documentSnapshot.getString(FieldNames.LAST_NAME);
                    mobilePhone = documentSnapshot.getString(FieldNames.MOBILE_PHONE);
                }
                else
                {
                    Crashlytics.log(Log.ERROR, createLogTagForClass(getClass()), "Exception occurred while listening for changes on document with ID " + documentID + ". Exception message: " + exception.getMessage());
                    Crashlytics.logException(exception);
                }
            });
    }

    @NonNull
    public static Task<DocumentSnapshot> getDocumentSnapshot(@NonNull FirebaseUser firebaseUser)
    {
        checkNotNull(firebaseUser);
        return FirebaseFirestore.getInstance().collection(USERS_COLLECTION_NAME)
                .document(firebaseUser.getUid())
                .get();
    }

    public static User fromDocumentSnapshot(@NonNull DocumentSnapshot documentSnapshot) throws IllegalArgumentException
    {
        checkNotNull(documentSnapshot);
        checkRequiredStringField(documentSnapshot, FieldNames.EMAIL);
        checkRequiredStringField(documentSnapshot, FieldNames.FIRST_NAME);
        checkRequiredStringField(documentSnapshot, FieldNames.LAST_NAME);
        checkRequiredStringField(documentSnapshot, FieldNames.MOBILE_PHONE);

        User user = new User(documentSnapshot.getId());

        user.email = documentSnapshot.getString(FieldNames.EMAIL);
        user.firstName = documentSnapshot.getString(FieldNames.FIRST_NAME);
        user.lastName = documentSnapshot.getString(FieldNames.LAST_NAME);
        user.mobilePhone = documentSnapshot.getString(FieldNames.MOBILE_PHONE);
        user.attachUpdateEventListeners();

        return user;
    }

    public String getEmail()
    {
        return email;
    }

    public String getFirstName()
    {
        return firstName;
    }

    public String getLastName()
    {
        return lastName;
    }

    public String getMobilePhone()
    {
        return mobilePhone;
    }
}

此类的想法是从 Firestore 加载它(使用方法 getDocumentSnapshotfromDocumentSnapshot),然后该类检查自身是否有更新(使用方法 >attachUpdateEventListeners)。问题是客户端代码可以在类更新时调用 getter 方法(由于 Firestore 触发的更新),因此客户端可能会获取该字段的过时值。

我正在寻找一种编写如下代码的方法:

public String myGetter()
{
    if (isUpdateRunning())
    {
        waitForUpdate();
    }

    return myField;
}

但我不知道如何实现两个方法 isUpdateRunningwaitForUpdate

我找到的唯一解决方案是在我的 User 类中放置一个标志 isUpdateRunning,当snapshotListener 启动并在完成时返回 false。然后,在我的 getter 中,如果此标志的值为 true,我将在返回该值之前等待固定的时间(使用 Thread.sleep)。不过,我认为这不是最好的解决方案(也是因为使用 Thread.sleep 我必须处理 InterruptedException)。

private boolean isUpdateRunning;

private User(String documentID)
{
    this.documentID = documentID;
    isUpdateRunning = false;
}

private void attachUpdateEventListeners()
{
    FirebaseFirestore.getInstance()
        .collection(USERS_COLLECTION_NAME)
        .document(documentID)
        .addSnapshotListener((documentSnapshot, exception) -> {
            if (exception == null)
            {
                isUpdateRunning = true;
                // omitted code
                // re-assigning fields...
                isUpdateRunning = false;
            }
            // omitted code
        });
}

public String myGetter()
{
    if (isUpdateRunning)
    {
        Thread.sleep(1000); // how to handle the InterruptedException?
    }

    return myField;
}

最佳答案

您不能使用这些 boolean 值来做到这一点,并且尝试调用 sleep(1000) 并不能在所有情况下为您提供帮助,在这种情况下它也被称为不好的做法。如果从数据库获取数据的时间超过 1 秒,会发生什么情况?您的应用程序将无限期卡住,这是一种糟糕的用户体验。根据您的连接速度,可能需要几百毫秒到几秒钟的时间才能获得数据。因此无法保证需要多长时间。

How to wait until a SnapshotListener has finished?

Firebase API 是异步,这意味着 onEvent() 方法在调用后立即返回,并且它返回的任务的回调将在一段时间后调用。

为了避免阻塞主线程,解决此问题的一个快速解决方案是仅在 onEvent() 方法中使用这些结果,否则,如果您想等待数据,我建议您查看我的答案的最后一部分 post 其中我解释了如何使用自定义回调来完成它。您还可以看看这个video 为了更好地理解。

关于java - Android - 如何等待 SnapshotListener 完成?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51825131/

相关文章:

android - ViewPager 中光标驱动的内容

java - 使用 JAX-RS 处理异步 http 请求?

java - 将泛型类型类传递给方法

java - 使用 POI 根据单元格公式结果设置单元格样式

java - JSR 303 Bean Validation,验证在 XML 中定义数组值

java - 如何在 Android Studio 中使用谷歌云翻译 API?

java - 使用 JAXB 映射通用的嵌套 XML 数据结构

android - 使用大而美观的光标录制 Android 应用程序的视频?

具有定时器控制的线程池操作的 C# WinService

c++ - 获取释放内存顺序与顺序一致性不同的实际例子是什么?