Kotlin Stateflow new value emitted but not collected

39 views Asked by At

I have a fragment that shows a location after collecting it from the viewmodel stateflow, the location is stored in shared preferences than emitted to a mutableflowstate when the fragment starts.

The fragment can show a BottomSheetDialogue, in the BottomSheetDialog the user can update the location, I am updating the location via the viewmodel in both shared prefs and the mutablestateflow.

The problem is when the new location is updated, and the bottom sheet is dismissed the new value is not collected in the fragment, or the collector is not notified.

I am not sure exactly what I am doing wrong here.

Fragment

val viewModel: LocationViewModel by viewModels() lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.shippingLocation.collect { location ->
                    if (location != null) {
                        binding.shippingLocation.text = location
                    } else {
                        binding.shippingLocation.text = ""
                    }
                }
            }
        }

binding.shippingLocationEditBtn.setOnClickListener {
                val editLocationSheet = EditLocationFragment()
                editLocationSheet.show(parentFragmentManager, "EditLocationSheet")
            }

ViewModel

    private val _shippingLocation = MutableStateFlow<String?>(null)
    val shippingLocation: StateFlow<String?> = _shippingLocation.asStateFlow()


    init {
        getShippingLocation()
    }

    fun updateShippingLocation(location: String?) {
        sharedPreferences.edit()
            .putString(PREFS_SHIPPING_LOCATION_KEY, location)
            .apply()
        _shippingLocationWilaya.value = location
    }

    private fun getShippingLocation() {
        val location = sharedPreferences.getString(PREFS_SHIPPING_LOCATION_KEY, null)
        viewModelScope.launch {
            if (location == null) {
                _shippingLocation.value = ""
            } else {
                _shippingLocation.value = location
            }
        }
    }

BottomSheetDialog

val viewModel: LicationViewModel by viewModels()
binding.editShippingLocationTopAppBar.setOnMenuItemClickListener { menuItem ->
            when (menuItem.itemId) {
                R.id.saveShippingLocation -> {
                    if (location != null) {
                        viewModel.updateShippingLocation(location)
                        dismiss()
                    } else {
                        Toast.makeText(
                            requireContext(),
                            "Please select a location",
                            Toast.LENGTH_SHORT
                        ).show()
                    }
                    true
                }
                else -> false
            }
        }
3

There are 3 answers

1
AnumShafiq On

You are collecting the value only on the view started state which means the value will be collected only when the view started not every time it changes its value.

You need to collect the value every time it changes.

val viewModel: LocationViewModel by viewModels()

lifecycleScope.launch {
    viewModel.shippingLocation.collect { location ->
        binding.shippingLocation.text = location.orEmpty()
    }
}

binding.shippingLocationEditBtn.setOnClickListener {
    val editLocationSheet = EditLocationFragment()
    editLocationSheet.show(parentFragmentManager, "EditLocationSheet")
}
0
Jamal N On

I have found that the problem is that I am not having the same instance of the viewmodel, in each fragment a new instance of the viewmodel (with a new data) is created.

I thought that the viewModels() function is used with hilt ( @HiltViewModel notation at the viewmodel level) to create a shared viewmodel across fragments, but it turned out that i was wrong about this.

0
ekd On

Here is your problem;

init { getShippingLocation() }

this part is not working.

Maybe, you can add Dispatcher to your getShippingLocation function, you can try by doing public function and remove init block, if it is okay for you.