web-dev-qa-db-ja.com

Kotlin 1.3のコルーチンでUIを更新する方法

APIを呼び出して、変数の準備ができたら、それぞれUIコンポーネントを更新しようとしています。

これは、コルーチンを起動している私のネットワークシングルトンです。

object MapNetwork {
    fun getRoute(request: RoutesRequest,
                 success: ((response: RoutesResponse) -> Unit)?,
                 fail: ((throwable: Throwable) -> Unit)? = null) {
        val call = ApiClient.getInterface().getRoute(request.getURL())

        GlobalScope.launch(Dispatchers.Default, CoroutineStart.DEFAULT, null, {

            try {
                success?.invoke(call.await())
            } catch (t: Throwable) {
                fail?.invoke(t)
            }

        })
    }
}

そして、これは私がそれを呼ぶ方法です:

network.getRoute(request,
            success = {
                // Make Some UI updates
            },
            fail = {
                // handle the exception
            }) 

そして、UIスレッド以外のスレッドからUIを更新できないという例外が発生します:

com.google.maps.api.Android.lib6.common.apiexception.c: Not on the main thread

私はすでに this ソリューションを試しましたが、Continuation<T>クラスのresumeはKotlin 1.3以降「非推奨」です

10
Mohsen

直接の質問に答えるには、正しいコンテキストでコルーチンを起動する必要があります。

_val call = ApiClient.getInterface().getRoute(request.getURL())
GlobalScope.launch(Dispatchers.Main) {
    try {
        success?.invoke(call.await())
    } catch (t: Throwable) {
        fail?.invoke(t)
    }
}
_

ただし、コルーチンを使用する方法は間違っているため、これは氷山の一角にすぎません。彼らの主な利点はコールバックを回避することですが、あなたはそれらを再紹介しています。また、 構造化された並行性 のベストプラクティスを侵害しています。これは、本番用ではないGlobalScopeを使用して。

await onできる_Deferred<RoutesResponse>_を提供する非同期APIを既に持っているようです。使用方法は次のとおりです。

_scope.launch {
    val resp = ApiClient.getInterface().getRoute(request.getURL()).await()
    updateGui(resp)
}
_

中断可能なコードを実行する必要があるすべてのGUIコールバックでlaunchブロックを使用することを提案しているという事実に悩まされるかもしれませんが、実際にはこの機能を使用する推奨方法です。 launchブロックのコンテンツは、その外部のコードと同時に実行されるため、Thread { ... my code ... }.start()の記述と厳密に並行しています。

上記の構文は、scopeを実装するCoroutineScope変数が用意されていることを前提としています。たとえば、Activity

_class MyActivity : AppCompatActivity(), CoroutineScope {
    lateinit var masterJob: Job
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + masterJob

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        masterJob = Job()
    }

    override fun onDestroy() {
        super.onDestroy()
        masterJob.cancel()
    }
}
_

coroutineContextがデフォルトのコルーチンディスパッチャーを_Dispatchers.Main_に設定する方法に注意してください。これにより、プレーンな_launch { ... }_構文を使用できます。

8
Marko Topolnik

コルーチン-Androidを使用している場合は、Dispatchers.Main
(gradle依存関係はimplementation "org.jetbrains.kotlinx:kotlinx-coroutines-Android:1.0.0"

network.getRoute(request,
        success = {
            withContext(Dispatchers.Main) {
                // update UI here
            }
        },
        fail = {
            // handle the exception
        }) 
6