- buildDualScheduleConfig: set contentFetch timeout/retryAttempts/retryDelay (match capacitor DailyNotification networkConfig), userNotification.vibration, return type DualScheduleConfiguration - @timesafari/daily-notification-plugin 2.1.1 → 2.1.3 (package-lock) - doc: plugin feedback (contentFetch JSON, parseUserNotificationConfig optional fields) and Android DailyNotificationWorker duplicate scheduleId note
5.5 KiB
Plugin feedback: Android parseUserNotificationConfig — optional fields vs getBoolean / getString
Date: 2026-03-20 21:11 PST
Target repo: @timesafari/daily-notification-plugin (daily-notification-plugin)
Consuming app: crowd-funder-for-time-pwa (TimeSafari)
Platform: Android (Kotlin)
Related: Same class of issue as plugin-feedback-android-scheduleDualNotification-contentFetch-json.md (contentFetch / parseContentFetchConfig).
Summary
DailyNotificationPlugin.parseUserNotificationConfig() uses JSObject / JSONObject strict getters for fields that the published TypeScript UserNotificationConfig marks as optional (sound?, vibration?, priority?, title?, body?). If a key is omitted, Android throws JSONException (e.g. No value for vibration), and scheduleDualNotification fails before scheduling.
Recommended direction (plugin): Align Kotlin parsing with dist/esm/definitions.d.ts by using optional reads + defaults, consistent with the fix already applied for parseContentFetchConfig (e.g. optIntOrNull, or Capacitor/JSON equivalents for booleans and strings).
Recommended direction (app / already done in TimeSafari): Send explicit sound, vibration, and priority (and title/body) in buildDualScheduleConfig() so older plugin builds that still use strict getters continue to work.
Does it make sense to change both sides? Yes — same reasoning as for contentFetch: the plugin should match its public contract; the app can stay explicit for compatibility and clarity.
Symptoms (consuming app)
- In-app toast: “Could not schedule New Activity notification. Please try again.” (generic catch after
scheduleDualNotificationrejects.) - Logcat:
E DNP-PLUGIN: Schedule dual notification error
E DNP-PLUGIN: org.json.JSONException: No value for vibration
E DNP-PLUGIN: at org.json.JSONObject.getBoolean(JSONObject.java:419)
E DNP-PLUGIN: at org.timesafari.dailynotification.DailyNotificationPlugin.parseUserNotificationConfig(DailyNotificationPlugin.kt:2428)
E DNP-PLUGIN: at org.timesafari.dailynotification.DailyNotificationPlugin.scheduleDualNotification(DailyNotificationPlugin.kt:1392)
(First failure observed after contentFetch timeouts were fixed was vibration; the same pattern can affect sound or priority if those keys are omitted.)
Root cause
Published TypeScript contract (UserNotificationConfig)
From definitions.d.ts (representative):
title?,body?,sound?,vibration?,priority?— all optional.
Current Android implementation (strict)
In DailyNotificationPlugin.kt, parseUserNotificationConfig (line numbers approximate; search for parseUserNotificationConfig):
private fun parseUserNotificationConfig(configJson: JSObject): UserNotificationConfig {
return UserNotificationConfig(
enabled = configJson.getBoolean("enabled") ?: true,
schedule = configJson.getString("schedule") ?: "0 9 * * *",
title = configJson.getString("title"),
body = configJson.getString("body"),
sound = configJson.getBoolean("sound"),
vibration = configJson.getBoolean("vibration"),
priority = configJson.getString("priority")
)
}
getBoolean("vibration")(andgetBoolean("sound")) throw if the key is missing — optional in TS, required at runtime on Android.getString("title"),getString("body"),getString("priority")likewise throw if missing (depending onJSObject/JSONObjectbehavior for absent keys).
So minimal or TS-faithful payloads omit vibration → immediate JSONException.
Plugin-side recommendations
-
Treat
UserNotificationConfigoptional fields as optional on Android, mirroringdefinitions.d.ts:vibration: e.g.optBoolean/ nullable + defaulttrue(orfalseif that matches product default — document the default).sound: same pattern; defaulttrueis typical for notifications.priority: optional string with default"normal"(or map from TS union).title/body: if TS allows omission, use optional reads + defaults consistent with dual-schedule UX (or reject with a clearcall.rejectmessage instead of a rawJSONException).
-
Reuse the same helper style as
parseContentFetchConfigafter the timeout fix (optIntOrNull, etc.) so one codebase convention applies to all dual-schedule JSON parsing. -
Tests: Unit or integration test that calls
scheduleDualNotificationwith a minimaluserNotificationobject (only what TS strictly requires, if anything) and asserts scheduling succeeds on Android. -
iOS parity: If iOS already accepts omitted
vibration/sound, Android should match; if not, align both platforms to the sameUserNotificationConfigrules.
App-side note (TimeSafari)
src/services/notifications/dualScheduleConfig.ts — buildDualScheduleConfig() now includes vibration: true (with sound: true) so current native code paths succeed. Keeping this explicit is still recommended even after the plugin is fixed.
References
- Plugin:
android/.../DailyNotificationPlugin.kt—parseUserNotificationConfig - TS:
dist/esm/definitions.d.ts—UserNotificationConfig,DualScheduleConfiguration - App:
src/services/notifications/dualScheduleConfig.ts—buildDualScheduleConfig