Reputation: 85
I am new to Jetpack Compose and I just started trying it out by creating a timer application but I ran into a problem.
In my application each task has a different duration and after one of them is finished (the time has elapsed) then the next one should start. My problem is that my app works correctly only for the first task. After the first task is finished and the second one should be displayed the task title and description change but the timer's countdown value remains 0, it doesn't update from the previous state.
The onFinished calls one of the viewmodel's method that will fetch the next task that should be displayed and loads it into a livedata. I observe the changes on this livedata the following way:
val task = viewModel.currentTask.observeAsState().value
This task has a duration field that I pass to this Timer composable but when this task will be updated (because the livedata has a new value) the Timer composable doesn't recognize these changes. It doesn't restart the countdown, it stays 0.
I am not sure if I am understanding and using correctly the MutableState concept so can someone please help me?
@Composable
fun Timer(duration: Long, onFinished: () -> Unit) {
var currentTimerValue by remember { mutableStateOf(duration) }
LaunchedEffect(key1 = currentTimerValue) {
if (currentTimerValue > 0) {
delay(1000L)
currentTimerValue--
} else {
onFinished.invoke()
}
}
Text(text = currentTimerValue.toString(), fontSize = 24.sp, color = Color.White)
}
Upvotes: 8
Views: 3976
Reputation:
The accepted approach by @Thracian will only work if duration has a different value. If you try to reset your timer back to the same value, it won't work. Here I propose a re-work of the timer (this is taken from my perosnal project):
@Composable
fun CountdownTimer(
data: CountdownTimerRelevantData,
onEventFromViewModel: Flow<PuzzleScreenEventViewModelToScreen>,
modifier: Modifier = Modifier
) {
var remainingSeconds by remember { mutableIntStateOf(0) }
var startCountdown by remember { mutableStateOf(false) }
ObserveAsEvents(onEventFromViewModel) { event ->
if (event is ShowNextPuzzleEvent) {
startCountdown = true
}
}
LaunchedEffect(startCountdown) {
startCountdown = false
remainingSeconds = data.initialTime
while(remainingSeconds > 0) {
delay(1.seconds)
remainingSeconds--
onEventToViewModel(CheckVictoryConditionEvent)
}
}
Text(
text = remainingSeconds.toTimeFormattedMinutesString()
+ ":"
+ remainingSeconds.toTimeFormattedSecondsString(),
modifier = modifier
)
}
data class CountdownTimerRelevantData(
val initialTime: Int
)
You can use shared flow or event for the event flow from the viewmodel. Simply emit the event from your viewmodel.
Upvotes: 0
Reputation: 66674
The issue is here
var currentTimerValue by remember { mutableStateOf(duration) }
After Timer composable enters composition the block inside remember is initilized and it doesn't change unless you reset it with new key.
You can update it as
var currentTimerValue by remember(duration) { mutableStateOf(duration) }
which will reset to duration every time duration param of Timer function changes
Upvotes: 14