WorkManager a Notifikácie na pozadí

1. Úvod do WorkManagera

WorkManager je knižnica určená na plánovanie odložiteľných a garantovaných úloh na pozadí. Zabezpečuje vykonanie práce aj vtedy, keď je aplikácia zatvorená alebo zariadenie reštartované. Je ideálny na pravidelnú kontrolu dát na serveri, synchronizáciu databázy a následné zobrazenie lokálnej notifikácie používateľovi priamo z behu úlohy na pozadí.

2. Potrebné závislosti (Version Catalog & build.gradle.kts)

1. Do súboru gradle/libs.versions.toml pridajte definíciu WorkManagera:

[versions]
work = "2.9.0"

[libraries]
androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work" }

2. Následne do súboru app/build.gradle.kts pridajte závislosť:

dependencies {
    implementation(libs.androidx.work.runtime.ktx)
}

3. Povolenia a manifest (AndroidManifest.xml)

Od verzie Android 13 (API 33) je na zobrazenie notifikácií potrebné povolenie. Pridajte ho do AndroidManifest.xml:

<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

V Jetpack Compose vyžiadate toto povolenie od používateľa priamo v composable obrazovke pred plánovaním prác:

val permissionLauncher = rememberLauncherForActivityResult(
    contract = ActivityResultContracts.RequestPermission(),
    onResult = { isGranted ->
        if (isGranted) {
            // Povolenie udelené
        }
    }
)

LaunchedEffect(Unit) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
    }
}

4. Vytvorenie Notification Channel (Kanála oznámení)

Pre Android 8.0+ (API 26+) musí každá notifikácia patriť do kanála. Kanál je najlepšie zaregistrovať pri štarte aplikácie (v triede Application alebo v MainActivity.onCreate()):

fun createNotificationChannel(context: Context) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val channelId = "sync_channel_id"
        val name = "Kontrola úloh"
        val descriptionText = "Oznámenia o dokončení úloh na pozadí"
        val importance = NotificationManager.IMPORTANCE_DEFAULT

        val channel = NotificationChannel(channelId, name, importance).apply {
            description = descriptionText
        }

        val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        notificationManager.createNotificationChannel(channel)
    }
}

5. Trieda Worker a zobrazenie notifikácie

Úloha na pozadí dedí z triedy CoroutineWorker. V metóde doWork() prebehne požadovaná logika a v prípade úspechu sa zostaví a odošle notifikácia cez NotificationCompat.Builder:

import android.Manifest
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters

class NotificationWorker(
    private val context: Context,
    workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {

    override suspend fun doWork(): Result {
        // 1. Vykonanie potrebnej práce na pozadí (napr. načítanie z databázy)
        val title = inputData.getString("title") ?: "Nová správa"
        val message = inputData.getString("message") ?: "Nezabudnite dnes na Vašu výzvu."

        // 2. Zobrazenie notifikácie
        sendNotification(title, message)

        return Result.success()
    }

    private fun sendNotification(title: String, message: String) {
        val channelId = "sync_channel_id"

        // Kliknutie otvorí hlavnú Compose aktivitu
        val intent = Intent(context, MainActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        }
        val pendingIntent = PendingIntent.getActivity(
            context,
            0,
            intent,
            PendingIntent.FLAG_IMMUTABLE
        )

        val builder = NotificationCompat.Builder(context, channelId)
            .setSmallIcon(R.drawable.ic_notification) // Nahraďte vlastnou ikonou zo zložky res/drawable
            .setContentTitle(title)
            .setContentText(message)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setContentIntent(pendingIntent)
            .setAutoCancel(true)

        // Kontrola povolenia pred zobrazením
        if (ActivityCompat.checkSelfPermission(
                context,
                Manifest.permission.POST_NOTIFICATIONS
            ) == PackageManager.PERMISSION_GRANTED
        ) {
            NotificationManagerCompat.from(context).notify(1001, builder.build())
        }
    }
}

6. Spustenie a naplánovanie práce z aplikácie

V Compose UI alebo vo ViewModeli môžete naplánovať jednorazovú alebo opakovanú prácu a voliteľne jej odovzdať vstupné parametre:

// Nastavenie obmedzení (napr. iba pri pripojení na internet)
val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .build()

// Vstupné dáta pre Worker
val inputData = workDataOf(
    "title" to "Aktualizácia dát",
    "message" to "Dáta boli úspešne stiahnuté na pozadí."
)

// Zostavenie jednorazovej požiadavky
val workRequest = OneTimeWorkRequestBuilder<NotificationWorker>()
    .setConstraints(constraints)
    .setInputData(inputData)
    .build()

// Zaradenie do frontu WorkManagera
WorkManager.getInstance(context).enqueue(workRequest)

Pre periodické spúšťanie (napr. každé 2 hodiny):

val repeatingRequest = PeriodicWorkRequestBuilder<NotificationWorker>(
    2, TimeUnit.HOURS
).setInputData(inputData).build()

WorkManager.getInstance(context).enqueueUniquePeriodicWork(
    "PeriodicSyncWork",
    ExistingPeriodicWorkPolicy.KEEP,
    repeatingRequest
)

7. Spustenie a inicializácia priamo z MainActivity (Jetpack Compose)

Ukážka kompletnej MainActivity, ktorá pri štarte inicializuje kanál, v Composable požiada o povolenie a po kliknutí na tlačidlo naplánuje vykonanie Workera[cite: 2, 20]:

import android.Manifest
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.work.*

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 1. Vytvorenie kanála pri štarte aplikácie
        createNotificationChannel(this)

        setContent {
            NotificationScreen()
        }
    }
}

@Composable
fun NotificationScreen() {
    val context = LocalContext.current

    // 2. Žiadosť o povolenie na zobrazenie notifikácií (Android 13+)
    val permissionLauncher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.RequestPermission(),
        onResult = { isGranted ->
            // Spracovanie výsledku povolenia
        }
    )

    LaunchedEffect(Unit) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
        }
    }

    // 3. UI s tlačidlom na spustenie WorkManagera
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Button(
            onClick = {
                val inputData = workDataOf(
                    "title" to "Zadanie dokončené",
                    "message" to "Práca z WorkManagera bola úspešne vykonaná."
                )

                val workRequest = OneTimeWorkRequestBuilder<NotificationWorker>()
                    .setInputData(inputData)
                    .build()

                // Spustenie cez WorkManager pomocou kontextu z LocalContext
                WorkManager.getInstance(context).enqueue(workRequest)
            }
        ) {
            Text("Spustiť úlohu a zobraziť notifikáciu")
        }
    }
}