Skip to content

feat: wire LifecycleCoordinator + openURL public API [3/4]#32

Open
choudlet wants to merge 2 commits into
chrish/sc-38234/lifecycle-event-trackerfrom
chrish/sc-38235/lifecycle-wiring-openurl
Open

feat: wire LifecycleCoordinator + openURL public API [3/4]#32
choudlet wants to merge 2 commits into
chrish/sc-38234/lifecycle-event-trackerfrom
chrish/sc-38235/lifecycle-wiring-openurl

Conversation

@choudlet

@choudlet choudlet commented Apr 27, 2026

Copy link
Copy Markdown
Collaborator

Shortcut: sc-38235
Parent: sc-36799
iOS reference: sc-38230
Slice 3 of 4 — first slice in the stack where the feature actually emits events end-to-end.

Stacked on #31 (sc-38234). Merge sc-38233 + sc-38234 first; this PR will retarget to main automatically.

Summary

End-to-end integration of the lifecycle subsystem behind InitOptions.trackLifecycleEvents (default false — opt-in). After this slice the feature is fully functional; existing customers are not affected on upgrade.

Coordinator seam

  • New LifecycleCoordinator wraps LifecycleEventTracker. MetaRouterAnalyticsClient no longer references the tracker directly — future session / attribution work has a clean place to land alongside lifecycle.
  • Constructed only when trackLifecycleEvents == true. When disabled, lifecycleCoordinator == null and every dispatch site is a null-safe no-op.

Public API

  • openURL across AnalyticsInterface, AnalyticsProxy, PendingCall, and the proxied call replay. Name matches Segment's iOS SDK and avoids the "handle" over-promise (the SDK doesn't route or parse the URL — it buffers it for the next Application Opened).
  • openURL on the client logs Logger.warn and no-ops when the feature is disabled. Silent no-op was bad DX; misconfig is now diagnosable from logcat.

AppContext caching

  • Client reads AppContext.fromContext() exactly once at init and injects the same snapshot into both DeviceContextProvider and LifecycleEventTracker. Per-event enrichment no longer hits PackageManager.

Opt-in default

  • InitOptions.trackLifecycleEvents flips truefalse. KDoc updated. InitOptionsTest renamed to assert the new default.

Lifecycle ordering

  • onBackground emits Application Backgrounded before flush / flushToDisk so the event lands in the same drain (unchanged behavior; comment kept next to the code).

Stack

  1. sc-38233 — storage + bundle metadata foundation
  2. sc-38234 — LifecycleEventTracker algorithm + tests
  3. this PR — wiring + openURL public API
  4. sc-38236 — README documentation

class DeviceContextProvider(private val context: Context) {
class DeviceContextProvider(
private val context: Context,
private val appContext: AppContext = AppContext.fromContext(context)

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.

now reads the shared AppContext

@choudlet choudlet force-pushed the chrish/sc-38234/lifecycle-event-tracker branch from 78e1343 to f30267a Compare April 27, 2026 21:55
End-to-end integration of the lifecycle subsystem. After this slice the
feature is fully functional behind `InitOptions.trackLifecycleEvents`
— default stays false so existing customers are not affected on
upgrade.

Coordinator seam
- LifecycleCoordinator wraps LifecycleEventTracker. MetaRouterAnalytics-
  Client no longer references the tracker directly; future session /
  attribution work has a clear place to land alongside lifecycle events.
- Constructed only when trackLifecycleEvents=true. When the feature is
  off, lifecycleCoordinator is null and every dispatch site is a
  null-safe no-op.

AppContext caching
- AnalyticsClient reads AppContext.fromContext() exactly once at init
  and injects the same snapshot into both DeviceContextProvider and
  LifecycleEventTracker. The per-event enrichment path no longer hits
  PackageManager.

Public API
- handleDeepLink renamed to openURL across AnalyticsInterface,
  AnalyticsProxy, PendingCall, and the proxied call replay. Name
  matches Segment's iOS SDK and avoids the over-promise of 'handle'
  (the SDK does not route or parse the URL — it buffers it for the
  next Application Opened).
- openURL on the client logs Logger.warn and no-ops when the feature
  is disabled. Silent no-op was bad DX; misconfiguration is now
  diagnosable from logcat.

Opt-in default
- InitOptions.trackLifecycleEvents flips from true to false. KDoc
  updated. InitOptionsTest renamed to assert the new default.

Lifecycle ordering
- onBackground emits Application Backgrounded BEFORE flush /
  flushToDisk so the event lands in the same drain (unchanged behavior;
  comment kept next to the code).

Refs: sc-38235
Slice 3 follow-ups from code review.

- onForeground ordering: dispatcher.resume() now runs BEFORE
  coordinator.onForeground(). Mirrors iOS so the resumed dispatcher
  picks up the just-emitted Application Opened in its next tick rather
  than waiting for the following flush cycle. Functionally benign on
  Android (track() enqueues regardless of dispatcher state) but the
  cross-platform parity contract was an explicit constraint.
- Coordinator gate: trackLifecycleEvents is now the sole on/off signal.
  Previously a test seam injection of injectedLifecycleCoordinator could
  install a coordinator while the flag was false. Production paths were
  fine via MetaRouter.kt, but the off-state should be structurally
  enforced regardless of the seam.
- AnalyticsInterface.openURL KDoc: drop the misleading
  Intent.EXTRA_REFERRER + getStringExtra suggestion. Per Android docs
  EXTRA_REFERRER is a Uri (returns null via getStringExtra). Point hosts
  at Activity.referrer?.host, which is the canonical API for the
  calling-app host.
- DeviceContextProvider: KDoc on the appContext constructor default
  noting it exists for test ergonomics only. Production code must pass
  the explicit cached snapshot.

Refs: sc-38235
@choudlet choudlet force-pushed the chrish/sc-38235/lifecycle-wiring-openurl branch from 382bcd4 to 4e7a426 Compare April 27, 2026 21:58
// every consumer (DeviceContextProvider for per-event enrichment, the
// lifecycle tracker for install/update detection). Bundle/PackageInfo is
// OS-loaded and immutable post-process-start, so caching is safe.
val appContext = injectedAppContext ?: AppContext.fromContext(context)

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.

allows for DI for testing

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