Local notifications silently do nothing on Android: `add()` returns success with no delegate, and `NotificationWorker` throws on a missing icon resource
Nobody has claimed this yet.
- Dominant language
- Swift
- Stars
- 330
- Forks
- 76
- Avg merge
- 3h 6m
- Merged PRs (30d)
- 1
Description
Environment: Skip 1.9.7, skip-ui 1.59.2, skip-fuse-ui 1.18.1, Swift 6.3.0, Android 15 (API 35) emulator. Skip Fuse.
Summary
On Android, scheduling a local notification through
UNUserNotificationCenter.current().add(_:) completes without throwing and
delivers nothing. There is no error, no log, and no notification record — the
app has no way to know it failed.
Two independent causes, both of which must be fixed by the app, and neither of
which is discoverable from the API:
1. add() silently no-ops unless a delegate is set.
SkipUI/UIKit/UserNotifications.swift begins:
public func add(_ request: UNNotificationRequest) async throws {
guard let delegate else { return }
let notification = UNNotification(request: request, date: Date.now)
let options = await delegate.userNotificationCenter(self, willPresent: notification)
guard options.contains(.banner) || options.contains(.alert) else { return }
...
This diverges from iOS in two ways. On iOS add() does not require a delegate
at all, and willPresent is consulted only when a notification fires while
the app is foregrounded — it has no bearing on whether a notification is
scheduled or delivered while backgrounded. Here it is consulted at schedule
time and silently discards the request.
The practical effect: an app that follows Apple's documentation, sets no
delegate, and schedules a notification gets a successful add() and no
notification, forever, with nothing to debug.
2. NotificationWorker throws when neither icon resource exists.
Once a delegate is set, the work is enqueued and then fails:
E WM-WorkerWrapper: Work [ tags={ skip.ui.NotificationWorker, ... } ] failed because it threw an exception/error
E WM-WorkerWrapper: java.lang.IllegalArgumentException: Drawable resource ID must not be 0
E WM-WorkerWrapper: at androidx.core.graphics.drawable.IconCompat.createWithResource(IconCompat.java:237)
E WM-WorkerWrapper: at skip.ui.NotificationWorker.doWork(UserNotifications.kt:1153)
The worker resolves its small icon as:
var resId = context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName())
if resId == 0 {
resId = context.getResources().getIdentifier("ic_launcher", "mipmap", context.getPackageName())
}
builder.setSmallIcon(IconCompat.createWithResource(context, resId))
A stock skip create app ships neither drawable/ic_notification nor
mipmap/ic_launcher — it has no Android/app/src/main/res directory at all,
since icons are generated from the Darwin asset catalog. So both lookups return
0 and IconCompat.createWithResource throws. This failure happens on a
WorkManager background thread, so again the app sees nothing.
Related: #203 covered the same icon lookup, for the case where a resource
exists but is in the wrong format. This is the adjacent case — where neither
resource exists at all — which throws instead of rendering incorrectly.
Reproduction
skip createa Fuse app.- Add
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
to the manifest and grant it at runtime. - Schedule a notification:
let content = UNMutableNotificationContent()
content.title = "Test"
content.body = "Hello"
let request = UNNotificationRequest(
identifier: "test",
content: content,
trigger: UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
)
try await UNUserNotificationCenter.current().add(request)
- Nothing is delivered.
adb shell dumpsys notification --noredactshows no
record for the package and no registered channel.add()did not throw.
Suggested fixes
- Do not require a delegate to schedule. Treat a missing delegate as the
iOS default (deliver normally), and consultwillPresentat delivery time
for foreground presentation, not at schedule time. If the current behaviour
is intentional,add()should throw rather than return successfully. - Fall back to a built-in icon (
android.R.drawable.ic_dialog_infoor the
application icon fromcontext.applicationInfo.icon) when neither lookup
resolves, rather than passing 0 toIconCompat. Alternatively, have
skip creategenerate a defaultic_notificationdrawable. - Surface worker failures. A
NotificationWorkerexception is currently
invisible to the app; logging it through Skip's logger would have turned a
multi-hour investigation into a one-line diagnosis.
The silent-success path is the worst part of this. Either failure alone would
be quick to find if it reported anything at all.
Workaround (what we shipped)
No #if SKIP escape hatch required — both fixes are ordinary app code:
- Set a delegate that opts into presentation:
final class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
return [.banner, .sound]
}
}
// retain it — UNUserNotificationCenter.delegate is weak
UNUserNotificationCenter.current().delegate = notificationDelegate
- Add
Android/app/src/main/res/drawable/ic_notification.xml(a white
silhouette on a transparent background).
With both in place, delivery works on Android and the same source is unchanged
on iOS.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with SkipUI/UIKit/UserNotifications.swift and the NotificationWorker implementation in UserNotifications.kt, then reproduce the issue using the supplied skip create app and adb commands. Trace scheduling without a delegate and worker behavior when both icon lookups return zero. Done means both cases report or handle failure appropriately and notifications can be scheduled and delivered without app-provided icon resources.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, kotlin, swift
- Domain
- mobile-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100