Как обновить пользовательский интерфейс в сопрограммах в Kotlin 1.3
Я пытаюсь вызвать 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 ответа
Чтобы ответить на ваш ближайший вопрос, вы должны просто запустить сопрограмму в правильном контексте:
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
блокировать в каждом обратном вызове GUI, где вы должны выполнить приостановленный код, но на самом деле это рекомендуемый способ использования этой функции. Это находится в строгой параллели к письму 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 { ... }
синтаксис.
private var viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
uiScope.launch {
withContext(Dispatchers.IO) {
//Do background tasks...
withContext(Dispatchers.Main){
//Update UI
}
}
}
Если вы используете сопрограммы-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
})