RORK LABJP
MAX — Rork Max builds native Swift apps instead of React Native for iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessageNATIVE — Rork Max unlocks native features like AR/LiDAR scanning, Metal 3D games, widgets, and Dynamic IslandAPPLE — It also reaches advanced Apple capabilities such as HealthKit, HomeKit, NFC, App Clips, and on-device Core MLRN — Standard Rork builds iOS and Android from a single prompt using React Native (Expo)PRICE — Rork is free to start, with paid plans beginning at $25 per monthGROWTH — Rork now sees 743,000+ monthly visits, 85% growth, and recently raised $15MMAX — Rork Max builds native Swift apps instead of React Native for iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessageNATIVE — Rork Max unlocks native features like AR/LiDAR scanning, Metal 3D games, widgets, and Dynamic IslandAPPLE — It also reaches advanced Apple capabilities such as HealthKit, HomeKit, NFC, App Clips, and on-device Core MLRN — Standard Rork builds iOS and Android from a single prompt using React Native (Expo)PRICE — Rork is free to start, with paid plans beginning at $25 per monthGROWTH — Rork now sees 743,000+ monthly visits, 85% growth, and recently raised $15M
Articles/Dev Tools
Dev Tools/2026-07-09Advanced

When Amplitude's Funnel Improved Overnight — Field Notes on Catching Event Schema Drift With a Self-Audit

In a Rork-built app, the Amplitude funnel showed signup-to-purchase conversion jumping from 8% to 19% overnight, yet revenue stayed flat. The cause was a property-name drift that inflated the count. Field notes on measuring the drift with an event contract and a runtime validator, then tightening it in stages.

Amplitudeanalytics4React Native200event designdata qualityExpo136funnel analysis

Premium Article

One morning the funnel showed signup-to-purchase conversion leaping from 8.1% to 19.4%. I had shipped nothing the night before. Something tightened in my chest. Then I checked Stripe — revenue had barely moved.

Conversion doubled while deposits stayed flat. That gap is the classic tell that your event tracking has quietly broken.

These are field notes on a schema drift I hit in a Rork-built React Native / Expo app instrumented with Amplitude — how I surfaced it with a runtime validator, measured the drift rate, and tightened it back in stages. This is not about installing the SDK. It is about what to do when the numbers start lying after you install it.

When numbers lie, the SDK throws nothing

The awkward thing about product analytics is that nothing fails when it breaks. Write track('Purchase Completed', { price: '¥580' }) and the SDK happily sends price as a string. Rows keep landing on the dashboard, looking perfectly ordinary.

Tracing it back, the cause was simple. From one update onward, the purchase event's property drifted from plan_id to planId (camelCase). Amplitude treats those as two different properties. On top of that, a duplicated launch path was firing Purchase Completed twice, and the funnel segment I was reading counted totals rather than uniques — so the number inflated.

Drift comes in more than one shape. The four I actually hit sort out like this.

Drift typeSymptomFunnel impact
Property name driftplan_id and planId mixedFilter conditions half-miss
Type driftprice sometimes number, sometimes stringSums and averages break
Double firingSame event on resume and on mountConversion reads higher than real
Event name driftPurchase Completed vs purchase_completedOne side vanishes from the funnel

All of these look fine to the SDK. That is precisely why the correctness of measurement is something you have to measure yourself.

Declare the event contract first

The turning point was fixing, in a single "contract," which events may fire and what properties they carry. Staring at scattered track() calls reveals nothing. You declare the intended shape first, then reconcile against it at runtime. The order matters.

I wrote the contract as types. Since TypeScript types vanish at runtime, I derive both the type and the validation schema from one object.

// analytics/contract.ts
// Event contract — the source of truth for allowed events and properties
export const EVENT_CONTRACT = {
  'Purchase Completed': {
    plan_id: 'string',
    price: 'number',
    currency: 'string',
  },
  'Registration Completed': {
    auth_method: 'string', // 'email' | 'apple' | 'google'
  },
  'Paywall Viewed': {
    source: 'string',
  },
} as const;
 
export type EventName = keyof typeof EVENT_CONTRACT;
export type PropSpec = Record<string, 'string' | 'number' | 'boolean'>;

The key decision is to treat any event name or property not in the contract as an error. To add a new event, you add it to the contract first. That small friction stops naming drift and missing properties at the entrance.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
A concrete runtime validator that turns silent schema drift — events firing but lying — into a countable Event Contract Violation
How I measured the contamination rate and walked a fake 19% conversion back to the real 8%
A CI check that freezes the event contract so a Rork or Expo update can't quietly break it before release
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Dev Tools2026-07-08
Your SVG Icon Shows in Expo Go but Vanishes in a Build — Making SVGs Render Reliably in Rork Apps
When a .svg import renders nothing, throws a resolve error, or ignores your theme colors in Rork and Expo apps, here is how to fix it with metro.config.js, react-native-svg-transformer, and currentColor — with working code.
Dev Tools2026-07-08
When the Device Runs Out of Space, What Should Your App Protect?
Design Expo/React Native apps that assume writes can fail. Free-space budgets, LRU reclaim, data-protection tiers, and telemetry that surfaces silent failures — drawn from running six wallpaper apps.
Dev Tools2026-07-07
The App Icon Badge Still Says 3 — Rebuilding Expo Badge Counts Around a Single Source of Truth
Why an Expo app's icon badge drifts out of sync with real unread counts and refuses to clear — and how to rebuild it around a single source of truth, with working recompute-and-sync code and the production pitfalls that bite you.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →