如何从Kotlin异步闭包中保存数据?

时间:2020-06-08 19:41:38

标签: android kotlin asynchronous closures aws-amplify

我正在异步函数中调用API,并且想存储来自回调的响应。

具体来说,我在Android应用中使用AWS Amplify的API类别,并希望将返回值分配给ViewModel中的LiveData变量。

fun getMuscleGroup(id: String): ExampleData {
    var exampleData = ExampleData.builder().name("").build()

    Amplify.API.query(
        ModelQuery.get(ExampleData::class.java, id),
        { response ->
            Log.d("AmplifyApi", response.data.name)
            exampleData = response.data
        },
        { error -> Log.e("AmplifyApi", "Query failure", error) }
    )

    return exampleData
}

我可以接收到响应,并且可以正确记录该响应,但是由于该函数返回得较早,因此未将响应分配给exampleData变量。

在Android Studio中,变量exampleData突出显示为文本:

包装到参考对象中,以便在闭合中捕获时进行修改

由于我不太熟悉kotlin中的多线程API,因此我不确定如何在远程API返回其异步响应之前阻止该功能。

2 个答案:

答案 0 :(得分:2)

最基本的方法是使用标准Java线程安全性构造。

fun getMuscleGroup(id: String): ExampleData {
    var exampleData = ExampleData.builder().name("").build()
    val latch = CountDownLatch(1)
    Amplify.API.query(
        ModelQuery.get(ExampleData::class.java, id),
        { response ->
            Log.d("AmplifyApi", response.data.name)
            exampleData = response.data
            latch.countDown()
        },
        { error -> 
            Log.e("AmplifyApi", "Query failure", error)
            latch.countDown()
        }
    )

    latch.await()
    return exampleData
}

由于这是在Android上,因此这可能是一个不好的解决方案。我猜想在UI线程上正在调用getMuscleGroup,并且您不希望此方法实际阻塞。用户界面将冻结,直到网络调用完成。

更多的Kotlin方式是将方法设为暂停方法。

suspend fun getMuscleGroup(id: String): ExampleData {
    return suspendCoroutine { continuation ->
      Amplify.API.query(
        ModelQuery.get(ExampleData::class.java, id),
        { response ->
            Log.d("AmplifyApi", response.data.name)
            continuation.resume(response.data)
        },
        { error -> 
            Log.e("AmplifyApi", "Query failure", error)
            // return default data
            continuation.resume(ExampleData.builder().name("").build())
        }
    }
}

使用Kotlin协程暂停协程,直到准备好答案,然后返回结果。

其他选择是使用回调而不是返回值或LiveData或RxJava之类的可观察模式。

答案 1 :(得分:1)

您不能阻止等待异步结果。在最坏的情况下,它可能会导致向用户显示“应用程序无响应”(ANR)错误消息,并充其量会使您的应用程序显得混乱且无响应。

您可以改为为此函数添加回调:

fun getMuscleGroup(id: String, callback: (ExampleData) -> Unit) {
    var exampleData = ExampleData.builder().name("").build()

    Amplify.API.query(
        ModelQuery.get(ExampleData::class.java, id),
        { response ->
            Log.d("AmplifyApi", response.data.name)
            callback(response.data)
        },
        { error -> Log.e("AmplifyApi", "Query failure", error) }
    )
}

然后在调用代码的地方,将后续操作放在回调中:

fun onMuscleGroupClicked(id: String) {
    getMuscleGroup(id) { exampleData ->
        // do something with the data after it arrives
    }
}

协程是另一种选择。它们很好,因为您不必将顺序操作嵌套在回调中。要进行设置,我将使用query创建API库suspendCancellableCoroutine函数的扩展扩展函数版本。然后,您可以有序地在其他暂挂函数中自由使用它。但是您需要阅读有关协程的文档。从这里开始要进行大量解释。