Skip to content

feat: add provider-agnostic analytics layer - #161

Open
devs-rootstrap wants to merge 2 commits into
masterfrom
feat/analytics-layer
Open

devs-rootstrap wants to merge 2 commits into
masterfrom
feat/analytics-layer

Conversation

@devs-rootstrap

Copy link
Copy Markdown
Collaborator

Jira board reference:


What does this do?

Adds a provider-agnostic analytics layer so app code never talks to a vendor SDK directly:

  • src/lib/analytics/types.ts: the AnalyticsProviderAdapter contract (init, trackEvent, trackScreen, optional identify / reset) and a typed event catalogue (AnalyticsEventParams, AnalyticsEvent = keyof AnalyticsEventParams). trackEvent is type-checked on both the event name and its params; events declared with undefined params can be tracked without a payload.
  • src/components/providers/analytics.tsx: AnalyticsProvider (React context) that initialises every registered provider once on mount, plus the useAnalytics() hook exposing trackEvent, trackScreen, identify and reset. Every call fans out to all providers; failures (sync throws or rejected promises) are isolated per provider and logged with console.error, so a broken SDK never crashes the app or blocks the other providers.
  • src/components/analytics-screen-tracker.tsx: automatic screen tracking. It reads usePathname() from expo-router and calls trackScreen(pathname) on every route change.
  • src/lib/analytics/console-provider.ts: built-in consoleAnalyticsProvider that logs in __DEV__ and is a no-op otherwise. It is the default provider, so the template works out of the box with no vendor SDK and no native configuration.
  • src/app/_layout.tsx: wires AnalyticsProvider (inside AuthProvider) and AnalyticsScreenTracker; the provider list is a module-level constant.
  • Docs: new "Analytics" guide (docs/src/content/docs/guides/analytics.mdx, registered in the Guides sidebar) explaining the layer, how to add an event to the catalogue, and how to plug in a real vendor with a Firebase / Google Analytics adapter sketch. AGENTS.md provider hierarchy and agent_docs/architecture.md are updated accordingly.
  • Tests: __tests__/components/providers/analytics.test.tsx (init once, fan-out, optional methods, failure isolation, compile-time catalogue checks, usage from a component), __tests__/components/analytics-screen-tracker.test.tsx (tracks on mount and on route change only) and __tests__/lib/analytics/console-provider.test.ts (dev logging, production no-op).

Usage:

const analytics = useAnalytics();
analytics.trackEvent('BUTTON_PRESS', { accessibilityId: 'submit-button' });

Why did you do this?

Analytics should be present in every project from the start, but the vendor is often chosen or replaced later. Keeping the vendor behind a small adapter interface means swapping or adding a provider is a one-file change, screens stay free of SDK calls, and the event catalogue gives a single typed source of truth for what the app tracks.

Who/what does this impact?

  • Root provider tree: AnalyticsProvider is added between AuthProvider and BottomSheetModalProvider. No behaviour change for existing screens.
  • @/lib now also re-exports the analytics module (consoleAnalyticsProvider and the analytics types).
  • In development builds the console shows [analytics] ... lines for init, screen views and events. In production the default provider does nothing.
  • No new dependencies; package.json and pnpm-lock.yaml are untouched.

Assumptions

  • Google Analytics (Firebase) is not added as a dependency in this PR. It requires per-project native config files (google-services.json / GoogleService-Info.plist) and the @react-native-firebase/app config plugin, which would break builds in a template that does not ship them. The layer ships with the console provider and the guide documents how to add Firebase as a follow-up.
  • Screen names are reported as the expo-router pathname (e.g. /feed, /settings). Projects that need different names can map them inside their adapter.

Layer 2: Risk Classification

risk:medium: feature work touching the root layout and shared src/lib exports, with no changes to auth, payments or native code.

How did you test this?

  • Tested on iOS
  • Tested on Android
  • Tested on a small device
  • Tested on a real device
  • Tested all flows related with this PR changes
  • Tested accessibility
  • Added tests

Verification

  • pnpm lint: passed (exit 0)
  • pnpm type-check: passed (exit 0); this also validates the @ts-expect-error assertions covering the typed event catalogue
  • pnpm lint:translations: passed (exit 0)
  • pnpm test: passed, 31 suites / 152 tests (exit 0)
  • Not verified on a device or emulator.

How to test locally

  1. pnpm install and pnpm start:development, then open the app on a simulator or device.
  2. Watch the Metro console: on launch you should see [analytics] init and [analytics] screen: /...; navigate between tabs and screens and a new screen: line appears for each route change.
  3. In any component, call useAnalytics().trackEvent('BUTTON_PRESS', { accessibilityId: 'some-id' }) and check the [analytics] event: BUTTON_PRESS { accessibilityId: 'some-id' } line.
  4. Try trackEvent('NOT_IN_CATALOGUE', {}): pnpm type-check fails, as intended.
  5. pnpm test to run the unit tests.

Notes:

  • Follow-up: add a Firebase / Google Analytics adapter in projects that have the native config in place (see the "Plugging in a real provider" section of the new guide).
  • identify / reset are exposed by the hook but not yet called from the auth flow; wiring them to sign-in / sign-out can be done when a real provider is added.

Screenshots / Previews

Not applicable: no UI changes.

Copilot AI balanced review requested due to automatic review settings October 28, 2026 02:33
@devs-rootstrap
devs-rootstrap requested a review from a team as a code owner October 28, 2026 02:33
@devs-rootstrap devs-rootstrap added the risk:medium Feature work, refactors touching multiple files. label Oct 28, 2026
@github-actions

Copy link
Copy Markdown

💯 Test Coverage

Lines Statements Branches Functions
Coverage: 59%
58.75% (386/657) 60% (180/300) 53.38% (142/266)

😎 Tests Results

Tests Skipped Failures Errors Time
152 0 💤 0 ❌ 0 🔥 24.643s ⏱️
👀 Tests Details • (59%)
File% Stmts% Branch% Funcs% LinesUncovered Line #s
All files58.756053.3859.14 
src/app0000 
   _layout.tsx000025–125
src/components97.7710088.4697.77 
   analytics-screen-tracker.tsx100100100100 
src/components/providers74.1942.181.2572.72 
   analytics.tsx100100100100 
src/lib68.967571.4276.92 
   index.tsx0000 
src/lib/analytics100100100100 
   console-provider.ts100100100100 
   index.ts0000 
   types.ts0000 

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Startup analytics can be lost because screen-tracker calls are not queued until each adapter finishes initialization.

1 open issue
What changed in this PR

Overall Review: ai:serious

Adds a vendor-independent analytics layer to the Expo template, keeping SDK calls out of screens and providing development logging without new dependencies.

Changes:

  • Introduces typed events, an adapter contract, and a context API that dispatches calls to registered providers.
  • Wires automatic route tracking and the default console adapter into the root provider tree.
  • Adds tests and documentation for analytics usage and vendor integration.
File Description
src/​lib/​index.tsx Re-exports analytics utilities and types.
src/​lib/​analytics/​types.ts Defines the event catalogue and adapter contract.
src/​lib/​analytics/​index.ts Exports the analytics module.
src/​lib/​analytics/​console-provider.ts Adds development-only analytics logging.
src/​components/​providers/​analytics.tsx Provides the context API and isolated provider dispatch.
src/​components/​analytics-screen-tracker.tsx Tracks pathname changes automatically.
src/​app/​_layout.tsx Registers analytics providers and screen tracking.
docs/​src/​content/​docs/​guides/​analytics.mdx Documents usage, typed events, and vendor integration.
docs/​astro.config.mjs Adds the analytics guide to navigation.
AGENTS.md Updates the documented provider hierarchy.
agent_docs/​architecture.md Records analytics component and contract locations.
__tests__/​lib/​analytics/​console-provider.test.ts Tests development logging and production behavior.
__tests__/​components/​providers/​analytics.test.tsx Tests dispatch, failure isolation, and typed usage.
__tests__/​components/​analytics-screen-tracker.test.tsx Tests initial and route-change screen tracking.

Review effort: Balanced

Comment on lines +47 to +49
useEffect(() => {
fanOut(providers, async provider => provider.init());
}, [providers]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devs-rootstrap is this important?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:medium Feature work, refactors touching multiple files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants