Android-如何等待SnapshotListener完成?

时间:2018-08-13 14:53:01

标签: java android multithreading firebase google-cloud-firestore

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

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

我找到的唯一解决方案是在我的isUpdateRunning类中放置一个标记User,该标记将在true开始时设置为snapshotListener,并设置为{ {1}}完成时。然后,在我的getter中,如果此标志的值为false,则在返回值之前,我将等待一段固定的时间(使用true)。但是,我认为这不是最佳解决方案(也是因为使用Thread.sleep我将不得不处理Thread.sleep)。

InterruptedException

1 个答案:

答案 0 :(得分:1)

使用这些布尔值并尝试调用sleep(1000)并不能在所有情况下都对您有所帮助,在这种情况下,这也称为不良做法。如果从数据库获取数据所花费的时间大于1秒,该怎么办?您的应用将无限期冻结,这是糟糕的用户体验。根据您的连接速度,可能需要几百毫秒到几秒钟的时间才能获得该数据。因此,不能保证需要多长时间。

  

如何等待SnapshotListener完成?

Firebase API为asynchronous,这意味着onEvent()方法在调用后立即返回,并且它返回的Task中的回调将在一段时间后调用。

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