WorkManager 2.10 (2024 Q4 stable), Android background task'ların altın standardı. JobScheduler, AlarmManager ve Firebase JobDispatcher için tek replacement API. Coroutines-first yaklaşım, expedited work desteği, Flow<WorkInfo> observation — hepsi mature. Bu yazı WorkManager 2.10'un modern pattern'lerini, Android 15 foreground service restriction'larıyla uyumu, testing stratejisini ve migration best practice'lerini anlatır.
💡 Pro Tip: WorkManager her background task için değil. Network sync, upload, periodic refresh için ideal. Real-time event için değil — Firebase Messaging push + WorkManager kombinasyonu.
İçindekiler
- WorkManager Nedir, Ne Zaman
- CoroutineWorker: Modern Worker
- Expedited Work (Android 12+)
- Constraints Sistemi
- Work Chaining
- Unique Work Policies
- Flow<WorkInfo> Observer
- Android 15 Compatibility
- Testing
- Sonuç
WorkManager Nedir, Ne Zaman
Kullan:
- Network sync (deferrable, periodic)
- Image/video upload
- Database cleanup (haftalık)
- Analytics batch send
- Offline queue processing
- ML model download
Kullanma:
- Real-time messaging (FCM)
- Active user interaction (UI thread'de async)
- Ultra-time-sensitive operations (<30s)
- Background audio playback (MediaSession)
CoroutineWorker: Modern Worker
kotlin
1import androidx.work.CoroutineWorker2import androidx.work.WorkerParameters3import kotlinx.coroutines.Dispatchers4import kotlinx.coroutines.withContext5 6class SyncDataWorker(7 context: Context,8 params: WorkerParameters9) : CoroutineWorker(context, params) {10 11 override suspend fun doWork(): Result = withContext(Dispatchers.IO) {12 try {13 val data = inputData.getString("user_id")14 ?: return@withContext Result.failure()15 16 // Network call with coroutine17 val users = userRepository.syncUsers(data)18 19 // Persist20 userDao.insertAll(users)21 22 // Return success data23 val output = workDataOf("synced_count" to users.size)24 Result.success(output)25 } catch (e: IOException) {26 Result.retry() // Exponential backoff27 } catch (e: Exception) {28 Result.failure()29 }30 }31}Enqueue
kotlin
1val syncRequest = OneTimeWorkRequestBuilder<SyncDataWorker>()2 .setInputData(workDataOf("user_id" to userId))3 .setConstraints(4 Constraints.Builder()5 .setRequiredNetworkType(NetworkType.CONNECTED)6 .setRequiresBatteryNotLow(true)7 .build()8 )9 .setBackoffCriteria(10 BackoffPolicy.EXPONENTIAL,11 WorkRequest.MIN_BACKOFF_MILLIS,12 TimeUnit.MILLISECONDS13 )14 .build()15 16WorkManager.getInstance(context).enqueue(syncRequest)Expedited Work (Android 12+)
Kritik user-perceivable task'lar için:
kotlin
1val urgentSync = OneTimeWorkRequestBuilder<UploadWorker>()2 .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)3 .build()4 5WorkManager.getInstance(context).enqueue(urgentSync)Expedited work:
- Battery optimization bypass
- Doze mode'da çalışabilir (Android 12+)
- Quota-limited (günlük kotalar)
- Quota bittiğinde policy'ye göre fallback
Foreground Service Notification
kotlin
1class UploadWorker(ctx: Context, params: WorkerParameters) :2 CoroutineWorker(ctx, params) {3 4 override suspend fun getForegroundInfo(): ForegroundInfo {5 val notification = NotificationCompat.Builder(applicationContext, "upload_channel")6 .setContentTitle("Dosya yükleniyor")7 .setSmallIcon(R.drawable.ic_upload)8 .setOngoing(true)9 .build()10 11 return ForegroundInfo(12 NOTIFICATION_ID,13 notification,14 ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC15 )16 }17 18 override suspend fun doWork(): Result {19 setForeground(getForegroundInfo()) // Foreground service başlat20 // Upload work21 return Result.success()22 }23}Constraints Sistemi
kotlin
1val constraints = Constraints.Builder()2 .setRequiredNetworkType(NetworkType.UNMETERED) // Wi-Fi only3 .setRequiresCharging(true) // Charging4 .setRequiresBatteryNotLow(true) // Pil > %155 .setRequiresDeviceIdle(true) // Doze mode6 .setRequiresStorageNotLow(true) // Storage yeterli7 .build()8 9val request = OneTimeWorkRequestBuilder<HeavyUploadWorker>()10 .setConstraints(constraints)11 .build()Tüm constraint'ler MET olunca work çalışır. Değilse sessizce bekler.
Work Chaining
Sıralı task'lar:
kotlin
1WorkManager.getInstance(context)2 .beginWith(compressWork)3 .then(uploadWork)4 .then(notifyWork)5 .enqueue()6 7// Veya parallel + sequential karışık8WorkManager.getInstance(context)9 .beginWith(listOf(compressImageWork, compressVideoWork)) // parallel10 .then(uploadWork) // await both, then upload11 .enqueue()Data Passing
kotlin
1// Worker 1'in output'ı2override suspend fun doWork(): Result {3 val output = workDataOf("compressed_path" to "/tmp/img.jpg")4 return Result.success(output)5}6 7// Worker 2 input'u automatically worker 1'in output'u8override suspend fun doWork(): Result {9 val path = inputData.getString("compressed_path") ?: return Result.failure()10 // Upload path11}Unique Work Policies
Duplicate prevention:
kotlin
1WorkManager.getInstance(context).enqueueUniqueWork(2 "user_sync_job", // Unique name3 ExistingWorkPolicy.REPLACE, // KEEP, REPLACE, APPEND, APPEND_OR_REPLACE4 syncRequest5)- KEEP: Mevcut varsa yeni'yi ignore et
- REPLACE: Eski'yi cancel + yeni'yi çalıştır
- APPEND: Sıraya ekle (chain)
- APPEND_OR_REPLACE: Append, fail olanı replace
Periodic work:
kotlin
1val periodicRequest = PeriodicWorkRequestBuilder<SyncWorker>(2 15, TimeUnit.MINUTES, // repeat interval3 5, TimeUnit.MINUTES // flex period4).build()5 6WorkManager.getInstance(context).enqueueUniquePeriodicWork(7 "periodic_sync",8 ExistingPeriodicWorkPolicy.UPDATE, // UPDATE, KEEP9 periodicRequest10)Min interval: 15 dakika.
Flow<WorkInfo> Observer
Modern reactive monitoring:
kotlin
1class SyncViewModel : ViewModel() {2 val syncState: Flow<WorkInfo> = WorkManager3 .getInstance(context)4 .getWorkInfoByIdFlow(syncRequest.id)5 6 fun enqueueSync() = viewModelScope.launch {7 syncState.collect { workInfo ->8 when (workInfo.state) {9 WorkInfo.State.ENQUEUED -> showQueuedUi()10 WorkInfo.State.RUNNING -> showRunningUi()11 WorkInfo.State.SUCCEEDED -> {12 val count = workInfo.outputData.getInt("synced_count", 0)13 showSuccess(count)14 }15 WorkInfo.State.FAILED -> showError()16 WorkInfo.State.CANCELLED -> showCancelled()17 else -> {}18 }19 }20 }21}Compose UI:
kotlin
1@Composable2fun SyncScreen(vm: SyncViewModel = viewModel()) {3 val state by vm.syncState.collectAsState(initial = null)4 5 Column {6 when (state?.state) {7 WorkInfo.State.RUNNING -> CircularProgressIndicator()8 WorkInfo.State.SUCCEEDED -> Text("✓ Senkronize edildi")9 WorkInfo.State.FAILED -> Text("✗ Hata")10 else -> Button(onClick = { vm.enqueueSync() }) { Text("Sync") }11 }12 }13}Android 15 Compatibility
Android 15 foreground service strict mode:
setExpeditedkullan — Doze bypass içinforegroundServiceTypebelirt (dataSync, location, etc.)- Manifest'e permission ekle
setForegroundçağrısınıdoWorkiçinde yap
xml
1<!-- AndroidManifest.xml -->2<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />3<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />4 5<service6 android:name="androidx.work.impl.foreground.SystemForegroundService"7 android:foregroundServiceType="dataSync"8 tools:node="merge" />Testing
kotlin
1// Test harness2@RunWith(AndroidJUnit4::class)3class SyncWorkerTest {4 private lateinit var context: Context5 6 @Before7 fun setup() {8 context = ApplicationProvider.getApplicationContext()9 val config = Configuration.Builder()10 .setMinimumLoggingLevel(Log.DEBUG)11 .setExecutor(SynchronousExecutor())12 .build()13 WorkManagerTestInitHelper.initializeTestWorkManager(context, config)14 }15 16 @Test17 fun testSyncSuccess() = runTest {18 val worker = TestListenableWorkerBuilder<SyncWorker>(context)19 .setInputData(workDataOf("user_id" to "123"))20 .build()21 22 val result = worker.doWork()23 assertEquals(Result.success(), result)24 }25}ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
Okuyucu Ödülü
kotlin
// WorkManagerModule.kt (Hilt)@Module@InstallIn(SingletonComponent::class)object WorkManagerModule { @Provides @Singleton fun provideWorkManager( @ApplicationContext context: Context ): WorkManager = WorkManager.getInstance(context) @Provides @IntoMap @WorkerKey(SyncDataWorker::class) fun provideSyncWorker( userRepository: UserRepository ): ChildWorkerFactory = object : ChildWorkerFactory { override fun create(context: Context, params: WorkerParameters): ListenableWorker { return SyncDataWorker(context, params, userRepository) } }} // HiltWorkerFactory setup for DIHilt + WorkManager entegrasyonu ile dependency injection temiz.
External Resources:
Sonuç
WorkManager 2.10 Android background task'ın olgun, production-ready API'si. Coroutines-first, Flow<WorkInfo> reactive, expedited work kritik ihtiyaçlar için. Android 15 strict mode uyumlu. Migration JobScheduler/AlarmManager'dan 1-2 hafta; ROI yüksek (daha az battery drain, daha güvenilir). Next-gen work scheduling için hala geçerli olan standard.
*İlgili yazılar: [Compose 1.7](./jetpack-compose-1-7-performance-strong-skipping), [Kotlin 2.1](./kotlin-2-1-k2-compiler-context-parameters), [Android 15](./android-15-developer-privacy-sandbox-edge-foreground).*

