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

ViewModel Observer多次触发求助:开关切换时API响应重复回调

Hey there, let's work through this multiple callback issue you're seeing with your chart and switch component. The core problems here are twofold: you're registering a new Observer every time you trigger the API, and pending old API requests are completing and emitting data alongside the latest request. Let's fix this step by step.

First: Fix the repeated Observer registration

Right now, every call to hitSecondChartApi adds a new Observer to your LiveData. After a few switch toggles, you'll have multiple Observers all listening for data—each one triggering when a response comes in, causing those stacked callbacks.

Move your Observer registration to a one-time lifecycle method like onViewCreated in your Fragment:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    
    // Observe the LiveData ONCE here, not every API call
    userModel.responseSecondChartData.observe(viewLifecycleOwner, Observer { data ->
        data?.let {
            Log.e(TAG, "Second chart data: " + it.toString())
            Utils.debugger("FRAG ", "$it")
            secondChartData = it
            if (it.isNotEmpty()) {
                splitSecondParentList(it)
            } else {
                Utils.showLongToast(requireActivity(), "No Data for Distribution")
            }
        }
    })
}

Then update hitSecondChartApi to only trigger the API request, no more adding Observers here:

private fun hitSecondChartApi(country: String, category: String, market: String, weightKpi: String) {
    userModel.getResponseShareSecondChartData(country, category, market, weightKpi)
    Log.e(TAG, "Inside hitsecondchartapi: "+ country+":"+category+":"+market+":"+weightKpi)
}

Solution 1: Cancel old API requests before starting new ones

To ensure only the latest request's response is processed, we can cancel any ongoing API calls when a new switch toggle happens. Use coroutines and a Job in your ViewModel to manage this:

In your ViewModel:

import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch

class YourViewModel : ViewModel() {
    private var ongoingChartRequest: Job? = null
    val responseSecondChartData = MutableLiveData<List<YourChartDataType>>()

    fun getResponseShareSecondChartData(country: String, category: String, market: String, weightKpi: String) {
        // Cancel the previous request if it's still running
        ongoingChartRequest?.cancel()
        
        // Launch a new coroutine for the latest request
        ongoingChartRequest = viewModelScope.launch {
            try {
                // Replace this with your actual API call logic
                val fetchedData = yourRepository.fetchSecondChartData(country, category, market, weightKpi)
                responseSecondChartData.postValue(fetchedData)
            } catch (e: CancellationException) {
                // Ignore cancellation (this is expected when a new request starts)
            } catch (e: Exception) {
                // Handle any other errors (e.g., network issues)
                Log.e("ViewModel", "API request failed", e)
            }
        }
    }
}

This way, when you toggle the switch, the old pending request gets canceled immediately, so it never emits data to the LiveData. Only the latest request's response will trigger your Observer.

Solution 2: Use SingleEventLiveData for one-time event handling

If you want to ensure each API response is only processed once (even if the LiveData emits multiple times), use a SingleEventLiveData instead of regular MutableLiveData. This is useful if you have edge cases where old responses might still slip through (e.g., slow network calls that finish before cancellation).

First, define the helper classes:

open class SingleEvent<out T>(private val content: T) {
    var hasBeenHandled = false
        private set // Allow read access but not write

    // Returns content only if it hasn't been handled yet
    fun getContentIfNotHandled(): T? {
        return if (hasBeenHandled) {
            null
        } else {
            hasBeenHandled = true
            content
        }
    }

    // Returns content even if it's already been handled (for debugging)
    fun peekContent(): T = content
}

class SingleEventLiveData<T> : MutableLiveData<SingleEvent<T>>() {
    fun setEventValue(value: T) = setValue(SingleEvent(value))
    fun postEventValue(value: T) = postValue(SingleEvent(value))
}

Update your ViewModel to use SingleEventLiveData:

class YourViewModel : ViewModel() {
    val responseSecondChartData = SingleEventLiveData<List<YourChartDataType>>()

    fun getResponseShareSecondChartData(country: String, category: String, market: String, weightKpi: String) {
        viewModelScope.launch {
            val fetchedData = yourRepository.fetchSecondChartData(country, category, market, weightKpi)
            responseSecondChartData.postEventValue(fetchedData)
        }
    }
}

Then adjust your Fragment's Observer to handle the single event:

userModel.responseSecondChartData.observe(viewLifecycleOwner, Observer { event ->
    // Only process the event if it hasn't been handled before
    event.getContentIfNotHandled()?.let { data ->
        Log.e(TAG, "Second chart data: " + data.toString())
        Utils.debugger("FRAG ", "$data")
        secondChartData = data
        if (data.isNotEmpty()) {
            splitSecondParentList(data)
        } else {
            Utils.showLongToast(requireActivity(), "No Data for Distribution")
        }
    }
})

This ensures that even if an old response is emitted, it will be marked as handled and ignored by your Observer.

Bonus: Use viewLifecycleOwner

Notice we're using viewLifecycleOwner instead of this when observing. This ties the Observer to your Fragment's View lifecycle, so it automatically cleans up when the View is destroyed (preventing memory leaks and unnecessary callbacks).

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:13:02