I am developing an android app using Jetpack Compose and have a timer implemented using launchedEffect here is some dummy code for clearance
LaunchedEffect(key1 = timeLeft) {
if(timeLeft > 0) {
delay(100L)
timeLeft -= 100L
}
}
my problem is that when the app is in the background the LaunchedEffect stops running and the timer is "stuck" on the same value until I return to the app
CodePudding user response:
You have several options to run a background task.
- Alarms
- Workmanager
- Services
- Coroutines
First of all you might not need a background timer. You only need to remember start time and then show the timer when you are drawing ui.
If you need to do something after a period of time, and you know exactly when, use an alarm.
If you need this timer going all the time even with the app closed, consider using a foreground service.
If it is ok to stop the timer when the app is cleared from memory you can use a viewModel.
The best match to your case will be a viewmodel solution.
Here is the sample made from Empty Compose Activity Template:
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.viewmodel.compose.viewModel
import ru.makproductions.timerapp.ui.theme.TimerAppTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
TimerAppTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
val viewModel = viewModel(MainViewModel::class.java)
val state = viewModel.state.collectAsState()
LaunchedEffect(key1 = true, block = { viewModel.startTimer() })
Column(modifier = Modifier.fillMaxSize()) {
Text("Timer going: ${state.value.currentTime}")
Button(onClick = {
if (state.value.isTimerGoing) {
viewModel.stopTimer()
} else {
viewModel.startTimer()
}
}) {
if (state.value.isTimerGoing) {
Text(text = "Stop timer")
} else {
Text(text = "Start timer")
}
}
}
}
}
}
}
}
data class MainViewState(
val currentTime: Int = 0,
val isTimerGoing: Boolean = false
)
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
class MainViewModel : ViewModel() {
val state = MutableStateFlow<MainViewState>(MainViewState())
fun startTimer() {
state.tryEmit(state.value.copy(isTimerGoing = true))
CoroutineScope(Dispatchers.IO).launch {
while (state.value.isTimerGoing) {
withContext(Dispatchers.Main) {
state.tryEmit(state.value.copy(currentTime = state.value.currentTime 1))
}
delay(1000)
}
}
}
fun stopTimer() {
state.tryEmit(state.value.copy(isTimerGoing = false))
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'androidx.activity:activity-compose:1.3.1'
implementation "androidx.compose.ui:ui:$compose_ui_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version"
implementation 'androidx.compose.material:material:1.1.1'
//Add this line to get viewModel(...) in your composable
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1")
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_ui_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version"
}
Some docs:
Here is the official guide to background work - https://developer.android.com/guide/background
And here is documentation for the services - https://developer.android.com/guide/components/services
CodePudding user response:
Try to pass the key which will not change to the LaunchedEffect.