Search code examples
androidkotlinkotlin-coroutinesandroid-viewmodel

Fragment doesn't update upon State change


I'm working with Flow and MutateStates in Kotlin. I have a list of Data in my viewModel that corresponds to a couple of different fragments. As per my previous SO question, Only updated necessary components in MutableStateFlow<MutableList>, I used the suggested solution to have each individual fragment begiven its own flow. However, my fragments aren't being updated after my data has changed. I'm wondering if I'm doing anything incorrectly. Here's my code:

AppViewModel

class AppViewModel : ViewModel() {
    var dataList: MutableStateFlow<List<DeviceData>> = MutableStateFlow(listOf());


    fun getDataForFragment(index: Int): Flow<DeviceData> =
        dataList.map { it[index] }.distinctUntilChanged()

    fun updateStateFlow(index: Int, data: DeviceData) {
        Log.d("stateFlow", "updateStateFlow")
        dataList.value = dataList.value.toMutableList().apply {
            this[index] = data
        }
        Log.d("stateFlow", dataList.value.get(index).toString())
    }

    fun addStateFlow() {
        var deviceData_ = DeviceData()
        dataList.value = dataList.value.toMutableList().apply {
            this.add(deviceData_)
        }
    }
}

DeviceFragment

class DeviceFragment : Fragment() {
    private val viewModel: AppViewModel by activityViewModels()
    private var _binding: FragmentDeviceBinding? = null
    private val binding get() = _binding!!
    private var index: Int? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    @RequiresApi(Build.VERSION_CODES.O)
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentDeviceBinding.inflate(inflater)
        val bundle = this.arguments
        index = bundle!!.getInt("index", 0)
        return binding.root
    }

    @RequiresApi(Build.VERSION_CODES.O)
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.viewModelScope.launch {
            observers()
        }
    }

    private fun observers() {
        lifecycleScope.launchWhenResumed {
            index?.let {
                viewModel.getDataForFragment(it).collect {
                    binding.circularProgressView.setProgress(it.progress)
                    binding.apply {
            ...
                    }
                }
            }

            val THRESHOLD = 51.0f
            index?.let { it ->
                viewModel.getDataForFragment(it).debounce(500)?.collect {
                    val value = it.probability;
                    if (value != 0.0F) {    //avoid invoking at start of app
                        if (value > THRESHOLD) {
                            Log.d("stateFlow", "SENSOR DETECTED")
                            sensorDetected()
                        }
                    }
                }
            }
        }
    }

    private suspend fun sensorDetected() {
        if (index?.let { viewModel.getDataForFragment(it).first().detectCount } == 0) {
            Log.d("stateFlow", "sensor invoked")
        updateDetectorUI()
        }
    }

    private suspend fun updateDetectorUI() {
        when (index?.let { viewModel.getDataForFragment(it).first().detectCount }) {
            0 -> {
                binding.circularProgressView.setProgress(100f)
                changeTintColors(R.color.tint_green)
            }

            1 -> {
                changeTintColors(R.color.tint_purple)
            }

            2 -> changeTintColors(R.color.tint_purple)
            3 -> changeTintColors(R.color.tint_red)
            else -> {
                binding.circularProgressView.setProgress(100f)
                changeTintColors(R.color.tint_green)
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        _binding = null
    }
}

When I run my app, I get the logs in updateStateFlow with the correct data. However, no logs from DeviceFragment shows up despite it making sense to do so (the values pass the conditions in the code).

UPDATE: it seems like there's something wrong with the way I update my flow, but I can't figure out what's wrong :/


Solution

  • I don't know if this will help and to be honest I'm not expert in flows and state but I usually do state flow things like this in the view model:

    private val _someValue = MutableSateFlow(listof(failedTimeTravelAttempts))
    val someValue = _someValue.asStateFlow()
    
    fun updateSomeValue(input: List<TimeTravelStuff>) {
        _someValue.value = input
    }
    

    When I need the use this value and have it updated say it's contained in the view model I use it like:

    // collectAsState() may be what you need to use?
    val mSomeValue = mViewModel.someValue.collectAsState()
    
    
    // then when I need it
    mSomeValue.value
    

    And updating, which I can imagine you know this but for anyone not seeing where this is going:

    val mNewList = listOf(secretsToTimeTravel)
    
    mViewModel.updateSomeValue(mNewList)