Skip to content

Fix/sse empty data format exception#153

Open
vazarkevych wants to merge 3 commits into
growthbook:mainfrom
vazarkevych:fix/sse-empty-data-format-exception
Open

Fix/sse empty data format exception#153
vazarkevych wants to merge 3 commits into
growthbook:mainfrom
vazarkevych:fix/sse-empty-data-format-exception

Conversation

@vazarkevych

Copy link
Copy Markdown
Collaborator

fix: handle empty SSE data payload to prevent unhandled FormatException

Problem

When a GrowthBook SSE stream emits a heartbeat or keepalive event with an empty data field, the SDK crashed with an unhandled FormatException.

The root cause was a two-part issue:

  1. sseModel.data ?? "" silently fell through to jsonDecode(""), which throws a FormatException on an empty string.
  2. The SSE .listen() callback runs asynchronously, so the exception escaped the surrounding try/catch and became an unhandled error — crashing the listener silently in production.

Fix

In lib/src/Network/network.dart, the features event handler now guards against empty or null payloads before attempting JSON decoding:

final data = sseModel.data;
if (data == null || data.isEmpty) return;
Map<String, dynamic> jsonMap = jsonDecode(data);

Heartbeat/keepalive events are silently dropped. JSON parsing is only attempted when there is actual data.

Tests

Added test/common_test/sse_empty_data_test.dart covering:

  • Empty data field (data: ) — no unhandled exception, onSuccess not called
  • Missing data field (null) — no unhandled exception, onSuccess not called
  • Valid JSON payload — onSuccess called with the parsed map

@madhuchavva madhuchavva left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline review comment on SSE malformed payload handling.

Comment thread lib/src/Network/network.dart Outdated
lastKnownId = sseModel.id;
String jsonData = sseModel.data ?? "";
Map<String, dynamic> jsonMap = jsonDecode(jsonData);
Map<String, dynamic> jsonMap = jsonDecode(data);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes the heartbeat/empty-data case, which is good. There is still one related robustness gap: non-empty malformed data can still throw inside this async .listen() callback. If the server sends truncated JSON, invalid JSON, or a payload shape that is not Map<String, dynamic>, jsonDecode(data) can escape the surrounding request try/catch and become an unhandled async error. Can we wrap decode/type validation locally and route failures through onError or intentionally ignore only known heartbeat/keepalive events?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, wrapped jsonDecode and the type check in a local try/catch inside the .listen() callback — all parse failures now route through onError instead of escaping as unhandled async errors. Added tests for malformed JSON and non-object payloads.

@madhuchavva madhuchavva left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes based on the inline review comments. The notes call out functional gaps or compatibility/documentation risks that should be addressed before merge.

@vazarkevych vazarkevych requested a review from madhuchavva June 11, 2026 10:12
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