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

Android Kotlin中LiveData Observer第二次请求未触发问题

问题描述

实现侧边菜单点击触发API请求的功能时,预期每次点击菜单发起请求后,绑定的Observer都会被触发。实际测试表现如下:

  • 首次点击菜单时Retrofit可正常返回正确响应
  • 第二次点击发起请求时Observer未被触发
  • 初步排查确认Retrofit层已经正常更新MutableLiveData的值,但Repository层未返回对应数据
问题根因

出现第二次点击Observer不触发的问题,核心是数据层的LiveData设计逻辑错误:

  • 每次调用getDisastersByFilter()时,RemoteDataSource都会新建一个独立的MutableLiveData实例,Repository层的map操作也会基于这个新实例生成新的转换后LiveData,最终ViewModel返回给调用方的是一个全新的、没有任何观察者绑定的LiveData。
  • MapsFragment仅在页面初始化时,对第一次调用getDisastersByFilter("gempa")返回的旧LiveData做了订阅,后续点击菜单触发的请求,返回的新LiveData和之前订阅的旧实例完全无关,即使Retrofit正常postValue,也无法触发已经绑定在旧实例上的Observer。
  • 现有代码没有统一的数据分发容器:点击菜单时仅执行了创建新LiveData、发起请求的逻辑,没有把新的请求结果和Fragment正在观察的数据源做关联,相当于请求发出去之后,结果被扔到了一个没人监听的新对象里,自然不会有UI回调。
原有问题代码

RemoteDataSource.kt

override fun getDisastersByFilter(filter: String?): LiveData<ApiResponse<DisastersDTO?>> {
    val result = MutableLiveData<ApiResponse<DisastersDTO?>>()

    apiService.getDisastersByFilter(filter).enqueue(object : Callback<DisastersResponse> {
        override fun onResponse(
            call: Call<DisastersResponse>,
            response: Response<DisastersResponse>
        ) {
            if(response.isSuccessful) {
                val data = response.body()
                data?.disastersDTO?.let {
                    result.postValue(ApiResponse.Success(it))
                    Log.d("RemoteDataSource", "$it")
                } ?: run {
                    result.postValue(ApiResponse.Error("Bencana alam tidak ditemukan"))
                }
            } else {
                result.postValue(ApiResponse.Error("Terjadi kesalahan!"))
            }
        }

        override fun onFailure(call: Call<DisastersResponse>, t: Throwable) {
            result.postValue(ApiResponse.Error(t.localizedMessage!!))
            Log.d("RemoteDataSource", t.localizedMessage!!)
        }

    })

    return result
}

Repository.kt

override fun getDisastersByFilter(filter: String?): LiveData<Resource<List<Disaster>>> =
    remoteDataSource.getDisastersByFilter(filter).map {
        when (it) {
            is ApiResponse.Empty -> Resource.Error("Terjadi error")
            is ApiResponse.Error -> Resource.Error(it.errorMessage)
            is ApiResponse.Loading -> Resource.Loading()
            is ApiResponse.Success -> Resource.Success(
                DataMapper.disastersResponseToDisasterDomain(
                    it.data
                )
            )
        }
    }

SharedViewModel.kt

fun getDisastersByFilter(filter: String? = "gempa"): LiveData<Resource<List<Disaster>>> =
    useCase.getDisastersByFilter(filter)

MapsFragment 相关代码

private val viewModel: SharedViewModel by activityViewModels()
viewModel.getDisastersByFilter("gempa").observe(viewLifecycleOwner) {
        when (it) {
            is Resource.Success -> {
                Log.d("MapsFragmentFilter", "${it.data}")
                it.data?.let { listDisaster ->
                    if(listDisaster.isNotEmpty()) {
                        map.clear()
                        addGeofence(listDisaster)
                        listDisaster.map { disaster ->
                            placeMarker(disaster)
                            addCircle(disaster)
                        }
                    }
                }
            }

            is Resource.Error -> Toast.makeText(context, "Filter Error", Toast.LENGTH_SHORT).show()

            is Resource.Loading -> {}
        }
    }

MainActivity 触发请求相关代码

private val viewModel: SharedViewModel by viewModels()
binding.navViewMaps.setNavigationItemSelectedListener { menu ->
        when (menu.itemId) {
            R.id.filter_gempa -> viewModel.getDisastersByFilter("gempa")
            R.id.filter_banjir -> viewModel.getDisastersByFilter("banjir")
            R.id.about_us -> viewModel.getDisasters()
        }

        binding.drawerLayoutMain.closeDrawers()

        true
    }
修复方案

按照以下步骤调整代码即可解决问题:

  1. 改造ViewModel,持有唯一的可观察数据源
    不要在getDisastersByFilter方法里直接返回新的LiveData,而是在ViewModel内部定义一个固定的MutableLiveData作为所有请求结果的分发容器,观察者只需要订阅这一个实例即可。
    // 固定的数据分发实例,全局唯一
    private val _disasterList = MutableLiveData<Resource<List<Disaster>>>()
    val disasterList: LiveData<Resource<List<Disaster>>> = _disasterList
    
    fun getDisastersByFilter(filter: String? = "gempa") {
        // 先推送加载状态
        _disasterList.postValue(Resource.Loading())
        // 发起请求,将结果统一发射到固定的_diasterList中
        viewModelScope.launch {
            useCase.getDisastersByFilter(filter).collect { response ->
                _disasterList.postValue(response)
            }
        }
    }
    
  2. 调整Fragment的观察逻辑
    只需要在Fragment初始化时,订阅ViewModel中固定的disasterList即可,不需要在调用请求方法的时候链式observe。
    private val viewModel: SharedViewModel by activityViewModels()
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // 仅订阅一次固定数据源
        viewModel.disasterList.observe(viewLifecycleOwner) {
            when (it) {
                is Resource.Success -> {
                    Log.d("MapsFragmentFilter", "${it.data}")
                    it.data?.let { listDisaster ->
                        if(listDisaster.isNotEmpty()) {
                            map.clear()
                            addGeofence(listDisaster)
                            listDisaster.map { disaster ->
                                placeMarker(disaster)
                                addCircle(disaster)
                            }
                        }
                    }
                }
                is Resource.Error -> Toast.makeText(context, "Filter Error", Toast.LENGTH_SHORT).show()
                is Resource.Loading -> {}
            }
        }
        // 页面初始化默认加载一次数据
        viewModel.getDisastersByFilter("gempa")
    }
    
  3. 调整数据层实现
    RemoteDataSource和Repository层不需要每次请求都创建新的LiveData,可以改为直接返回业务数据,或者用Flow做数据流转换,最终所有结果都汇总到ViewModel的固定LiveData中即可。如果要保留现有层级写法,只需要确保上层始终持有同一个可观察实例,不要每次请求生成新的LiveData。
  4. 调整菜单点击逻辑
    MainActivity中点击菜单时,直接调用ViewModel的请求方法即可,不需要处理返回值,原有点击逻辑除了不需要接收返回值之外不需要做其他改动。

内容的提问来源于stack exchange,提问作者Mohammad Azri Khairuddin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 06:31:11