- 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
6.1 KiB
Plugin feedback: Android scheduleDualNotification — JSONException: No value for timeout
Date: 2026-03-20 18:21 PST
Target repo: @timesafari/daily-notification-plugin (daily-notification-plugin)
Consuming app: crowd-funder-for-time-pwa (TimeSafari)
Platform: Android (Kotlin)
Plugin version observed: 2.1.2 (from app node_modules)
Summary
Scheduling the New Activity dual notification on Android fails with a native JSONException because DailyNotificationPlugin.parseContentFetchConfig() uses JSONObject.getInt() for timeout, retryAttempts, and retryDelay. Those keys are absent from the app’s contentFetch object built by buildDualScheduleConfig(). The plugin’s own TypeScript ContentFetchConfig marks those fields as optional, so the Android parser is stricter than the published contract.
Recommended direction:
- Plugin (primary): Parse optional numeric fields with defaults (e.g.
optInt/ nullable + defaults) so payloads that omit them do not crash and matchdefinitions.d.ts. - App (secondary / compatibility): Include explicit
timeout,retryAttempts, andretryDelayoncontentFetchso older plugin versions that still usegetIntcontinue to work.
Does it make sense to change both sides? Yes. Fixing the plugin aligns behavior with the documented API and protects any consumer that omits those fields. Fixing the app is still valuable for older shipped plugin builds and makes network behavior explicit. Together you get backward compatibility, clearer intent, and no silent reliance on undocumented defaults.
Symptoms (consuming app)
- In-app toast: “Could not schedule New Activity notification. Please try again.” (generic error path after
scheduleDualNotificationrejects.) - Logcat (filtered on DNP / plugin tags):
E DNP-PLUGIN: Schedule dual notification error
E DNP-PLUGIN: org.json.JSONException: No value for timeout
E DNP-PLUGIN: at org.json.JSONObject.getInt(JSONObject.java:487)
E DNP-PLUGIN: at org.timesafari.dailynotification.DailyNotificationPlugin.parseContentFetchConfig(DailyNotificationPlugin.kt:2403)
E DNP-PLUGIN: at org.timesafari.dailynotification.DailyNotificationPlugin.scheduleDualNotification(DailyNotificationPlugin.kt:1391)
Root cause
Call path
scheduleDualNotification reads config.contentFetch and passes it to parseContentFetchConfig:
- File:
android/.../DailyNotificationPlugin.kt scheduleDualNotification~1391:parseContentFetchConfig(contentFetchObj)parseContentFetchConfig~2397–2411: usesgetIntfor three keys.
Strict Android parsing
Illustrative (exact line numbers may shift between releases):
// parseContentFetchConfig — timeout / retry fields are required via getInt()
timeout = configJson.getInt("timeout"),
retryAttempts = configJson.getInt("retryAttempts"),
retryDelay = configJson.getInt("retryDelay"),
getInt throws if the key is missing → first missing key in practice is timeout → JSONException: No value for timeout.
App payload today (consuming app)
File: src/services/notifications/dualScheduleConfig.ts — buildDualScheduleConfig() sets contentFetch to:
enabled,schedule,callbacksonly (notimeout,retryAttempts,retryDelay, nourl).
That matches the TypeScript contract in the plugin’s dist/esm/definitions.d.ts, where timeout, retryAttempts, and retryDelay are optional on ContentFetchConfig.
Contract mismatch
| Layer | timeout / retryAttempts / retryDelay |
|---|---|
TS ContentFetchConfig |
Optional (?) |
Android parseContentFetchConfig |
Required (getInt — throws if absent) |
The consuming app followed the TS API; Android rejected it at runtime.
Plugin-side recommendations
- Use optional reads with defaults for
timeout,retryAttempts, andretryDelay(and any similar fields), e.g. Kotlin/Capacitor equivalents ofoptIntorgetIntegerwith fallbacks documented inContentFetchConfig. - Document defaults in the plugin README or API docs if they are applied on native when omitted.
- Consider tests that call
scheduleDualNotificationwith a minimalcontentFetch(onlyenabled,schedule,callbacks) and assert scheduling succeeds on Android. - Optional: If
urlis also read in a way that assumes presence, align with TS (url?) the same way.
App-side recommendations (later; crowd-funder-for-time-pwa)
When you implement the app fix:
- Extend
contentFetchinbuildDualScheduleConfig()(src/services/notifications/dualScheduleConfig.ts) to include explicit integers, for example aligned with existing app/network conventions (the app’scapacitor.config.tsalready uses atimeoutvalue in one place — reuse or document chosen values). - Ensure both code paths that build dual config stay in sync (e.g.
AccountViewView.vueusesbuildDualScheduleConfigfor New Activity scheduling and forupdateDualScheduleConfigfallback).
This unblocks users on current plugin versions that still require those keys.
References (paths in consuming app workspace)
- App config builder:
src/services/notifications/dualScheduleConfig.ts - Native scheduling entry:
node_modules/@timesafari/daily-notification-plugin/android/.../DailyNotificationPlugin.kt(scheduleDualNotification,parseContentFetchConfig)
Answer: change both plugin and app?
Yes, it makes sense to change both, for different reasons:
| Side | Why |
|---|---|
| Plugin | Fixes the real bug: native behavior must match the published optional TS fields; avoids breaking any client that sends a minimal contentFetch. |
| App | Defense in depth and support for already-shipped plugin binaries that will not get the Kotlin fix until users update the app. Explicit values also document intended fetch/retry behavior in one place. |
If you only fix the plugin, new app releases still need users to update the native binary. If you only fix the app, any other consumer of the plugin or future minimal payloads can hit the same crash until the plugin is fixed.