进行网络通话时使用协程更新UI

时间:2019-01-01 09:39:28

标签: android kotlin kotlin-coroutines

在尝试使用协同程序进行网络通话时,我试图显示一个微调框。 UI似乎不显示LOADING_ITEMS状态(微调器),直到itemsFromRepo调用返回,然后微调器显示片刻,然后显示项。我的印象是,因为它位于协同程序中,所以状态将设置为LOADING_ITEMS,将清除项目,并在微调器显示在UI上的同时在后台进行网络调用。然后,当网络通话结束时,协同程序将继续运行并设置各项然后声明。

这是使用协程的正确方法吗?而且范围,我认为这是我几个月前玩过的实验协同程序中的新功能。

// 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 个答案:

答案 0 :(得分:3)

最佳做法是使用本地范围处理协程:

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线程。

答案 1 :(得分:-2)

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

您正在Android主线程中进行API调用,而要在后台进行调用,则应使用 Dispatchers.Default

有关分派器的信息,请参阅此link