Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Jetpack Compose not updating without LaunchedEffect of unrelated item

Please can someone throw any light on the problem that I have with Jetpack Compose and a ViewModel.

I have a data class called ProductEntity.

data class ProductEntity(
    var id: Int = 0,
    var name: String = "",
    var quant: Int = 1,

    )

In a ViewModel I have:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

  1. private local list of products,
  2. Boolean button state and
  3. a Products list which is a MutableState<List<ProductEntity>>. This is set to start with the values from the local list of products
  4. a function to update the product list based on a counter number passed
class TestGlobalViewModel : ViewModel() {

    private val localList = ArrayList<ProductEntity>().apply {
        add(ProductEntity(name = "a"))
        add(ProductEntity(name = "b"))
        add(ProductEntity(name = "c"))
    }

    var buttonState by mutableStateOf(false)

    var products = mutableStateOf<List<ProductEntity>>(
            localList
    )

    fun updateProducts(counter:Int){
        val localList2 = ArrayList<ProductEntity>()
        localList2.add(ProductEntity(name = "a$counter"))
        localList2.add(ProductEntity(name = "b$counter"))
        localList2.add(ProductEntity(name = "c$counter"))
        localList2.add(ProductEntity(name = "a${counter + 1}"))
        localList2.add(ProductEntity(name = "b${counter + 1}"))
        localList2.add(ProductEntity(name = "c${counter + 1}"))
        localList2.add(ProductEntity(name = "a${counter + 3}"))
        localList2.add(ProductEntity(name = "b${counter + 3}"))
        localList2.add(ProductEntity(name = "c${counter + 3}"))

        products = mutableStateOf(localList2)
    }

}

I have the HomeScreen Composable

In there I have:

  1. a reference to the ViewModel
  2. mutable state of a counter
  3. mutable state of a filtered list. This latter list is updated whenever the Products list is replaced.

I have a Button that displays the first item in the filtered list and does three things when pressed

  1. Increments the counter
  2. updates the product list
  3. toggles the buttonState variable in the view model

Finally I have a LaunchedEffect triggered by the button state that does nothing.


@Composable
fun HomeScreen() {

    val vm = viewModel<TestGlobalViewModel>()

    var counter by remember {
        mutableStateOf(1)
    }
    var filteredList by remember {
        mutableStateOf<List<ProductEntity>>(emptyList())
    }

    filteredList = run {
        val filtered = ArrayList<ProductEntity>()
        vm.products.value.forEach {
            if (it.name.contains("a")) filtered.add(it)
        }
        filtered
    }

    LaunchedEffect(vm.buttonState) {}

    TestTheme {
        Surface(
                modifier = Modifier.fillMaxSize(),
                color = MaterialTheme.colorScheme.background
        ) {
            Button(onClick = { // temporary print
                counter += 1
                vm.updateProducts(counter)
                Log.i("CEtest", // print out the filtered list
                      buildString {
                          filteredList.forEach {
                              append("filteredList = $it \n")
                          }
                      })
                vm.buttonState = vm.buttonState == false
                }) {
                Text(text = filteredList[0].name) 
            }

        }
    }
}

This works perfectly until either the LaunchedEffect is removed or the button state is not changed. At that point nothing works – the button text is not updated and the filtered list is not changed.

I cannot see how this is occurring and why I need the button state LaunchedEffect to trigger the rest of the process.

Any help you can give would be gratefully appreciated.

Thanks

>Solution :

Consider making products a MutableState variable in the ViewModel itself. This way, Compose can directly observe changes to this state and trigger recomposition accordingly.

class TestGlobalViewModel : ViewModel() {

    // ...

    var products by mutableStateOf(localList)
        private set

    fun updateProducts(counter: Int) {
        // ... (no need to create a new mutableStateOf)
        products = localList2
    }
}

If you’re using Jetpack Compose with ViewModels, it’s recommended to use LiveData in your ViewModel and observe it in Compose. This way, Compose can automatically handle the observation and recomposition.

class TestGlobalViewModel : ViewModel() {

    private val _products = MutableLiveData<List<ProductEntity>>(localList)
    val products: LiveData<List<ProductEntity>> get() = _products

    fun updateProducts(counter: Int) {
        // ...
        _products.value = localList2
    }
}

In your Composable:

val products by vm.products.observeAsState(emptyList())
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading