📱 Device Features · Lesson 38
WorkManager, Services & Broadcasts
Which tool for background work?
| Need | Use |
|---|---|
| Work while the screen is open (load data) | Coroutines in viewModelScope |
| Must finish even if the app closes: reminders, uploads, sync | WorkManager |
| User-visible ongoing task: music, navigation, workout tracking | Foreground Service |
| Exact clock time (alarm app) | AlarmManager (exact alarms) |
Setup
TOML + Kotlin DSL
# libs.versions.toml
work = "2.10.0"
androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work" }
// app/build.gradle.kts
implementation(libs.androidx.work.runtime.ktx)Step 1: Write a Worker
Kotlin – reminder/ReminderWorker.kt
package com.codingeasily.studentdiary.reminder
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.codingeasily.studentdiary.R
import com.codingeasily.studentdiary.data.AppDatabase
import com.codingeasily.studentdiary.data.SettingsStore
/** WorkManager runs this at the reminder time, even if the app was closed. */
class ReminderWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
val noteId = inputData.getLong(KEY_NOTE_ID, -1L)
if (noteId == -1L || !SettingsStore(applicationContext).remindersEnabled) return Result.success()
val note = AppDatabase.get(applicationContext).noteDao().getById(noteId)
?: return Result.success() // note was deleted
if (note.isDone) return Result.success()
NotificationHelper.show(
applicationContext,
note.id,
applicationContext.getString(R.string.reminder_title, note.title),
note.content.ifBlank { note.subject }
)
return Result.success()
}
companion object {
const val KEY_NOTE_ID = "note_id"
}
}| Return | Meaning |
|---|---|
| Result.success() | Done |
| Result.retry() | Try again later with backoff |
| Result.failure() | Give up |
Step 2: Schedule it
Kotlin – reminder/ReminderScheduler.kt
package com.codingeasily.studentdiary.reminder
import android.content.Context
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.workDataOf
import java.util.concurrent.TimeUnit
object ReminderScheduler {
private fun workName(noteId: Long) = "reminder_$noteId"
/** Schedules (or re-schedules) one reminder for a note. */
fun schedule(context: Context, noteId: Long, atMillis: Long) {
val delay = atMillis - System.currentTimeMillis()
if (delay <= 0) return
val request = OneTimeWorkRequestBuilder<ReminderWorker>()
.setInitialDelay(delay, TimeUnit.MILLISECONDS)
.setInputData(workDataOf(ReminderWorker.KEY_NOTE_ID to noteId))
.build()
WorkManager.getInstance(context)
.enqueueUniqueWork(workName(noteId), ExistingWorkPolicy.REPLACE, request)
}
fun cancel(context: Context, noteId: Long) {
WorkManager.getInstance(context).cancelUniqueWork(workName(noteId))
}
}Real example: note reminders
Kotlin
// NoteViewModel.save() – schedule or cancel when a note is saved val id = repo.save(note) val due = note.dueAt if (due != null && !note.isDone) ReminderScheduler.schedule(getApplication(), id, due) else ReminderScheduler.cancel(getApplication(), id)
Periodic work and constraints
Kotlin
class BackupWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
override suspend fun doWork(): Result = try {
uploadNotesToServer()
Result.success()
} catch (e: IOException) {
Result.retry() // WorkManager tries again later (backoff)
}
}
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED) // Wi-Fi only
.setRequiresCharging(true)
.build()
val daily = PeriodicWorkRequestBuilder<BackupWorker>(1, TimeUnit.DAYS)
.setConstraints(constraints)
.build()
WorkManager.getInstance(context)
.enqueueUniquePeriodicWork("daily_backup", ExistingPeriodicWorkPolicy.KEEP, daily)Observe work status
Kotlin
WorkManager.getInstance(requireContext())
.getWorkInfosForUniqueWorkLiveData("daily_backup")
.observe(viewLifecycleOwner) { infos ->
val state = infos.firstOrNull()?.state
binding.tvBackup.text = "Backup: ${state ?: "not scheduled"}" // ENQUEUED, RUNNING, SUCCEEDED…
}Android Studio → App Inspection → Background Task Inspector shows all scheduled and running work.
Foreground Service (music, tracking)
XML + Kotlin
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<service
android:name=".PlayerService"
android:exported="false"
android:foregroundServiceType="mediaPlayback" />
// PlayerService.kt
class PlayerService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification = NotificationCompat.Builder(this, "player")
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("Playing: Kotlin lecture 3")
.build()
startForeground(1, notification) // must be called within a few seconds
// … start playback
return START_NOT_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
}
// start it
ContextCompat.startForegroundService(this, Intent(this, PlayerService::class.java))Common mistakes
- Using a plain background Service for uploads → killed on Android 8+; use WorkManager.
- Non-unique work for reminders → duplicates after every edit; use
enqueueUniqueWorkwith REPLACE. - Passing big objects in input data (10 KB limit) → pass an id and read from Room.
- Foreground service without
foregroundServiceTypeon Android 14 → crash.
Summary
- WorkManager = guaranteed background work; CoroutineWorker for suspend code.
- OneTime with delay for reminders, Periodic (≥15 min) with constraints for sync.
- Foreground Service only for ongoing, user-visible tasks.
✅ Practice
Schedule a daily "Revision time!" notification at about 7 PM with PeriodicWorkRequest, and show its status (ENQUEUED/RUNNING) on the Settings screen.
Frequently Asked Questions
Does WorkManager run at the exact time?
Not exactly. WorkManager guarantees the work will run, but the system may delay it a little to save battery (Doze). For alarm-clock accuracy use AlarmManager.setExactAndAllowWhileIdle with the exact-alarm permission.
What is the minimum interval for periodic work?
15 minutes. Shorter intervals are rounded up to 15 minutes.
Does work survive a phone restart?
Yes. WorkManager stores scheduled work in its own database and reschedules it after reboot.
