Reputation: 177
I try to create an quiz app with Jetpack Compose but I need some response to understand how Jetpack Compose recompose after some change into a mutableStateOf.
After choosing a response I call the function into my viewModel to change the state of response to change background color. But if I don't add this code
state = state.copy(
isLoading = true
)
The background color doesn't change, why ?
Here complete code of Composable question's screen :
@Composable
fun QuizzScreen(
viewModel: QuizzViewModel,
modifier: Modifier = Modifier
) {
val state = remember {
viewModel.state
}
Box(
modifier = modifier.fillMaxSize(),
contentAlignment = Center
) {
if (state.isLoading) {
CircularProgressIndicator()
} else if (state.error != null) {
Text(
text = state.error,
color = MaterialTheme.colors.error
)
} else {
Column(
modifier = modifier
.fillMaxWidth()
.padding(16.dp)
) {
Text(
text = Html.fromHtml(state.question, Html.FROM_HTML_MODE_LEGACY).toString(),
color = MaterialTheme.colors.onSurface,
fontSize = 32.sp,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
)
Spacer(modifier = Modifier.height(48.dp))
for (response: Response in state.responses) {
Response(response) {
viewModel.checkAnwser(response)
}
}
}
}
}
}
@Composable
fun Response(
response: Response,
onClick: () -> Unit
) {
Spacer(modifier = Modifier.height(24.dp))
Button(
colors = ButtonDefaults.buttonColors(backgroundColor = if (response.state == ResponseStatus.Init) MaterialTheme.colors.surface else if (response.state == ResponseStatus.Correct) Color.Green else Color.Red),
onClick = onClick
) {
Text(
text = Html.fromHtml(response.response, Html.FROM_HTML_MODE_LEGACY).toString(),
fontSize = 24.sp,
textAlign = TextAlign.Center,
color = Color.White,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
)
}
}
And now my ViewModel
class QuizzViewModel(
val repository: QuizzRepository
) : ViewModel() {
private val _isLoading = MutableStateFlow(true)
var isLoading = _isLoading.asStateFlow()
var state by mutableStateOf(QuizzState())
init {
viewModelScope.launch {
loadQuizz()
}
}
suspend fun loadQuizz() {
state = state.copy(isLoading = true)
when (val quizzLocal = repository.getLocalQuizz()) {
is Resource.Success -> {
val quizz: Quizz = quizzLocal.data!!
state = state.copy(
question = quizz.question,
responses = quizz.responses.map { response ->
Response(
response,
ResponseStatus.Init
)
},
correctAnswer = quizz.correctAnswer,
isLoading = false
)
}
is Resource.Error -> {
state = state.copy(
isLoading = false,
error = quizzLocal.message!!
)
}
else -> Unit
}
_isLoading.value = false
}
fun checkAnwser(response: Response) {
val correctAnwser = (response.response == state.correctAnswer)
state.responses.map {
if (!correctAnwser && it == response) {
it.state = ResponseStatus.Error
}
if (it.response == state.correctAnswer) {
it.state = ResponseStatus.Correct
}
}
// If i comment this,
// not recompose
state = state.copy(
isLoading = true
)
}
}
Upvotes: 2
Views: 1165
Reputation: 8102
I think the problem is that you are caching ViewModel's state by using remember{viewModel.state}
@Composable
fun QuizzScreen(
viewModel: QuizzViewModel,
modifier: Modifier = Modifier
) {
val state = remember {
viewModel.state
}
Using remember{}
caches the state to the default value which makes the state remain unchanged even with recomposition.
Instead use val state = viewModel.state
Upvotes: 3