Skip to content

fix: stop publishing unsupported tracking values as 0 - #77

Open
vahidlazio wants to merge 1 commit into
mainfrom
vahidt/flutter-unknown-value-handling
Open

fix: stop publishing unsupported tracking values as 0#77
vahidlazio wants to merge 1 commit into
mainfrom
vahidt/flutter-unknown-value-handling

Conversation

@vahidlazio

Copy link
Copy Markdown
Collaborator

The defect

Dart wraps a value it has no Confidence type for — a DateTime, a null, a custom object — as {'type': 'unknown', 'value': value.toString()}. The stringified value does reach native. Both native sides then discarded it, differently:

Platform convertValue / convert behaviour Result
iOS default: returns ConfidenceValue.init(integer: 0) publishes 0
Android else -> throws IllegalArgumentException("Unknown type unknown") event lost

So track('my_event', {'ts': DateTime.now()}) publishes ts = 0 on iOS. The event is accepted, so nothing surfaces the corruption — the data is just wrong. On Android the same input takes a different path: because Dart's track is fire-and-forget, the throw surfaces only as a logged handler error while the event is dropped.

Two platforms, same input, two different wrong outcomes.

The fix

Both sides now publish the string Dart already computed, so they agree and the caller's value survives. The unrecognised-type branches are hardened the same way rather than left to coerce, and iOS logs when it hits one.

Why stringify rather than the alternatives:

  • Rejecttrack must not throw at the application (see fix: always reply to track method channel calls and surface failures #75, which made exactly that guarantee for the reply path).
  • Omit the key — silent loss is the failure class being fixed here, not a fix for it.
  • Stringify — Dart has already produced a legible representation; keeping it means a human debugging the event sees "2026-09-07 12:34:56.000Z" rather than 0 or nothing.

A null becomes the string "null". That is deliberate: it is visible and truthful about what the caller passed, where 0 was neither.

Testing

The native halves cannot be exercised from Dart, and this repo's Kotlin unit tests are not run by CI (the android-test job runs flutter drive on an emulator, not ./gradlew testDebugUnitTest). The added Dart tests therefore pin the wire format the native fix depends on.

Verified falsifiable — coercing the unknown branch to 0 fails with Expected: '2026-09-07 12:34:56.000Z' / Actual: <0> and Expected: not <0> / Actual: <0>.

flutter analyze clean (one pre-existing example/.env asset warning, created by CI) and flutter test 5/5 passing.

Scope

Pre-existing on main and independent of #75 (track/flush reply paths) and #76 (async reply safety).

🤖 Generated with Claude Code

Dart wraps a value it has no Confidence type for — a DateTime, a null, a
custom object — as {'type': 'unknown', 'value': value.toString()}, so the
stringified value does reach native. Both native sides then threw it away.

iOS convertValue's default branch returned ConfidenceValue(integer: 0), so
track('my_event', {'ts': DateTime.now()}) published ts = 0. The event was
accepted, so nothing surfaced the corruption; the data was simply wrong.

Android convert() threw IllegalArgumentException("Unknown type unknown")
instead. Since Dart's track is fire-and-forget, that surfaced only as a
logged handler error while the event was lost — a different failure from
iOS for the same input.

Both now publish the string Dart already computed, so the two platforms
agree and the caller's value survives. Stringifying is preferred over
rejecting (track must not throw at the app) and over omitting the key
(silent loss is the failure being fixed). The unrecognised-type branches
are hardened the same way rather than left to coerce.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vahidlazio
vahidlazio requested a review from nicklasl September 7, 2026 14:41
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.

1 participant