Package
segment_analytics: ^1.1.11
Summary
On every fresh app launch, the Flutter SDK starts with an empty trait
set. Traits sent to analytics.identify(userId, userTraits) in a previous
session are not read back from disk on init, and subsequent identify(...)
calls do NOT merge with the previously-shipped traits — the SDK ships only
what the caller passed in that single call.
This diverges from the Segment analytics-kotlin / analytics-android
SDKs, which persist the last-known Traits on disk and merge incoming
traits into that persisted map on every identify call. As a result, the
event that lands on the Segment ingestion endpoint from Android carries the
full accumulated trait union; the same app port to Flutter carries only the
delta from the most recent call.
Reproduction
- Fresh install, cold start.
analytics.identify('u1', UserTraits(custom: {'a': 1}))
- Kill the process. Cold start again.
analytics.identify('u1', UserTraits(custom: {'b': 2}))
- Inspect the outbound
identify payload — traits contains only {b: 2}.
Expected (Android parity): {a: 1, b: 2}.
Same divergence is observable within a single session across multiple
identify calls: call 2's payload does not include the traits shipped in
call 1.
Expected behavior
Match analytics-kotlin (see Traits.kt + UserInfo persistence via
Storage.write(Storage.Constants.Traits, ...)):
- Read persisted traits from disk on
createClient / init.
- On every
identify, deep-merge incoming traits into the persisted map
and write back.
- On
reset(), clear the persisted map.
Every app that ports from analytics-android/kotlin has to reimplement this
just to keep dashboards keyed on cumulative traits working. Would be great
to have it in the SDK itself.
Questions
- Is trait persistence on the roadmap, or is the current wholesale-replace
behavior intentional?
- If intentional, what's the recommended pattern for apps that need
analytics-kotlin-parity accumulation? (Is a Plugin of type
enrichment on the identify event the right hook, or should apps keep
doing the merge above the SDK boundary as we do today?)
###Environment
- segment_analytics: 1.1.11
- Flutter: 3.11.0
- Platform: Android + iOS (same behavior on both)
Current workaround
We accumulate traits manually in the app layer:
// Full trait union re-sent on every identify.
final Map<String, Object?> _accumulatedTraits = <String, Object?>{};
Future<void> init() async {
_hydrateAccumulatedTraits(); // reads jsonEncoded map from SharedPreferences
...
}
Future<void> identify({String? userId, required Map<String, Object?> traits}) async {
_accumulatedTraits.addAll(traits);
unawaited(_prefs.setAccumulatedTraits(jsonEncode(_accumulatedTraits)));
await _segment.identify(
userId: userId,
userTraits: UserTraits(custom: Map<String, dynamic>.from(_accumulatedTraits)),
);
}
Future<void> reset() async {
_accumulatedTraits.clear();
unawaited(_prefs.setAccumulatedTraits(null));
return _segment.reset(resetAnonymousId: true);
}
Package
segment_analytics: ^1.1.11Summary
On every fresh app launch, the Flutter SDK starts with an empty trait
set. Traits sent to
analytics.identify(userId, userTraits)in a previoussession are not read back from disk on init, and subsequent
identify(...)calls do NOT merge with the previously-shipped traits — the SDK ships only
what the caller passed in that single call.
This diverges from the Segment analytics-kotlin / analytics-android
SDKs, which persist the last-known
Traitson disk and merge incomingtraits into that persisted map on every
identifycall. As a result, theevent that lands on the Segment ingestion endpoint from Android carries the
full accumulated trait union; the same app port to Flutter carries only the
delta from the most recent call.
Reproduction
analytics.identify('u1', UserTraits(custom: {'a': 1}))analytics.identify('u1', UserTraits(custom: {'b': 2}))identifypayload —traitscontains only{b: 2}.Expected (Android parity):
{a: 1, b: 2}.Same divergence is observable within a single session across multiple
identifycalls: call 2's payload does not include the traits shipped incall 1.
Expected behavior
Match analytics-kotlin (see
Traits.kt+UserInfopersistence viaStorage.write(Storage.Constants.Traits, ...)):createClient/ init.identify, deep-merge incoming traits into the persisted mapand write back.
reset(), clear the persisted map.Every app that ports from analytics-android/kotlin has to reimplement this
just to keep dashboards keyed on cumulative traits working. Would be great
to have it in the SDK itself.
Questions
behavior intentional?
analytics-kotlin-parity accumulation? (Is a Plugin of type
enrichment on the identify event the right hook, or should apps keep
doing the merge above the SDK boundary as we do today?)
###Environment
Current workaround
We accumulate traits manually in the app layer: