Scenario-Based Use Cases
Scenario 1: Music Player
Requirement
User starts a song, goes to another app, and music should continue.
Best Choice
Foreground ServiceWhy?
| Reason |
| ----------------------------------------- |
| Music is user-visible work |
| Music should continue in background |
| User should see notification |
| Notification can have play/pause controls |
Flow
User clicks Play
↓
Start Foreground MusicService
↓
Show music notification
↓
Start MediaPlayer
↓
User controls music from notificationScenario 2: Large Video Upload
Requirement
User uploads a large video. Upload should continue after leaving the screen.
Best Choice
| Case | Best Option |
|---|---|
| Upload is immediate and user-visible | Foreground Service |
| Upload can be delayed/retried | WorkManager |
Flow
User clicks Upload
↓
Create upload notification
↓
Start Foreground Service
↓
Upload file
↓
Update progress notification
↓
Upload complete
↓
Stop serviceScenario 3: Daily Data Sync
Requirement
App needs to sync data once every day in background.
Best Choice
WorkManagerWhy?
| Reason |
| --------------------------------------------- |
| Daily sync is deferrable |
| WorkManager handles retry |
| WorkManager handles network constraints |
| WorkManager works better with OS restrictions |
Flow
Schedule PeriodicWorkRequest
↓
Set network constraint
↓
Run once every 24 hours
↓
Sync data
↓
Retry if failedScenario 4: Location Tracking
Requirement
Fitness app tracks user running for 1 hour.
Best Choice
Foreground Service with location typeWhy?
| Reason |
| ------------------------------------- |
| Location tracking is ongoing |
| User must know location is being used |
| Work should continue in background |
| Notification is required |
Manifest Example
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<service
android:name=".LocationTrackingService"
android:exported="false"
android:foregroundServiceType="location" />Scenario 5: Bluetooth Device Connection
Requirement
Activity needs to communicate with a Bluetooth device continuously.
Best Choice
Bound ServiceUse Foreground Service also if Bluetooth communication should continue after app goes to background.
Flow
Activity binds to BluetoothService
↓
Activity calls connect()
↓
Service maintains Bluetooth socket
↓
Activity calls sendCommand()
↓
Service returns response
↓
Activity unbinds when screen closesScenario 6: Timer App
Requirement
User starts stopwatch and closes app. Timer should continue.
Best Choice
Foreground ServiceWhy?
| Reason |
| ---------------------------------- |
| Timer is ongoing |
| User-visible task |
| Notification can show elapsed time |
Scenario 7: Chat Message Notification
Requirement
App receives chat message from FCM.
Best Choice
| Task | Best Option |
|---|---|
| Simple notification | FirebaseMessagingService |
| Long processing | WorkManager |
| Download image/message sync | WorkManager |
Flow
FCM message received
|
v
Is it simple notification?
|
+----+----+
| |
Yes No
| |
v v
Show Schedule
Notification WorkManagerMini Example — Location Foreground Service
class LocationTrackingService : Service() {
override fun onCreate() {
super.onCreate()
createNotificationChannel()
}
override fun onStartCommand(
intent: Intent?,
flags: Int,
startId: Int
): Int {
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Tracking your run")
.setContentText("Location tracking is active")
.setSmallIcon(R.drawable.ic_location)
.setOngoing(true)
.build()
startForeground(101, notification)
startLocationUpdates()
return START_STICKY
}
private fun startLocationUpdates() {
// Request location updates from FusedLocationProviderClient
}
override fun onDestroy() {
super.onDestroy()
// Stop location updates here
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"Location Tracking",
NotificationManager.IMPORTANCE_LOW
)
getSystemService(NotificationManager::class.java)
.createNotificationChannel(channel)
}
}
companion object {
const val CHANNEL_ID = "location_channel"
}
}Mini Example — Started Service for Log Upload
class LogUploadService : Service() {
override fun onStartCommand(
intent: Intent?,
flags: Int,
startId: Int
): Int {
val logFile = intent?.getStringExtra("log_file")
Thread {
try {
uploadLogFile(logFile)
} finally {
stopSelf(startId)
}
}.start()
return START_REDELIVER_INTENT
}
private fun uploadLogFile(file: String?) {
Log.d("LogUploadService", "Uploading $file")
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}Why use stopSelf(startId)?
If multiple start requests come,
stopSelf(startId) stops the correct request safely.---
Mini Example — Bound Counter Service
CounterService.kt
class CounterService : Service() {
private val binder = CounterBinder()
private var counter = 0
inner class CounterBinder : Binder() {
fun getService(): CounterService {
return this@CounterService
}
}
override fun onBind(intent: Intent?): IBinder {
return binder
}
fun increment() {
counter++
}
fun getCounter(): Int {
return counter
}
}MainActivity.kt
class MainActivity : AppCompatActivity() {
private var service: CounterService? = null
private var bound = false
private val connection = object : ServiceConnection {
override fun onServiceConnected(
name: ComponentName?,
binder: IBinder?
) {
val counterBinder = binder as CounterService.CounterBinder
service = counterBinder.getService()
bound = true
}
override fun onServiceDisconnected(name: ComponentName?) {
bound = false
service = null
}
}
override fun onStart() {
super.onStart()
bindService(
Intent(this, CounterService::class.java),
connection,
Context.BIND_AUTO_CREATE
)
}
fun onButtonClick() {
service?.increment()
val value = service?.getCounter()
Log.d("Counter", "Value = $value")
}
override fun onStop() {
super.onStop()
if (bound) {
unbindService(connection)
bound = false
}
}
}