Как обновить пользовательский интерфейс в сопрограммах в Kotlin 1.3 - PullRequest
0 голосов
/ 31 октября 2018

Я пытаюсь вызвать API, и когда мои переменные будут готовы, обновите компоненты пользовательского интерфейса соответственно.

Это мой сетевой синглтон, который запускает сопрограмму:

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
            }) 

И я получаю Исключение, которое говорит, что не может обновить пользовательский интерфейс из любого потока, кроме потока пользовательского интерфейса:

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

Я уже пробовал это решение, но resume в Continuation<T> классе "устарело" с Kotlin 1.3

Ответы [ 2 ]

0 голосов
/ 05 ноября 2018

Чтобы ответить на ваш ближайший вопрос, вы должны просто запустить сопрограмму в правильном контексте:

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

Однако это будет лишь верхушка айсберга, потому что ваш подход - неправильный способ использования сопрограмм. Их ключевое преимущество - избегать обратных вызовов, но вы их снова вводите. Вы также нарушаете структурированный параллелизм передовой опыт, используя GlobalScope, который не предназначен для производственного использования.

Очевидно, у вас уже есть асинхронный API, который дает вам Deferred<RoutesResponse>, который вы можете await включить. Способ его использования заключается в следующем:

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

Вы можете быть огорчены тем фактом, что я предлагаю иметь блок launch в каждом обратном вызове графического интерфейса пользователя, где вы должны выполнить приостанавливаемый код, но на самом деле это рекомендуемый способ использования этой функции. Он строго параллелен написанию Thread { ... my code ... }.start(), потому что содержимое вашего launch блока будет одновременно выполняться с кодом за его пределами.

Приведенный выше синтаксис предполагает, что у вас есть готовая переменная 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 { ... }.

0 голосов
/ 31 октября 2018

Если вы используете сопрограммы 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
        }) 
...