Morphois
Morphois

Reputation: 21

Android (Compose) -> MutableStateOf Changes do not update the UI

I'm struggling with my app for university and I can't find out why it behaves like it does. I have a EventDetail screen on which the user can join an event. If he does, he gets added to the MutableList Members. I implemented a Membercount on the detail Screen aswell as an if statement to switch the button to leave if the user is already member of the event. Below, you can find a shortened example of the problem. Can somebody tell me what I am doing wrong?

ViewModel

class EventsViewModel(private val eventRepository: 
EventRepository = EventRepository()) :
ViewModel() {

var eventList = mutableStateOf<MutableList<Event>>(mutableListOf())
var currentEvent = mutableStateOf<Event?>(null)


fun joinEvent() {
    if (currentEvent.value == null) return

    viewModelScope.launch {
        withContext(Dispatchers.IO) {

            val event = eventRepository.joinEvent(currentEvent.value!!)

            currentEvent.value = event
        }
    }
}
}

Composable

fun EventDetailScreen(navController: NavController, eventId: String) {
val viewModel: EventsViewModel = viewModel()
val userViewModel: userViewModel = viewModel()


viewModel.getEvent(eventId)

val event by remember {
    viewModel.currentEvent
}

if (event!!.createdBy != userViewModel.currentUser.value!!.uid)
if (event!!.members.find { it == userViewModel.currentUser.value!!.uid } == null)
    Button_Primary(
    onClick = { viewModel.joinEvent() },
    text = "Join Event",
)
else
    Button_Secondary(
    onClick = { viewModel.leaveEvent() },
    text = "Leave Event",
)`

Event

data class Event(
    val id: String?,
    val title: String,
    val description: String,
    val startDateTime: LocalDateTime,
    val endDateTime: LocalDateTime,
    val location: String?,
    val createdBy: String?,
    val createdAt: LocalDateTime = LocalDateTime.now(),
    var members: MutableList<String> = mutableListOf(),
    val currentIds: MutableList<String> = mutableListOf()
)

I already found, that members can be set to MutableStateOf<MutableList> but I thought, that this should only be done in the UI, as I don't want to send a mutableState to Firebase.

I would expect the button to switch to Leave Event after I joined and switch to Join if I left the event. But it only gets updated when I close the screen. I can see the button change to the right state 1 second before i finally leave the screen.

Upvotes: 1

Views: 1845

Answers (1)

Tenfour04
Tenfour04

Reputation: 93834

You can't make a working State out of a mutable type like MutableList or your Event class (which is mutable because it has some vars and MutableLists inside it). Compose cannot detect mutations to these mutable instances. (In general, even without Compose, you would pretty much never combine var with a mutable type either because it makes something mutable in two different ways, which is error-prone.) For lists, use mutableStateListOf instead of a List inside a MutableState. Inside your Event class, change the var to val and the MutableLists into read-only Lists.

You should also mark the Event class with @Immutable so the read-only Lists won't cause unnecessary recompositions. But it is up to you not to reuse MutableLists as arguments that you pass to the Event constructor, or you are breaking the @Immutable contract.

Finally, this code in your composable:

val event by remember {
    viewModel.currentEvent
}

means that event will always be whatever the value of viewModel.currentEvent was the first time the function was called, so it can never see newer values. Change it to:

val event by viewModel.currentEvent

You don't need to remember things that you retrieve from a ViewModel because the ViewModel outlives your UI.

Upvotes: 1

Related Questions