Repository navigation
feat: add provider-agnostic analytics layer - #161
Open
devs-rootstrap wants to merge 2 commits into
Open
devs-rootstrap wants to merge 2 commits into
devs-rootstrap wants to merge 2 commits into
Conversation
💯 Test Coverage
😎 Tests Results
👀 Tests Details • (59%)
|
There was a problem hiding this comment.
🟡 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]); |
This branch has not been deployed
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.

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: theAnalyticsProviderAdaptercontract (init,trackEvent,trackScreen, optionalidentify/reset) and a typed event catalogue (AnalyticsEventParams,AnalyticsEvent = keyof AnalyticsEventParams).trackEventis type-checked on both the event name and its params; events declared withundefinedparams can be tracked without a payload.src/components/providers/analytics.tsx:AnalyticsProvider(React context) that initialises every registered provider once on mount, plus theuseAnalytics()hook exposingtrackEvent,trackScreen,identifyandreset. Every call fans out to all providers; failures (sync throws or rejected promises) are isolated per provider and logged withconsole.error, so a broken SDK never crashes the app or blocks the other providers.src/components/analytics-screen-tracker.tsx: automatic screen tracking. It readsusePathname()from expo-router and callstrackScreen(pathname)on every route change.src/lib/analytics/console-provider.ts: built-inconsoleAnalyticsProviderthat 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: wiresAnalyticsProvider(insideAuthProvider) andAnalyticsScreenTracker; the provider list is a module-level constant.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.mdprovider hierarchy andagent_docs/architecture.mdare updated accordingly.__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:
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?
AnalyticsProvideris added betweenAuthProviderandBottomSheetModalProvider. No behaviour change for existing screens.@/libnow also re-exports the analytics module (consoleAnalyticsProviderand the analytics types).[analytics] ...lines for init, screen views and events. In production the default provider does nothing.package.jsonandpnpm-lock.yamlare untouched.Assumptions
google-services.json/GoogleService-Info.plist) and the@react-native-firebase/appconfig 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./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 sharedsrc/libexports, with no changes to auth, payments or native code.How did you test this?
Verification
pnpm lint: passed (exit 0)pnpm type-check: passed (exit 0); this also validates the@ts-expect-errorassertions covering the typed event cataloguepnpm lint:translations: passed (exit 0)pnpm test: passed, 31 suites / 152 tests (exit 0)How to test locally
pnpm installandpnpm start:development, then open the app on a simulator or device.[analytics] initand[analytics] screen: /...; navigate between tabs and screens and a newscreen:line appears for each route change.useAnalytics().trackEvent('BUTTON_PRESS', { accessibilityId: 'some-id' })and check the[analytics] event: BUTTON_PRESS { accessibilityId: 'some-id' }line.trackEvent('NOT_IN_CATALOGUE', {}):pnpm type-checkfails, as intended.pnpm testto run the unit tests.Notes:
identify/resetare 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.