You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为什么协程执行API请求及数据库插入后LiveData未及时更新?

根本原因

你遇到的问题和协程返回值无关,是异步操作的执行时序问题:

  • 你调用的loadUrlModelFromApi方法里,viewModelScope.launch启动的是异步任务,不会阻塞当前线程执行,方法调用后会立刻返回,不会等待网络请求、数据库插入操作执行完成
  • 你在调用loadUrlModelFromApi之后立刻读取urlEntityId.value,这时候后台任务还没执行完,_urlEntityId还没被赋值,所以第一次读取拿到的是null;第二次点击时拿到的是第一次后台任务执行完成后存入的旧ID,所以看起来第二次才生效
  • 另外你原ViewModel代码中_isLoading.postValue(false)的位置也错误,会导致loading状态在任务刚启动就被关闭,和实际执行进度不符

解决方案

方案1:监听LiveData变化后再跳转(符合MVVM设计规范)

利用LiveData的观察者特性,等ID更新后再执行跳转逻辑

  1. 修正ViewModel代码
class SearchViewModel @Inject constructor(private val repository: UrlRepository) : ViewModel() {
    private val _isLoading = MutableLiveData<Boolean>()
    val isLoading: LiveData<Boolean> = _isLoading
    private val _toastError = MutableLiveData<String>()
    val toastError: LiveData<String> = _toastError
    private val _urlEntity = MutableLiveData<UrlEntity>()
    val urlEntity: LiveData<UrlEntity> = _urlEntity
    private val _urlEntityId = MutableLiveData<Long>()
    val urlEntityId: LiveData<Long> = _urlEntityId

    fun loadUrlModelFromApi(domainName: String) {
        _isLoading.postValue(true)
        viewModelScope.launch(Dispatchers.IO) {
            val newId = repository.getUrl(domainName)
            _urlEntityId.postValue(newId)
            _isLoading.postValue(false) // 移动到协程内部,任务执行完成后再关闭loading
        }
    }

    // 新增重置ID方法,避免跳转后回到页面重复触发跳转
    fun resetUrlEntityId() {
        _urlEntityId.value = null
    }
}
  1. 修正Fragment代码,新增LiveData监听
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    // 新增ID变化监听
    viewModel.urlEntityId.observe(viewLifecycleOwner) { newId ->
        // 排除空值和loading状态,避免无效触发
        if (newId != null && viewModel.isLoading.value == false) {
            val action = SearchFragmentDirections.actionSearchFragmentToResultFragment(newId)
            navController?.navigate(action)
            viewModel.resetUrlEntityId()
        }
    }

    binding.etSearch.apply {
        setOnEditorActionListener { v, actionId, event ->
            if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                val pattern = DOMAIN_VALIDATION
                val url = this.text.toString()
                if (url.matches(pattern)) {
                    // 只触发请求,不立刻跳转
                    viewModel.loadUrlModelFromApi(url)
                } else {
                    binding.textLayout.error = "Please enter correct domain name."
                }
                true
            } else {
                false
            }
        }
        doOnTextChanged { text, start, before, count ->
            binding.textLayout.error = null
        }
    }
}

方案2:使用挂起函数直接返回结果(更简洁,适合一次性请求场景)

把请求方法改成挂起函数,直接在协程中等待结果返回后再跳转

  1. 修正ViewModel代码
class SearchViewModel @Inject constructor(private val repository: UrlRepository) : ViewModel() {
    private val _isLoading = MutableLiveData<Boolean>()
    val isLoading: LiveData<Boolean> = _isLoading
    private val _toastError = MutableLiveData<String>()
    val toastError: LiveData<String> = _toastError
    private val _urlEntity = MutableLiveData<UrlEntity>()
    val urlEntity: LiveData<UrlEntity> = _urlEntity

    // 改成挂起函数,直接返回插入的ID
    suspend fun loadUrlModelFromApi(domainName: String): Long {
        _isLoading.postValue(true)
        val newId = withContext(Dispatchers.IO) {
            repository.getUrl(domainName)
        }
        _isLoading.postValue(false)
        return newId
    }
}
  1. 修正Fragment代码,在生命周期协程中调用挂起函数
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    binding.etSearch.apply {
        setOnEditorActionListener { v, actionId, event ->
            if (actionId == EditorInfo.IME_ACTION_SEARCH) {
                val pattern = DOMAIN_VALIDATION
                val url = this.text.toString()
                if (url.matches(pattern)) {
                    // 启动生命周期协程,等待请求结果返回后再跳转
                    lifecycleScope.launch {
                        val newId = viewModel.loadUrlModelFromApi(url)
                        val action = SearchFragmentDirections.actionSearchFragmentToResultFragment(newId)
                        navController?.navigate(action)
                    }
                } else {
                    binding.textLayout.error = "Please enter correct domain name."
                }
                true
            } else {
                false
            }
        }
        doOnTextChanged { text, start, before, count ->
            binding.textLayout.error = null
        }
    }
}

内容的提问来源于stack exchange,提问作者Sosiskin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 17:54:01