Ojav
Ojav

Reputation: 980

How To Change a State from Another Thread to force a recomposition in Jetpack Compose?

I have a simple Text Element where I want to display the current time.

I tried to start a periodically Timer Task but it says APP STOPPED WORKING. What is the correct approach to periodically update my var currentTime by mutableStateOf("12:00") inside another Thread so it forces a correct recomposition without crashing?.

class MyViewModel : ViewModel() {
    var currentTime by mutableStateOf("12:00")

    init {
        val clockTimer = Timer()
        clockTimer.scheduleAtFixedRate(object : TimerTask() {
            override fun run() {
                val hoursAndMinutes = SimpleDateFormat("HH:mm", Locale.US).format(Date())
                Log.d("DEBUG", "Current Time is: $hoursAndMinutes")
                currentTime = hoursAndMinutes
            }
         }, 0, 60000)
    }
}

@Preview
@Composable
fun ClockView(myViewModel: MyViewModel = viewModel()){
    Text(
        text = myViewModel.currentTime
    )
}

EDIT1: ADDED STACKTRACE

FATAL EXCEPTION: Timer-0
   Process: com.myapp.c, PID: 15437
        java.lang.IllegalStateException: Reading a state that was created after the snapshot was taken or in a snapshot that has not yet been applied
            at androidx.compose.runtime.snapshots.SnapshotKt.readError(Snapshot.kt:1857)
            at androidx.compose.runtime.snapshots.SnapshotKt.current(Snapshot.kt:2110)
            at androidx.compose.runtime.SnapshotMutableStateImpl.setValue(SnapshotState.kt:299)
            at com.myapp.c.ui.MyViewModel.setCurrentTime(Test.kt:41)
            at com.myapp.c.ui.MyViewModel$1.run(Test.kt:25)
            at java.util.TimerThread.mainLoop(Timer.java:562)
            at java.util.TimerThread.run(Timer.java:512) 

Upvotes: 1

Views: 2286

Answers (1)

Himanshu Bansal
Himanshu Bansal

Reputation: 306

The Snapshots are transactional, hence needed to be run on main thread here, And cannot be updated from another thread except main. Hence to update/read the value you need to be on UI thread, i.e. main thread.

run this currentTime = hoursAndMinutes on main thread, by wrapping it in coroutine, like

viewModelScope.launch {
    currentTime = hoursAndMinutes
}

As viewModelScope is bound to Dispatchers.Main.immediate, it will run on main thread.

Upvotes: 2

Related Questions