Gon3s
Gon3s

Reputation: 177

I don't understand why Jetpack Compose don't recompose when I change list into a mutableStateOf?

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

Answers (1)

Tonnie
Tonnie

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

Related Questions