REALLY DESPERATE, notification works perfectly in emulator, once on real phone breaks. HELP NEEDED.
expo-notifications on Android — Failed to schedule the notification. org.json.JSONObject — crashes on physical Xiaomi but not on emulator, surviving all fix attempts
We've been chasing a persistent org.json.JSONObject crash in expo-notifications@0.32.17 for three app versions. Looking for anyone who's actually resolved this.
What we're building: A React Native (Expo, CNG) app that schedules local push notifications. Users set recurring reminders; we call scheduleNotificationAsync once per date slot.
The error — identical every time:
Failed to schedule the notification. org.json.JSONObject
Appears in logcat and Sentry. org.json.JSONObject is the Java class name, not a message string — meaning a JSONException escapes ArgumentsNotificationContentBuilder.getBody() uncaught, because the catch block only handles NullPointerException.
Reproduction pattern:
- ✅ Works fine: Android emulator (stock AOSP)
- ❌ Fails: Physical Xiaomi device (MIUI, Android 13)
- ❌ Fails: EAS production build from Google Play
- ✅ Works: Local
gradlew assembleRelease
Our actual scheduling code:
slotBuilder.ts — builds the payload, coerces all data values to strings:
typescript
function toSafeData(obj: Record<string, unknown>): Record<string, string> {
const out: Record<string, string> = {};
for (const [k, v] of Object.entries(obj)) {
if (v == null) continue;
out[k] = typeof v === 'string' ? v : JSON.stringify(v);
}
return out;
}
// data passed to scheduler:
data: toSafeData({
reminderId: reminder.id, // string
style: reminder.style, // string
locale: reminder.locale,// string
})
scheduler.ts — the actual scheduleNotificationAsync call:
typescript
const notifId = await Notifications.scheduleNotificationAsync({
content: {
title: slot.title, // string
body: slot.body, // string
data: slot.data, // Record<string, string> — fully guarded above
sound: true, // ← boolean — our current suspect
...(Platform.OS === 'android' && { channelId: REMINDER_CHANNEL_ID }),
},
trigger: {
type: Notifications.SchedulableTriggerInputTypes.DATE,
date: slot.fireDate,
},
});
What we've already tried:
patch-packageto widen the Java catch block to also catchJSONException→ patch applies to source on EAS server but bytecode in the final APK is unchanged (expo-notifications resolves as a pre-built AAR from Maven, not compiled from source)EAS_BUILD_DISABLE_MAVEN_CACHE=1→ patch still doesn't applytoSafeData()guard on the entiredatafield → error persists
Current hypothesis: toSafeData() correctly sanitizes the data sub-object. The only remaining non-string value in the content object is sound: true — a boolean at the top level of content, not inside data. Our Android channel (setupChannel()) already sets sound: null (device default), making sound: true in the content object completely redundant on Android. We suspect MIUI's modified notification stack does a secondary JSONObject serialization pass over the full content object, and the boolean is what triggers the exception. Stock AOSP (emulator) tolerates it.
Questions:
- Has anyone actually resolved
org.json.JSONObjectfromexpo-notificationson a physical Android device? - Is
sound: truein notification content (not channel) known to cause issues on MIUI or other OEM ROMs? - Is there any documented difference in how expo-notifications serializes the content object between stock Android and MIUI?
- Are there other non-string fields in
NotificationContentInputthat could cause this on certain ROMs? - Any known issues on expo-notifications?
- Or even investigation ideas or direction?
- we do have a icon transparent png, for notification icon, but i think this is unlikely.
Any help and pointer is appreciated. been literally trying to fix for over a week, however with no results, and now not even much direction or ideas.