Tyagi // Engineering Log
← Back to Projects
Product Analytics production

Runo Product Analytics & Amplitude Instrumentation

A single tracking facade so the rest of the app never has to know which analytics backend it's hitting

Worked on a product analytics layer at Runo — Amplitude provider abstraction, event taxonomy, user/company traits, deduplication, and platform-aware tracking across auth, billing, CRM, and call workflows.

Date 2026-05
Reading Time 3 min read
Difficulty
Intermediate
Stack
Flutter,Dart,Amplitude +2
Reported

153+

Analytics-related commits

Reported

Web + Mobile

Deployment surface

How it evolved

  1. 2026-04

    Ad hoc tracking calls audited

    Events were scattered across feature code with no shared identity, dedupe, or platform-awareness model.

  2. 2026-05

    ProductAnalytics facade built

    AmplitudeProvider behind a single AnalyticsProvider contract, with structured user/company traits.

  3. 2026-05

    Deduplication and platform targeting tuned

    Time-windowed dedupe and AnalyticsTargetPlatform detection rolled out across web and mobile.

This case study describes patterns and architecture inferred from commit history at Runo, a private codebase. Business outcomes (retention lift, funnel conversion changes) are intentionally omitted because they aren’t independently verifiable from available source material. Claims here are phrased as “worked on” / “contributed to” rather than “led,” per the evidence policy in this site’s README.

Problem & Context

Before this work, analytics events at Runo were scattered across the codebase rather than expressed through one coherent system. Tracking calls were added ad hoc wherever a feature needed visibility, which made it hard to reason about event identity, user/company context, deduplication, or platform-specific behavior (web vs. mobile, sandbox vs. production).

Solution & Architecture

The work centered on building a ProductAnalytics abstraction over an AmplitudeProvider, so the rest of the app could call a single tracking surface without knowing which backend (or environment) it was hitting.

Diagram source (mermaid)
flowchart LR
  A[App event call sites] --> B[ProductAnalytics facade]
  B --> C{Environment check}
  C -->|production, logged in| D[AmplitudeProvider]
  C -->|sandbox / not logged in| E[No-op / logged skip]
  D --> F[Amplitude]
  B --> G[CrmActivityAccumulator]
  G --> H[(Hive local storage)]

Key pieces:

  • Provider abstractionAmplitudeProvider implements an AnalyticsProvider interface, so initialization, identify, track, and flush logic live behind one contract.
  • User/company traits — structured ProductAnalyticsUserTraits rather than ad hoc property maps, covering signup date, team size, and account-level metadata.
  • Deduplication — a time-windowed dedupe check (tuned over several iterations) to avoid double-counting events fired from overlapping code paths.
  • Platform targetingAnalyticsTargetPlatform detection so web and mobile contexts can diverge where needed (e.g., disabling page-view autocapture on web, enabling it selectively on mobile).
  • CRM activity accumulationCrmActivityAccumulator, backed by Hive, summarizes CRM activity locally before it’s reported, rather than firing one event per micro-action.

Key Decisions & Tradeoffs

Sandbox/non-production hosts intentionally skip Amplitude initialization, with an explicit

forceSandbox escape hatch for testing — trading a small amount of debug friction for protecting production analytics data quality.

  • Chose to centralize identification logic in ProductAnalyticsUtils rather than scattering identify() calls near each login/signup site, after observing duplicate identification attempts.
  • Increased the event deduplication window after follow-up commits suggested the original window was too short for catching the events it was meant to catch.
  • Renamed report_events to activity_events and moved internal_analytics to product_analytics — a deliberate internal-naming cleanup that traded short-term churn for clearer long-term boundaries between “raw analytics events” and “CRM activity summaries.”

Implementation Details

Instrumentation was added across login/signup, payment success, billing flows, attendance, rechurn, templates, allocation rules, CRM fields, search execution, detail views, and filters — using the same AppEvent shape so new events follow an existing pattern rather than inventing local conventions each time.

Lessons Learned

  • Analytics infrastructure is the kind of work that’s easy to defer because it touches everything and failures are subtle — but the deduplication and skip-logging additions only became necessary after shipping the first version and seeing noisy data in practice.
  • Flush behavior needed to become asynchronous and tied to app lifecycle changes (not just timers) to avoid losing events on background/close.

[!WARNING] Earlier versions of this system had an auto-identification path that was later removed — a reminder that “automatically identify when possible” can quietly create duplicate or premature identify calls if not scoped carefully to login state.