feat(config): prefer autocapture over defaultTracking for mobile#305
Merged
Conversation
Add appLifecycles and deepLinks to AutocaptureOptions and AutocaptureEnabled so mobile autocapture can be configured via the canonical autocapture API (matching Amplitude-Swift and Amplitude-Kotlin, which have already deprecated defaultTracking in favor of autocapture). This commit only widens the Dart surface — the AutocaptureOptions field is serialized via Configuration.toMap() but native plugins don't yet read it. That wiring follows in subsequent commits. - AutocaptureOptions.appLifecycles: bool (default false) - AutocaptureOptions.deepLinks: bool (default false, Android-only on the iOS side since iOS doesn't expose .deepLinks as an AutocaptureOption) - AutocaptureEnabled also sets both true so the 'enable everything' sentinel keeps that semantic - Class doc updated; sessions re-labeled 'Cross-platform' - Tests for defaults, toMap, and AutocaptureEnabled
…itly set Make Configuration's autocapture constructor parameter nullable. When null, derive an AutocaptureOptions from the (deprecated) defaultTracking. The field itself stays non-null, so callers continue to read a real Autocapture value. This lets us treat 'autocapture' as the single source of truth on the Dart side. Native plugins read only the resolved autocapture map; they don't need to know about defaultTracking. defaultTracking can be safely deprecated without breaking existing callers — their values flow into the autocapture map automatically. Mirrors Amplitude-Swift's pattern (defaultTracking.didSet writes through to autocapture). Behavior: - Explicit autocapture wins over defaultTracking (documented). - AutocaptureDisabled/Enabled sentinels are preserved (not overridden). - Derived AutocaptureOptions folds: sessions, appLifecycles, deepLinks (mobile) and attribution, pageViews (web). formInteractions and fileDownloads stay on the deprecated defaultTracking path — they have no AutocaptureOptions equivalent yet.
Replace the defaultTracking reading block with a direct autocapture map translation. By the time the map reaches native, the Dart Configuration constructor (see commit 8cb5931) has already resolved the effective autocapture from defaultTracking, so no merge or fallback logic is needed here. frustrationInteractions is intentionally NOT translated to a native AutocaptureOption — it is a Dart-only flag. Native FRUSTRATION_INTERACTIONS only sees FlutterView and would emit useless rage events for Flutter apps. screenViews is similarly not translated — screen view tracking is implemented in Flutter, not the native SDK. Manual verification: build the example app with autocapture: AutocaptureOptions(appLifecycles: true, deepLinks: true) and confirm [Amplitude] Application Started events fire on Android. For backward-compat, building with only defaultTracking should produce the same behavior (Task A2 derivation does the legwork).
Replace the defaultTracking reading block with a direct autocapture map translation. By the time the map reaches native, the Dart Configuration constructor (commit 8cb5931) has already resolved the effective autocapture from defaultTracking, so no merge or fallback logic is needed here. iOS's native AutocaptureOptions option set does NOT include .deepLinks (iOS handles deep links outside the autocapture set), so the deepLinks key in the autocapture map is silently ignored on iOS. This matches the pre-existing iOS behavior — defaultTracking on Amplitude-Swift also has no deepLinks field. frustrationInteractions is intentionally NOT translated — Dart-only flag. screenViews is similarly not translated — implemented in Flutter. Manual verification: build the example app on iOS with autocapture: AutocaptureOptions(appLifecycles: true) and confirm [Amplitude] Application Started events fire. For backward compat, building with only defaultTracking should produce the same behavior (Task A2 derivation does the legwork).
…eOptions Mark the DefaultTrackingOptions class, Configuration.defaultTracking field, and Configuration's defaultTracking constructor parameter @deprecated with a migration message pointing to autocapture / AutocaptureOptions. This matches the native SDKs (Amplitude-Swift Configuration.swift:70-76 and Amplitude-Kotlin Configuration.kt:171), which have already done the same. Existing callers passing defaultTracking continue to work unchanged — the Configuration constructor's autocapture resolver (commit 8cb5931) folds their values into the derived AutocaptureOptions, so behavior is preserved. The deprecation warning is the migration nudge. Files that intentionally maintain the deprecation bridge get a single 'ignore_for_file: deprecated_member_use_from_same_package' header rather than scattered per-line ignores — they exist to support the deprecated API, and noisy warnings inside them add no signal. Switched the example app to autocapture: AutocaptureEnabled() so the example uses the new canonical API.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed:
AutocaptureDisabledsilently falls back to platform defaults- Native Android and Darwin parsing now treats a boolean false autocapture value as an explicit empty autocapture option set instead of falling back to defaults.
Or push these changes by commenting:
@cursor push 3ffc9b6406
Preview (3ffc9b6406)
diff --git a/android/src/main/kotlin/com/amplitude/amplitude_flutter/AmplitudeFlutterPlugin.kt b/android/src/main/kotlin/com/amplitude/amplitude_flutter/AmplitudeFlutterPlugin.kt
--- a/android/src/main/kotlin/com/amplitude/amplitude_flutter/AmplitudeFlutterPlugin.kt
+++ b/android/src/main/kotlin/com/amplitude/amplitude_flutter/AmplitudeFlutterPlugin.kt
@@ -237,15 +237,18 @@
call.argument<String>("serverUrl")?.let { builder.serverUrl = it }
call.argument<Int>("minTimeBetweenSessionsMillis")
?.let { builder.minTimeBetweenSessionsMillis = it.toLong() }
- call.argument<Map<String, Any>>("autocapture")?.let { map ->
- // The Dart Configuration constructor already resolved the effective
- // autocapture map (deriving it from defaultTracking when not set
- // explicitly), so we just translate the map to a native
- // AutocaptureOption set.
- builder.autocapture = buildSet {
- if (map["sessions"] == true) add(AutocaptureOption.SESSIONS)
- if (map["appLifecycles"] == true) add(AutocaptureOption.APP_LIFECYCLES)
- if (map["deepLinks"] == true) add(AutocaptureOption.DEEP_LINKS)
+ when (val autocapture = call.argument<Any>("autocapture")) {
+ false -> builder.autocapture = emptySet()
+ is Map<*, *> -> {
+ // The Dart Configuration constructor already resolved the effective
+ // autocapture map (deriving it from defaultTracking when not set
+ // explicitly), so we just translate the map to a native
+ // AutocaptureOption set.
+ builder.autocapture = buildSet {
+ if (autocapture["sessions"] == true) add(AutocaptureOption.SESSIONS)
+ if (autocapture["appLifecycles"] == true) add(AutocaptureOption.APP_LIFECYCLES)
+ if (autocapture["deepLinks"] == true) add(AutocaptureOption.DEEP_LINKS)
+ }
}
}
call.argument<Map<String, Any>>("trackingOptions")?.let { map ->
diff --git a/darwin/amplitude_flutter/Sources/amplitude_flutter/SwiftAmplitudeFlutterPlugin.swift b/darwin/amplitude_flutter/Sources/amplitude_flutter/SwiftAmplitudeFlutterPlugin.swift
--- a/darwin/amplitude_flutter/Sources/amplitude_flutter/SwiftAmplitudeFlutterPlugin.swift
+++ b/darwin/amplitude_flutter/Sources/amplitude_flutter/SwiftAmplitudeFlutterPlugin.swift
@@ -192,10 +192,13 @@
let migrateLegacyData = args["migrateLegacyData"] as? Bool ?? true
// The Dart Configuration constructor already resolved the effective
- // autocapture map (deriving it from defaultTracking when not set
- // explicitly), so we just translate the map to a native
- // AutocaptureOptions option set.
+ // autocapture value (deriving it from defaultTracking when not set
+ // explicitly), so we just translate it to a native AutocaptureOptions
+ // option set.
let autocaptureOptions: AutocaptureOptions = {
+ if (args["autocapture"] as? Bool) == false {
+ return []
+ }
guard let map = args["autocapture"] as? [String: Any] else {
return Configuration.Defaults.autocaptureOptions
}You can send follow-ups to the cloud agent here.
AutocaptureDisabled() serializes to `false`, but both the Android and iOS plugins only matched on Map and dropped `false` into the "no autocapture key" branch, which fell back to native SDK defaults instead of disabling autocapture. Match on `false` explicitly and translate it to an empty option set so the three cases (disabled, options map, absent) line up across Dart, iOS, and Android.
Contributor
Author
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 0607b56. Configure here.
…nullable Address PR review nit: dt no longer needs to be derived from a nullable. Move the defaultTracking defaulting to the call site so the helper takes a non-null DefaultTrackingOptions and the inner null-fallback (and the local 'dt') go away. The const DefaultTrackingOptions() default appears in the initializer twice but it's const-canonicalized — zero cost. No behavior change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Migrates the Flutter SDK so
Configuration.autocaptureis the canonical autocapture configuration on mobile, matching the native iOS and Android SDKs which deprecateddefaultTrackingsome time ago (Amplitude-Swift Configuration.swift, Amplitude-Kotlin Configuration.kt). The Flutter SDK was the only one still routing mobile config throughdefaultTracking.AutocaptureOptionsnow exposesappLifecyclesanddeepLinks(mobile).AutocaptureEnabledsets them too so the "enable everything" sentinel keeps its semantic.Configurationconstructor'sautocaptureparameter is now nullable. When omitted, anAutocaptureOptionsis derived from the (deprecated)defaultTrackingfield, folding both mobile fields (sessions,appLifecycles,deepLinks) and web fields that exist on both classes (attribution,pageViews). This is the load-bearing piece: it preserves end-to-end behavior for existingdefaultTrackingcallers without requiring any code change on their side.AmplitudeFlutterPlugin.kt, iOSSwiftAmplitudeFlutterPlugin.swift) now read only theautocapturemap and translate it directly to the nativeAutocaptureOptionset /AutocaptureOptionsoption set. No merge or fallback logic in native — by the time the map reaches native, Dart has already resolved it.DefaultTrackingOptionsclass,Configuration.defaultTrackingfield, and the constructor parameter are all@Deprecatedwith a migration message pointing toautocapture.deepLinksis intentionally not translated — iOS handles deep links outside the native autocapture option set, matching pre-existing behavior onAmplitude-Swift.Why this approach
A native-side merge (each plugin reads both
autocaptureanddefaultTrackingand resolves) was the first attempt but duplicates the resolution logic across two platforms and leavesConfiguration.autocaptureshowing something different from what platforms actually use. Dart-side derivation keeps a single source of truth, makes the native plugins dumb translators, and matches thedidSetpattern Amplitude-Swift already uses.It also sets up planned Flutter frustration tracking cleanly: that flag will live on
AutocaptureOptionsbut native plugins will explicitly skip it during translation (native frustration tracking only seesFlutterViewand would emit useless events), and that "skip" lives in one place per platform with no other config to disentangle.Backward compatibility
Existing callers using
defaultTracking: DefaultTrackingOptions(...)see no behavior change. The derivation in the constructor folds their values into the autocapture map the native plugin now reads. They'll see one new deprecation warning fromflutter analyzeand a migration message pointing them atautocapture.Configuration(defaultTracking: DTO(appLifecycles: true))defaultTracking→{SESSIONS, APP_LIFECYCLES}autocapture→ same{SESSIONS, APP_LIFECYCLES}Configuration(autocapture: AutocaptureOptions(appLifecycles: true)){SESSIONS}fromdefaultTrackingdefault{SESSIONS, APP_LIFECYCLES}(now respected)Configuration(apiKey: 'k')(neither){SESSIONS}fromdefaultTrackingdefaultssessions: true→ same{SESSIONS}defaultTrackingautocapturewins;defaultTrackingignored. New behavior, documented.Test plan
flutter test— all unit tests green (45 tests including 6 new derivation tests)flutter analyze— clean (only a pre-existing untracked-file issue in scratch)defaultTrackingconfig still emits[Amplitude] Application Started/ Session eventsautocaptureconfig emits same eventsAutocaptureEnabled()enables mobile autocaptureAutocaptureDisabled()produces zero[Amplitude]eventsadb shell am start ...fires[Amplitude] Deep Link Openedwhen enabledautocapture+defaultTracking) —autocapturewins (confirms derivation contract)Notes for reviewers
// ignore_for_file: deprecated_member_use_from_same_packageat the top oflib/configuration.dart,test/configuration_test.dart, andtest/default_tracking_test.dartis intentional — those files exist to maintain the deprecation bridge and verifying it works. Scattered per-line ignores would be noisier without adding signal.example/pubspec.lockis removed in a separatechore:commit — it was tracked despiteexample/pubspec.lockalready being in.gitignore:21.AmplitudeFlutterPluginTest.ktdoesn't run via CI (flutter testonly covers Dart), and the standalone./gradlew testbuild can't resolveio.flutter.*. Worth revisiting when Android tests start running through Flutter tooling.🤖 Generated with Claude Code
Note
Medium Risk
Changes which autocapture flags reach native SDKs and introduces explicit
autocapturevsdefaultTrackingprecedence when both are set; behavior for legacydefaultTracking-only callers is intended to stay the same via Dart derivation.Overview
Makes
Configuration.autocapturethe single path for mobile default/autocapture behavior, aligned with native iOS/Android SDKs.AutocaptureOptions/AutocaptureEnablednow includeappLifecyclesanddeepLinksin their serialized maps.Dart resolves effective autocapture in
Configuration._resolveAutocapture: explicitautocapturewins; otherwise values are derived from deprecateddefaultTracking(sessions, mobile flags, and webattribution/pageViews).defaultTrackingis@Deprecatedbut still emitted intoMap()for compatibility.Android and iOS plugins stop reading
defaultTrackingand map only theautocaptureargument (false→ disabled; map → native option sets). iOS still does not mapdeepLinksfrom the map (unchanged platform behavior).The example app initializes with
AutocaptureEnabled()instead ofDefaultTrackingOptions.all(). Unit tests cover derivation and mobile autocapture serialization.Reviewed by Cursor Bugbot for commit 87674ff. Bugbot is set up for automated code reviews on this repo. Configure here.