A worker has an application Context, which is all a notification needs. CatalogSyncWorker (CoroutineWorker) remembers the cached prices before it refreshes and compares them afterwards:
private suspend fun cachedBooks(): List<Book> = repo.observeBooks().firstOrNull().orEmpty()
/** 5.30.3 and 5.30.5 announce price drops, redraw the widget, return the book count. */
private suspend fun afterSync(before: List<Book>): Int {
val oldPrice = before.associate { it.id to it.price }
val books = cachedBooks()
books.filter { it.price < (oldPrice[it.id] ?: it.price) }.forEach {
BookNestNotifications.showPriceDrop(applicationContext, it, oldPrice.getValue(it.id))
}
BookNestWidget().updateAll(applicationContext) // every placed BookNest widget
return books.size
}To test it, a "sync now" (Constraints and Chaining) cached the test server's $18.90 for Salt and Saffron, the server's price was lowered to $15.50 (echo 15.50 > server/price), and a second "sync now" logged:
D/BookNestWork( 6396): sync attempt 1, id=a7a24c40 D/BookNestNet( 6396): refreshed 6 books from the network I/BookNestNotify( 6396): posted id=3 on channel deals D/BookNestWork( 6396): report: 6 books in the local catalog

Long workers, such as a large upload, call setForeground(ForegroundInfo(id, notification)) to run as a foreground service with an ongoing notification (Foreground Services); expedited work needs the same through getForegroundInfo() on Android 11 and earlier.