Skip to content

fix: don't drop a message when a payload field is null - #3131

Open
blue-archon wants to merge 1 commit into
BlueBubblesApp:masterfrom
blue-archon:fix/null-field-message-crash
Open

blue-archon wants to merge 1 commit into
BlueBubblesApp:masterfrom
blue-archon:fix/null-field-message-crash

Conversation

@blue-archon

Copy link
Copy Markdown
Contributor

Problem

Incoming chat/message/attachment parsing casts the decoded payload with .cast<String, Object>(). The resulting CastMap re-casts every value to the non-nullable Object and throws a Null is not a subtype of Object cast error the first time any field is genuinely null. That aborts the whole parse and drops the chat/message/attachment for that event.

It fires on live new-message / updated-message events from both the socket (action_handler) and the method channel (FCM/UnifiedPush). Incremental sync backfills the item later, so it looks intermittent, but the real-time event is lost in the meantime. Seen on both Android and desktop during normal use, for example a null run inside an attributedBody.

Fix

Add a guarded deepNormalizeJson (lib/utils/deep_map_normalize.dart) that rebuilds maps and lists with null-tolerant reads and returns scalars (including numeric-looking strings like "1234") untouched. Route every parse site through asStringDynamicMapRequired instead of the raw cast: the method-channel entrypoints, the socket action_handler, and Chat / Message / AttributedBody fromMap.

No behavior change on well-formed payloads; a null field now parses instead of throwing.

@zlshames

Copy link
Copy Markdown
Member

im still not sure about this deepNormalize function... you've had it on a couple of PRs already, and I feel like it's compensation for something that should be resolved somewhere else at the root. Maybe casting it properly from the start rather than having to normalize it like we are. Maybe I'm wrong, but something feels off here

@blue-archon
blue-archon force-pushed the fix/null-field-message-crash branch from b9c61c3 to 4091815 Compare July 25, 2026 20:11
@blue-archon

Copy link
Copy Markdown
Contributor Author

You're right, that's the cleaner fix. The real bug was the cast target: .cast<String, Object>() rethrows every value as non-nullable Object, so a genuinely null field throws. .cast<String, dynamic>() is what fromMap already expects and it permits the null, with no normalize step. I swapped the casts and dropped deepNormalizeJson.

blue-archon added a commit to blue-archon/bluebubbles-app that referenced this pull request Jul 26, 2026
…deepNormalize

Per zlshames's review on BlueBubblesApp#3131: the root fix is to cast decoded payloads to Map<String, dynamic> (what fromMap already expects, and which permits null values) instead of the non-nullable Map<String, Object>. Replace asStringDynamicMapRequired with .cast<String, dynamic>() at the parse sites, restore the plain jsonDecode/cast at the method-channel boundary, and delete deep_map_normalize.dart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jjoelj
jjoelj changed the base branch from development to master July 27, 2026 03:14
blue-archon added a commit to blue-archon/bluebubbles-app that referenced this pull request Aug 3, 2026
…deepNormalize

Per zlshames's review on BlueBubblesApp#3131: the root fix is to cast decoded payloads to Map<String, dynamic> (what fromMap already expects, and which permits null values) instead of the non-nullable Map<String, Object>. Replace asStringDynamicMapRequired with .cast<String, dynamic>() at the parse sites, restore the plain jsonDecode/cast at the method-channel boundary, and delete deep_map_normalize.dart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
blue-archon added a commit to blue-archon/bluebubbles-app that referenced this pull request Aug 4, 2026
…deepNormalize

Per zlshames's review on BlueBubblesApp#3131: the root fix is to cast decoded payloads to Map<String, dynamic> (what fromMap already expects, and which permits null values) instead of the non-nullable Map<String, Object>. Replace asStringDynamicMapRequired with .cast<String, dynamic>() at the parse sites, restore the plain jsonDecode/cast at the method-channel boundary, and delete deep_map_normalize.dart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
blue-archon added a commit to blue-archon/bluebubbles-app that referenced this pull request Aug 12, 2026
…deepNormalize

Per zlshames's review on BlueBubblesApp#3131: the root fix is to cast decoded payloads to Map<String, dynamic> (what fromMap already expects, and which permits null values) instead of the non-nullable Map<String, Object>. Replace asStringDynamicMapRequired with .cast<String, dynamic>() at the parse sites, restore the plain jsonDecode/cast at the method-channel boundary, and delete deep_map_normalize.dart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@blue-archon
blue-archon force-pushed the fix/null-field-message-crash branch from 4091815 to fefca59 Compare August 12, 2026 19:35
blue-archon added a commit to blue-archon/bluebubbles-app that referenced this pull request Aug 14, 2026
…deepNormalize

Per zlshames's review on BlueBubblesApp#3131: the root fix is to cast decoded payloads to Map<String, dynamic> (what fromMap already expects, and which permits null values) instead of the non-nullable Map<String, Object>. Replace asStringDynamicMapRequired with .cast<String, dynamic>() at the parse sites, restore the plain jsonDecode/cast at the method-channel boundary, and delete deep_map_normalize.dart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Incoming chat/message/attachment parsing cast the decoded payload with .cast<String, Object>(). That builds a CastMap that re-casts every value to the non-nullable Object, so a genuinely null field throws a cast error and the whole parse aborts, dropping that chat/message/attachment. It fires on live socket and method-channel (FCM/UnifiedPush) new and updated-message events.

The maps are handed to fromMap, which already takes Map<String, dynamic>, so the cast target should have been dynamic all along. Cast to .cast<String, dynamic>() instead, which permits the null. Also default a null attributedBody string to empty so it does not throw at construction once the cast no longer throws first.
blue-archon added a commit to blue-archon/bluebubbles-app that referenced this pull request Sep 29, 2026
…deepNormalize

Per zlshames's review on BlueBubblesApp#3131: the root fix is to cast decoded payloads to Map<String, dynamic> (what fromMap already expects, and which permits null values) instead of the non-nullable Map<String, Object>. Replace asStringDynamicMapRequired with .cast<String, dynamic>() at the parse sites, restore the plain jsonDecode/cast at the method-channel boundary, and delete deep_map_normalize.dart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@blue-archon
blue-archon force-pushed the fix/null-field-message-crash branch from fefca59 to 8ceff76 Compare September 29, 2026 14:09

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants