WorkManager keep notification after work is done

Viewed 1736

I want to display a notification while a Worker is running in the background. I can do that with something like the code below:

override suspend fun doWork(): Result {
    val manager = NotificationManagerCompat.from(applicationContext)
    val channel = "some_channel"
    val id = 15
    val builder = NotificationCompat.Builder(applicationContext, channel)

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
        manager.createNotificationChannel(NotificationChannel(channel, "DefaultName", NotificationManager.IMPORTANCE_LOW))

    builder
        .setOnlyAlertOnce(true)
        .setOngoing(true)
        .setAutoCancel(false)
        .setPriority(NotificationCompat.PRIORITY_HIGH)
        .setSmallIcon(android.R.drawable.ic_dialog_alert)
        .setTicker("Background Task")
        .setContentText("Starting")
        .setProgress(0, 0, true)

    setForeground(ForegroundInfo(id, builder.build()))

    delay(500)

    manager.notify(id, builder.setContentText("Progress A").setProgress(20, 0, false).build())
    for (i in 1..20) {
        delay(100)
        manager.notify(id, builder.setProgress(20, i, false).build())
    }

    delay(500)

    manager.notify(id, builder.setContentText("Progress B").setProgress(2, 0, false).build())
    delay(1000)
    manager.notify(id, builder.setProgress(2, 1, false).build())
    delay(1000)
    manager.notify(id, builder.setProgress(2, 2, false).build())

    delay(1000)

    manager.notify(id, builder.setContentText("Done!").setProgress(0, 0, false).build())

    delay(500)

    return Result.success()
}

But I also want to display the result of the worker in the notification and keep it until the user sees and clears it, yet the notification is always cleared at the end of the work.

How can I keep the notification alive? .setOngoing(true) and .setAutoCancel(false) didn't help.

2 Answers

A much simpler way I think I found is to either notify the final notification with a different id (use .setOngoing(false)):

manager.notify(
    id + 1,
    builder.setContentText("Done!")
        .setAutoCancel(true)
        .setOngoing(false)
        .build()
)
return Result.success()

Or, send it after a bit of delay:

CoroutineScope(Main).launch {
    delay(200)
    manager.notify(
        id,
        builder.setContentText("Done!")
            .setAutoCancel(true)
            .setOngoing(false)
            .build()
    )
}
return Result.success()

A workaround that I found, is to have a BroadcastReceiver that takes the final notification and shows it. This is possible because Notification extends Parcelable. Note: have the NotificationReceiver use a different notification id than the one your Worker is using, otherwise the Worker will close the notification.

class NotificationReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action == ACTION_SEND_NOTIFICATION) {
            val notification = intent.getParcelableExtra<Notification>(EXTRA_NOTIFICATION)
            val id = intent.getIntExtra(EXTRA_ID, DEFAULT_ID)
            if (notification == null)
                Log.e(TAG, "EXTRA_NOTIFICATION ($EXTRA_NOTIFICATION) was not provided!")
            else
                NotificationManagerCompat.from(context).notify(id, notification)
        }
    }

    companion object {
        const val TAG = "NotificationReceiver"

        const val ACTION_SEND_NOTIFICATION = "intent.action.SEND_NOTIFICATION"

        const val EXTRA_NOTIFICATION = "notification_parcel"
        const val EXTRA_ID = "notification_id"
        const val DEFAULT_ID = 1010
    }

}
<receiver android:name=".NotificationReceiver" android:enabled="true" />
    // At the end of doWork, before returning
    applicationContext.sendBroadcast(
        Intent(applicationContext, NotificationReceiver::class.java).setAction(
            NotificationReceiver.ACTION_SEND_NOTIFICATION
        ).putExtra(NotificationReceiver.EXTRA_NOTIFICATION, builder.build())
    )
Related