Используйте сопрограммы для обновления пользовательского интерфейса при выполнении сетевого вызова
Я пытаюсь показать счетчик при совершении сетевого вызова с использованием сопрограмм. Кажется, что пользовательский интерфейс не показывает состояние LOADING_ITEMS (счетчик) до тех пор, пока не будет возвращен вызов itemsFromRepo, затем в течение доли секунды отображается счетчик, а затем отображаются элементы. У меня сложилось впечатление, что, как и в сопрограмме, состояние будет установлено на LOADING_ITEMS, элементы будут очищены, сетевой вызов будет выполнен в фоновом режиме, пока счетчик отображается в пользовательском интерфейсе. Затем, когда сетевой вызов завершится, сопрограмма продолжит работу и установит элементы, затем установит состояние.
Это правильный способ использования сопрограмм? И масштаб, я думаю, это новое из экспериментальных сопрограмм, с которыми я играл несколько месяцев назад.
// ViewModel.kt
enum class State { LOADING_ITEMS, SELECTING_ITEM }
var state = ObservableField<State>()
var items = ObservableField<List<String>>()
private fun loadItems() {
state.set(State.LOADING_ITEMS)
items.set(emptyList())
GlobalScope.launch(Dispatchers.Main) {
val itemsFromRepo = apiRepo.getItems() // a network call
items.set(itemsFromRepo)
state.set(State.SELECTING_ITEM)
}
}
// Repo.kt
suspend fun getItems() = suspendCoroutine<List<String>> { cont ->
FirebaseDatabase.getInstance().getReference("Items")
.addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onCancelled(error: DatabaseError?) {
cont.resume(listOf(error?.message ?: "Unknown error"))
}
override fun onDataChange(snap: DataSnapshot?) {
cont.resume(snap?.children?.map { it.key } ?: emptyList())
}
})
}
2 ответа
Рекомендуется использовать локальную область видимости для обработки сопрограмм:
class ViewModel : CoroutineScope {
private var job: Job = Job()
// To use Dispatchers.Main (CoroutineDispatcher - runs and schedules coroutines) in Android add
// implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
enum class State { LOADING_ITEMS, SELECTING_ITEM }
var state = ObservableField<State>()
var items = ObservableField<List<String>>()
fun detachView() {
job.cancel()
}
private fun loadItems() {
state.set(State.LOADING_ITEMS)
items.set(emptyList())
launch {
val itemsFromRepo = apiRepo.getItems()
items.set(itemsFromRepo)
state.set(State.SELECTING_ITEM)
}
}
}
А по поводу вашего вопроса:
Это правильный способ использования сопрограмм?
Да, это правильный путь. Если у вас есть сетевой вызов внутри suspend
функция (это в вашем случае), то эта функция будет приостанавливать выполнение сопрограммы, пока вы не вызовете continuation.resume()
или другие связанные методы для возобновления сопрограммы. И приостановка сопрограммы не будет блокировать main
нить.
Замените метод loadItems() следующим:
private fun loadItems() {
state.set(State.LOADING_ITEMS)
items.set(emptyList())
GlobalScope.launch(Dispatchers.Main) {
val itemsFromRepo = async(Dispatchers.Default) { apiRepo.getItems() }
items.set(itemsFromRepo.await())
state.set(State.SELECTING_ITEM)
}
}
Вы выполняете вызов API в главном потоке Android, а для фонового вызова вы должны использовать Dispatchers.Default.
Информацию о Диспетчерах смотрите по этой ссылке