I want to develop an app that runs a timer on Android.
I would like it to function similarly to the default timer app on smartphones.
When the timer is running, it should display the decreasing time in the UI, and it should continue to function correctly even if the app is closed and reopened.
However, I am not sure how to maintain the state when the app is closed and reopened.
Initially, I implemented the timer app by setting the time using AlarmManager
so that an alarm would ring at the exact time.
However, when the app is closed and reopened, I couldn’t check the alarm registered with AlarmManager
. To address this, I used a separate DataStore
to save the state, but I thought this method wasn’t ideal. Additionally, with this method, I couldn’t display the decreasing timer state in the UI.
So currently, I am using WorkManager
. I used setForeground
to calculate the remaining time in the background.
There were unresolved issues while implementing the timer app using WorkManager
.
-
I implemented it to run for a long time using
setForeground
, but if the user arbitrarily stops the background service, the timer stops, and when the app is restarted, the work starts from the beginning. -
setBackoffCriteria
is set to default, so I don’t know how to prevent retries. -
In the case of the default timer app, the timer works normally even if the background service is arbitrarily stopped. I don’t know how they implemented it. Is there really a way? I’m curious how they did it.
-
For a timer app, I wonder if implementing it through WorkManager is a good pattern and if it is a commonly used structure.
Here is my current code.
class TimerWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val minutes = inputData.getInt("minutes", 0)
val notification = NotificationCompat
.Builder(applicationContext, NotificationHelper.CHANNEL_ID_TIMER_ALARM)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Backgrounding...")
.setOngoing(true)
.build()
val info = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
ForegroundInfo(999, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
} else {
ForegroundInfo(999, notification)
}
setForeground(info)
return try {
val endTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(minutes.toLong())
while (System.currentTimeMillis() < endTime) {
Thread.sleep(100)
println(endTime - System.currentTimeMillis())
}
Result.success()
} catch (e: InterruptedException) {
Result.failure()
}
}
}
fun scheduleTimerWork(context: Context, minutes: Int) {
val timerWorkRequest = OneTimeWorkRequestBuilder<TimerWorker>()
.setInputData(workDataOf("minutes" to minutes))
.build()
WorkManager.getInstance(context).enqueue(timerWorkRequest)
}
placeCover is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.