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