Home > Software design >  view doesn't get recomposes when mutable list changes
view doesn't get recomposes when mutable list changes

Time:10-12

I have a application that has a statelist in the viewmodel. and i have function that adds a number in the list. when i click on a button, it adds the number in the list. but the changes is not getting reflected in another view.

MainActivity.kt

class MainActivity : ComponentActivity() {

    private val viewModel by viewModels<MainViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApplicationTheme {
                // A surface container using the 'background' color from the theme
                Surface(color = MaterialTheme.colors.background) {

                    Column {
                        Button(onClick = {viewModel.increaseCounter()}) {
                            Text("Increase counter")
                        }
                        CountView(viewModel.counter)
                    }
                }
            }
        }
    }
}

@SuppressLint("CoroutineCreationDuringComposition")
@Composable
fun CountView(count: List<Int>) {
    var coroutineScope = rememberCoroutineScope()

    coroutineScope.launch {
        Log.d("inside the coroutine ${count}")
    }
}

MainViewModel.kt

class MainViewModel: ViewModel() {

    var counter = mutableStateListOf<Int>()

    fun increaseCounter() {
        Log.d(">>>", "in viewmodel ${counter.size}")
        counter.add(1)
    }
}

Desired Result: Whenever i click on the button the log has to printed because it adds a number in the mutableStateList.

But when i change the mutableStateListOf into mutableStateOf and store some integer and change the integer the view gets recomposes and prints the log when i click on the button

Please help! Thanks in advance.

CodePudding user response:

the statement must be on composable fun, and use stateflow or livedata to get value from viewmodel:

MainActivity.kt

class MainActivity : ComponentActivity() {

    private val viewModel by viewModels<MainViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApplicationTheme {
                // collect value from counter as state
                val counter by viewModel.counter.collectAsState()
                
                Surface(color = MaterialTheme.colors.background) {
                    Column {
                        Button(onClick = {viewModel.increaseCounter()}) {
                            Text("Increase counter")
                        }
                        CountView(counter)
                    }
                }
            }
        }
    }
}

MainViewModel.kt

class MainViewModel: ViewModel() {

    private val _counter = MutableStateFlow<Int>(0)
    val counter: StateFlow<Int> get() = _counter

    fun increaseCounter() {
        Log.d(">>>", "in viewmodel ${counter.size}")
        _counter.value  = 1
    }
}

CodePudding user response:

The main problem of your function not recomposing, is that you're not using the value for the view itself, so Compose caches it. If you add Text(count.joinToString { it.to() }) to your view, it'll will work.


But first of all you shouldn't use coroutines directly from composable functions. And adding @SuppressLint("CoroutineCreationDuringComposition") is not the correct solution to the error IDE shows to you:

Calls to launch should happen inside a LaunchedEffect and not composition

So instead your code should look like this:

@Composable
fun CountView(count: List<Int>) {
    LaunchedEffect(count) {
        println("inside the coroutine $count")
    }
}

This will also not work in your case, because with mutableStateListOf, LaunchedEffect compares key by pointer, and as this is still the same container, LaunchedEffect will not be relaunched. To compare by references it's cleaner to pass a plain list:

CountView(viewModel.counter.toList())

Note that inside LaunchedEffect you're already inside coroutine scope and can run suspend functions.

rememberCoroutineScope is usually used when you need to launch a coroutine from other side effects, like button click.

Read move about side effects in documentation

  • Related