Why my composable not recomposing on changing value for MutableState of HashMap?

Viewed 2722

Why my composable not recomposing on changing value for MutableState of HashMap.

ViewModel

 val imageList: MutableState<HashMap<Int, Uri>> = mutableStateOf(HashMap())
    fun setImage(imageUri: Uri) {
        imageList.value[imagePosition] = imageUri
    }

Fragment

if (viewModel.imageList.value[stepNo] != null && !TextUtils.isEmpty(
                                            viewModel.imageList.value[stepNo].toString()
                                        )
                                    ) {
                                        Image(
                                            contentDescription = "Recipe",
                                            painter = painterResource(R.drawable.ic_baseline_fastfood_24),
                                            modifier = Modifier
                                                .padding(8.dp)
                                        )
                                    }

fun permissionCallbacks() {
        imagePickerListener =
            registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
                if (result.resultCode == Activity.RESULT_OK) {
                    // There are no request codes
                    val data: Intent? = result.data
                    val selectedImageUri = data?.data
                    selectedImageUri?.let { viewModel.setImage(it) }
                }
            }
        requestPermissionLauncher =
            registerForActivityResult(
                ActivityResultContracts.RequestPermission()
            ) { isGranted: Boolean ->
                if (isGranted) {
                    val galleryIntent = Intent(
                        Intent.ACTION_PICK,
                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI
                    )
                    galleryIntent.type = "image/*"
                    imagePickerListener?.launch(galleryIntent)
                } else {
                   
                }
            }
    }

I see by debugging that value correctly set inside setImage() method but if condition for viewModel.imageList.value[stepNo] is not called again when value for hashmap is changed.

3 Answers

mutableStateOf can only track when you replace one value with an other one. If you change state of current value no way it can handle it

You can use mutableStateMapOf instead of mutableStateOf(HashMap()):

val imageList = mutableStateMapOf<Int, Uri>()
---
fun setImage(imageUri: Uri) {
    imageList[imagePosition] = imageUri
}

Ok, well firstly, the initialisation logic you are using contains boilerplate. I mean if you are writing val a = 0, then it is good. You do not need to specify the type here. val a : Int = 0 is wrong. In your initialisation you do not need to include MutableState<...>, you can specify the type of hashmap in the HashMap() call in the mutableStateOf(...).

Ok, secondly, you cannot treat any type of data as state (I mean compose cannot). You can instead try to use primitive types as your store, or check if there exists an exact type for your needs, that is pre-built in Compose.

For example,

val a by mutableStateOf("Hello")

will trigger recompositions, while

val a by mutableStateOf(Path())

will not, even if you re-assign the whole variable. This is because Path() is not meant to be a state-holder in Compose.

If you actually replace the map of Int and Uri, with two lists of Int and Uri each, it will work as expected because even the immutable listOf() is supported.

Now, you could also search if a particular pre-defined holder supports your use case. In this case, like there has been mutableStateListOf() added to the libraries, allowing you to use lists directly as a regular list object while still being a state-holder.

Philip says there's mutableStateMapOf(), which I haven't heard of but since there's mutableStateListOf(), it might be there too.

So yes, for your use case, if it exists, it will work properly, but you should know why the HashMap didn't work, which is why I posted the explanation.

Use SnapshotStateMap instead of HashMap:

enter image description here

Related