Mobile Analytics
Purpose
Guide for implementing mobile app analytics: event tracking, screen views, user properties, and privacy compliance.
Agent Protocol
Trigger
Phrases: "analytics", "event tracking", "Firebase Analytics", "Mixpanel", "Amplitude", "app analytics", "event logging", "user properties", "screen tracking", "funnel analytics", "user behavior", "telemetry"
Input Context
- Analytics provider (Firebase, Mixpanel, Amplitude, or custom)
- Event taxonomy and naming conventions
- Privacy requirements (GDPR, CCPA, ATT)
- Existing navigation system for automatic screen tracking
Output Artifact
Analytics module: provider initialization, event tracking service, screen view auto-tracker, user properties manager, consent management, privacy controls.
Response Format
No preamble. No postamble. No explanations. No filler/hedging/transitions. Compress output — why use many token when few do trick.
Completion Criteria
- Events fire with correct schema and reach analytics dashboard
- Screen views tracked automatically via navigation listener
- User properties set on login/update
- GDPR consent flow blocks non-essential events
- ATT prompt appears on iOS 14.5+
- User deletion API removes all stored analytics data
Max Response Length
6000 tokens
Architecture
Analytics Service Layer Pattern
AnalyticsService (Facade)
├── Consent checking before forwarding
├── Property enrichment (device, session, version)
├── Rate limiting and batching
├── Multi-provider routing
│
├── Provider A (Firebase)
├── Provider B (Mixpanel)
└── File/Log Fallback
Decision Tree: Provider Selection
What is your budget?
├── $0/month → Firebase Analytics
│ Sufficient for: event tracking, basic funnels, 500 user properties
│ Not sufficient for: behavioral cohorts, advanced retention, A/B testing
├── $500-2000/month MTU → Mixpanel or Amplitude
│ Choose Mixpanel: better for product-led growth, viral loops
│ Choose Amplitude: better for behavioral analytics, predictive models
└── Enterprise → Custom server + warehouse
Full data control, custom dashboards, no per-event cost
Decision Tree: SDK Initialization Strategy
Init timing?
├── Before app root renders → Firebase, Mixpanel, Amplitude (they start collecting immediately)
│ Must: wrap in try/catch, never crash on init failure
├── After first frame → Non-critical analytics (custom server, niche providers)
│ Screen view events from first frame won't be captured
└── Lazy init on first user action → Privacy-first approach
Show consent dialog first, init only after consent
Decision Tree: Identity Strategy
User lifecycle?
├── Anonymous first, then sign up → Identity stitching
│ On signup: alias anonymous ID to user ID
├── Login required to use app → User ID immediately
│ Set user ID on auth success, reset on logout
└── No auth → Device ID
Use advertising ID (ATT on iOS) or vendor ID
Workflow
Analytics SDK selection — Three categories of analytics providers. Free tier: Firebase Analytics (Google) — unlimited events, 500 user properties per app, basic segmentation (by device, country, version), funnels in Google Analytics 4, no cost. Good for most apps. Paid tiers: Mixpanel and Amplitude — advanced user profiling, behavioral cohorts, retention analysis, A/B testing, revenue tracking, funnel analysis with time-to-convert, custom dashboards, data warehouses export. Pricing based on MTU (monthly tracked users). Custom server: full data control, no third-party dependency, implement your own event ingestion, storage, dashboard, GDPR compliance certainty, higher engineering cost. Recommendation: start with Firebase Analytics (free + sufficient), migrate to Mixpanel/Amplitude when advanced segmentation needed.
Event naming taxonomy — Consistent naming convention is critical for usable analytics. Event names: snake_case, max 40 characters. Pattern: {domain}_{action}_{object} or {screen}_{action}. Examples: profile_edit_name, cart_add_item, settings_toggle_notifications, checkout_payment_complete. Reserved prefixes: screen_, error_, funnel_, perf_. Event properties: up to 25 per event, typed (string, number, boolean), keys also snake_case, max 40 chars per key, max 100 chars per string value. Never pass PII in properties. Use enum or constant class for event names and property keys to avoid typos and ensure discoverability. Version event schemas in a tracking plan document shared across team.
Automatic screen tracking — Hook into navigation system to dispatch screen_view events automatically. iOS: swizzle or UINavigationControllerDelegate to catch push/pop transitions. Android: NavController.OnDestinationChangedListener for Jetpack Navigation, or FragmentManager.FragmentLifecycleCallbacks. Cross-platform: React Navigation state listener, Flutter Navigator observer. Screen view event should include: screen_name (human-readable, e.g., "Profile"), screen_class (class name, e.g., "ProfileViewController"), screen_route (route pattern, e.g., "/user/:id"). Never manually fire screen view events — use the automatic hook. If a screen doesn't appear in dashboard, fix the navigation hook, not by adding manual calls.
User properties and identity — Set user properties at login/signup and on change. Properties: plan type, subscription status, days since install, push enabled, language, region, A/B test cohort. Use setUserId for cross-session user identity — use a stable identifier (not email or phone). Anonymize user ID for GDPR compliance (hash-based, server-generated). Identity stitching: when user signs up after anonymous usage, call identify() with new ID and alias anonymous events to new ID. Property limits: Firebase allows up to 500 user properties, Mixpanel/Amplitude have higher limits. Set all known properties at login to establish baseline for funnels and retention cohorts.
Custom event tracking — Feature usage: {feature}_{action} with relevant properties (e.g., search_filter_applied with filter type, result count). Non-fatal errors: error_{domain} with error_message, error_code, screen_name. Performance: perf_screen_load with duration_ms, perf_api_latency with endpoint, status_code. Conversion funnels: funnel_{name}_{step} with step number, step name. Use a single AnalyticsService facade class that all features call — never call provider SDKs directly. The facade handles consent checking, provider routing, property enrichment (device info, app version, session ID). This enables changing providers without modifying feature code.
Consent management and GDPR compliance — GDPR: consent dialog on first launch (opt-in required for non-essential events). Categories: essential (app lifecycle, crashes — always tracked), functional (user preferences, feature usage), analytics (behavioral tracking), personalization (advertising, content recommendation). CCPA: "Do Not Sell My Info" toggle in app settings. ATT (iOS 14.5+): ATTrackingManager.requestTrackingAuthorization() before accessing IDFA — required for personalized analytics, ad attribution, and cross-app tracking. Flow: Launch -> check consent status -> if not decided -> show consent dialog -> user choices -> persist preferences -> configure analytics provider accordingly. Android: Google Consent Management SDK for serving consent dialogs. For EU users, analytics must be stopped until consent is granted.
Data retention and deletion — Configure data retention in analytics provider: Firebase Analytics max 24 months, Mixpanel/Amplitude configurable (up to unlimited on enterprise). Implement user data deletion: (a) Call provider's deletion API (analytics.deleteUserData()), (b) Clear local analytics cache/queue, (c) Reset analytics user ID (generate new anonymous ID). For custom server: implement DELETE /api/analytics/user/{userId} endpoint that removes all events, properties, and profiles. Trigger deletion from app settings with confirmation dialog. Audit compliance: maintain a record of what data is collected, where it's stored, and how it can be deleted. Review at each major release.
Event batching and offline queue — Mobile analytics must handle unreliable network conditions. Implement an event queue that persists events to local storage when the device is offline and flushes when connectivity returns. Batch configuration: batch size of 25-50 events or 60-second interval (whichever comes first). Queue limits: cap at 500 events in the offline queue to avoid excessive storage usage. Oldest events are dropped first if the cap is exceeded. Flush priority: critical events (purchases, errors) flush on an expedited timer of 10 seconds. Implement exponential backoff for failed flushes: 2s, 4s, 8s, 16s, max 60s. On app background: force flush all queued events synchronously.
A/B test integration — When using Mixpanel or Amplitude, analytics and experimentation are coupled. Configure experiments in the analytics provider dashboard with targeting rules (country, user property, cohort). The SDK returns the variant assignment for each active experiment. Log experiment exposure as a dedicated event with experiment_id, variant_name, and user_properties so analysis can filter by experiment cohort. Never hardcode experiment logic — use a feature flag abstraction layer that consults the analytics provider's experiment API. Roll out gradually: 1% -> 5% -> 25% -> 50% -> 100%, monitoring core metrics at each step for regression.
Data quality monitoring — Analytics is only useful if the data is correct. Implement event schema validation: assert that every event has the expected properties with correct types. Log schema violations to a separate monitoring dashboard. Track event volume anomalies: if an event fires 10x more or less than the daily average, alert the team. Run daily data quality checks: expected events received, null property ratios, unexpected NaN or negative values. Monitor event latency: 95th percentile time from event fire to dashboard appearance should be under 5 minutes for real-time providers and under 2 hours for batch-export providers.
Analytics Provider Comparison
| Feature |
Firebase |
Mixpanel |
Amplitude |
Custom |
| Cost |
Free |
Paid (MTU) |
Paid (MTU) |
Engineering |
| Event limit |
Unlimited |
20M/mo (starter) |
10M/mo (starter) |
Unlimited |
| User properties |
500 |
Unlimited |
Unlimited |
Unlimited |
| Funnels |
Basic |
Advanced |
Advanced |
Custom |
| Retention |
Yes |
Yes |
Yes |
Custom |
| A/B testing |
No |
Yes |
Yes |
Custom |
| Data export |
BigQuery |
Warehouse sync |
Warehouse sync |
Direct |
| Real-time |
Near-real-time |
Real-time |
Real-time |
Configurable |
| Offline support |
Built-in |
Built-in |
Built-in |
Custom |
Best Practices
- Single
AnalyticsService facade — never call provider SDKs from feature code
- Event names:
snake_case, <=40 chars — consistent naming prevents analytics debt
- Properties limited to 25 per event — no unbounded property bags
- No PII in event properties: no email, name, phone, government ID
- Screen views tracked automatically via navigation listener — never manual
- Provide opt-out mechanism accessible from app settings
- Version event schemas in a tracking plan — share across team, review each sprint
- Instrument events for features you actively analyze, not everything possible
- Test analytics in CI: integration tests should verify events fire with correct schema
- Set user properties at login to establish baseline for all downstream analysis
- Batch events for efficiency, flush on background for completeness
- Monitor data quality with automated schema validation and volume anomaly alerts
- Keep a changelog of event schema changes to track analytics debt
Common Pitfalls
- Event naming inconsistency:
user_logged_in vs login_complete creates split funnels. Define naming convention upfront and enforce with lint rules.
- Over-tracking: Too many events dilute signal and increase cost. Track what you analyze, not everything possible.
- Consent fragment: Consent must apply to all provider SDKs — checking consent for Firebase but not Adjust is a compliance gap.
- ATT prompt timing: Prompt immediately on first launch = user denies. Contextual prompt (before personalized feature) has higher acceptance.
- Missing events after refactor: Event calls get lost during code churn. Integration tests should verify events fire.
- Identity fragmentation: Anonymous events before login, identified events after login — without identity stitching, funnels break at the login step.
- Property cardinality explosion: Using unique values (timestamps, UUIDs) as event properties creates unbounded cardinality that breaks segmentation.
- Delayed analytics SDK init: If the SDK initializes after the first screen renders, the initial screen_view event is lost. Init analytics before app root renders.
- Debug mode leaks: Debug/release analytics keys committed to the wrong build config results in test data polluting production dashboards.
- Event schema drift: After months of development, event payloads drift from the original tracking plan. Regular audits catch drift before dashboards break.
Performance
- Event batching: 25-50 events or 60-second interval reduces network calls by 95% vs per-event dispatch
- Payload size: average event ~500 bytes JSON compressed (device info, timestamp, properties)
- Network overhead: batched flush of 50 events ~5KB per request — negligible for cellular
- Storage: offline queue capped at 500 events ~250KB max local storage
- CPU impact: analytics SDK adds 1-3ms on the main thread per event fire — use background thread for serialization
- Memory: analytics libraries add 2-8MB to the app binary (Firebase ~5MB, Mixpanel ~3MB, Amplitude ~4MB)
- Startup delay: async analytics SDK init avoids blocking TTI — init in background, flush after first frame
Rules
- Never store user PII in event properties or user property names
- Screen views must be tracked automatically via navigation listener — explicit manual screen events are forbidden
- Every analytics event must pass through the AnalyticsService facade — direct SDK calls from feature code are not permitted
- Event naming must use
snake_case with max 40 characters per name
- Event properties must be typed (string, number, boolean) with max 25 per event
- Identity stitching must be implemented when users transition from anonymous to authenticated state
- All analytics tracking must respect the user's consent choices for each data category
- ATT prompt on iOS 14.5+ must appear before accessing IDFA for any purpose
- Event schemas must be versioned in a tracking plan document that is reviewed with every feature release
- Data retention policies must be configured in every analytics provider used by the app
- User data deletion API must remove all analytics data across all providers and local storage
- Analytics SDK initialization must not block the first render — lazy-init or async-init pattern required
- Offline event queue must have a bounded size with oldest-drop policy to prevent unbounded storage growth
- Production and debug analytics keys must be in separate build configurations — never ship debug keys to production
Deferred Deep Linking & Analytics Attribution
Deep linking attribution requires mapping installs back to the marketing source that drove them. The attribution chain works as follows: (1) user taps an analytics-tracked link containing campaign parameters, (2) link redirects through the attribution SDK's click tracker, (3) if the app isn't installed, the Store redirect saves the click data to the SDK's server, (4) on first app launch, the attributions SDK queries its server for pending attribution data, (5) the analytics SDK then sets user properties for install_source, campaign_id, ad_network, and click_timestamp. This allows analytics queries to segment by acquisition channel. The attribution window is typically 7-30 days after click. Implement attribution as a separate concern from analytics — use an attribution SDK (Adjust, Branch, AppsFlyer) alongside your analytics SDK.
Server-Side Analytics Validation
Event data flowing from mobile apps should be validated server-side before entering the analytics pipeline. Implement a validation proxy between the mobile client and your analytics provider: (1) validate event schema (required properties present, correct types, no PII leaking), (2) validate property cardinality (no more than 25 unique values for partition keys), (3) reject events exceeding 32KB payload limit, (4) apply rate limits per user (100 events/second max), (5) filter bot traffic and test device IDs. Use a serverless function or dedicated validation service. Log rejected events to a separate dead-letter queue for auditing and debugging. Schema validation catches tracking plan drift before it pollutes dashboards.
A/B Test Architecture Decision Tree
Experimentation needs?
├── No experimentation → Skip. Don't add A/B framework until needed.
├── Simple feature flags → Firebase Remote Config / LaunchDarkly
│ └── Boolean flags, gradual rollout, kill switch
├── Product A/B tests → Mixpanel Experiments / Amplitude Experiment
│ └── Full statistical engine, sample size calculator, MVT
└── Enterprise experimentation → Google Optimize / Optimizely
└── Server-side, personalization, audience targeting
Sampling Strategy Decision Tree
Event volume?
├── <10M events/month → No sampling. Track everything.
├── 10M-100M events/month → Adaptive sampling
│ └── High-value events: 100% (purchase, login, error)
│ └── Low-value events: 10% (scroll, hover, background)
└── >100M events/month → Fixed-rate sampling per event type
└── Set sample rates per event: critical=100%, important=50%, verbose=5%
└── Document sampling rate per event in tracking plan
Production Considerations
Analytics Failure Modes
| Failure |
Symptom |
Mitigation |
| SDK init crash |
App crashes on launch |
Wrap init in try/catch, never block launch |
| Queue overflow |
Events lost after queue cap |
Set bounded queue (max 500), oldest-drop |
| Network timeouts |
Events stuck in queue |
Exponential backoff flush (2s→60s), flush on app bg |
| Schema drift |
Dashboard values look wrong |
CI schema validation, tracking plan audit |
| Provider outage |
No events for hours |
Multi-provider routing, failover to log file |
| Consent lost |
Essential events also stop |
Separate essential from non-essential queues |
Troubleshooting Checklist
- Verify events appear in provider debug view after 5 minutes
- Check device logs for analytics SDK errors (look for provider name)
- Confirm consent status is correctly persisted between app launches
- Validate event payload size is under 32KB
- Ensure analytics SDK initialized before navigation system
- Test on airplane mode: events should queue and flush when reconnected
- Check user property cardinality — high-cardinality properties break segmentation
CI/CD Integration
- Run analytics schema validation as CI step using a local validation script
- Maintain a tracking plan YAML checked into the repo
- CI validates all new/edited events against the tracking plan
- Integration tests assert events fire with correct properties
- Periodic (nightly) data quality jobs compare actual event counts vs. expected
- Deploy analytics config changes separately from app releases (remote config)
Code Examples
Firebase Analytics Swift
import FirebaseAnalytics
final class AnalyticsService {
static let shared = AnalyticsService()
private var isInitialized = false
func initialize() {
FirebaseApp.configure()
isInitialized = true
}
func logEvent(_ name: String, parameters: [String: Any]? = nil) {
guard isInitialized, ConsentManager.shouldTrack(.analytics) else { return }
var enriched = parameters ?? [:]
enriched["app_version"] = App.version
enriched["session_id"] = SessionManager.current.id
Analytics.logEvent(name, parameters: enriched)
}
func setUserProperty(_ value: String?, forName name: String) {
guard ConsentManager.shouldTrack(.analytics) else { return }
Analytics.setUserProperty(value, forName: name)
}
}
Mixpanel Kotlin
class MixpanelAnalyticsProvider(private val context: Context) : AnalyticsProvider {
private lateinit var mixpanel: MixpanelAPI
override fun initialize(token: String) {
mixpanel = MixpanelAPI.getInstance(context, token)
}
override fun trackEvent(name: String, properties: Map<String, Any>?) {
mixpanel.track(name, properties)
}
override fun identify(userId: String) {
mixpanel.identify(userId)
}
override fun alias(anonymousId: String, userId: String) {
mixpanel.alias(anonymousId, userId)
}
override fun setUserProperties(properties: Map<String, Any>) {
val updater = mixpanel.people
properties.forEach { (key, value) -> updater.set(key, value) }
}
override fun flush() {
mixpanel.flush()
}
}
Amplitude React Native
import { init, track, identify, Identify } from '@amplitude/analytics-react-native';
export class AmplitudeService {
private initialized = false;
async init(apiKey: string, userId?: string) {
await init(apiKey, userId, {
flushIntervalMillis: 30000,
flushQueueSize: 30,
optOut: !ConsentManager.consentGiven,
}).promise;
this.initialized = true;
}
track(eventName: string, properties?: Record<string, any>) {
if (!this.initialized || !ConsentManager.shouldTrack('analytics')) return;
track(eventName, properties);
}
setUserId(id: string) {
identify(new Identify().set('userId', id));
}
setUserProperty(name: string, value: string | number | boolean) {
identify(new Identify().set(name, value));
}
}
Event Taxonomy Decision Tree
Event type?
├── User action → `{domain}_{verb}_{object}`
│ e.g., `cart_add_item`, `profile_edit_photo`, `search_submit_query`
│ Properties: object_id, object_type, result_count, duration_ms
├── Screen view → `screen_{name}`
│ e.g., `screen_home`, `screen_product_detail`, `screen_checkout_payment`
│ Properties: referrer, previous_screen, route
├── System event → `{domain}_{event}`
│ e.g., `session_start`, `push_received`, `sync_completed`
│ Properties: source, trigger, duration_ms, success
├── Error / non-fatal → `error_{domain}`
│ e.g., `error_network_timeout`, `error_api_validation`, `error_ui_render`
│ Properties: error_code, error_message, screen, retry_count
└── Performance → `perf_{metric}`
e.g., `perf_screen_load`, `perf_api_latency`, `perf_db_query`
Properties: duration_ms, byte_size, endpoint, cache_hit
Event Naming Convention Comparison
| Convention |
Example |
Pros |
Cons |
snake_case |
cart_add_item |
Readable, standard in SQL/analytics DBs |
Requires conversion for JS clients |
camelCase |
cartAddItem |
Matches JS/TS code style |
Awkward in SQL queries |
SCREAMING_SNAKE |
CART_ADD_ITEM |
Clearly separates from code |
Verbose, looks like constants |
| dot-notation |
cart.add.item |
Hierarchical, good for grouping |
Special handling in BigQuery column names |
{object}:{action} |
cart:add_item |
Clear domain boundary |
Colon requires escaping in some systems |
Recommendation: snake_case for all analytics events — it's SQL-friendly, readable, and consistent across platforms.
Anonymous Event Identity Strategy
User state at event time?
├── No user ID available → Log with device_id + session_id
│ device_id: vendor identifier (iOS) or Advertising ID (Android with ATT)
│ session_id: generated on each app launch, rotated on background >30min
├── User logs in mid-session → Call identify() with user_id
│ Server aliases anonymous session to user_id for stitching
├── User logs out → Reset analytics user ID
│ Generate new anonymous ID, do NOT reuse previous
└── Multi-account → Each login creates a new analytics identity chain
Previous account events retain old user_id, no cross-account linking
Analytics Implementation Checklist
Analytics Implementation Anti-Patterns & Patterns
- Tracking plan as an afterthought: Without a documented tracking plan, events drift within weeks. Write the plan before instrumenting any code.
- Server-side events duplicated as client events: Payment confirmation fires both from server and client. Server events are authoritative for revenue; client events provide UX context.
- Funnel analysis without time boundaries: "Users who do X and Y" without time constraints includes users who did Y weeks after X. Set a session window (same session) or time window (7 days).
- Sampling before understanding volume: Many providers sample by default at the free tier. Know your sample rate — 10% of events means 10x confidence interval on metrics.
- One massive tracking plan document: A single doc for 500 events becomes unmanageable. Split by domain area (Orders, Accounts, Content) with separate event tables.
- Not tracking property type information: "event properties are strings" loses numeric/boolean type data in analytics warehouses. Enforce types with schema validation.
- Events on every scroll/keystroke: At scale, high-frequency events cause data quality issues and cost. Debounce scroll events (every 3s, not every frame). Batch input events.
- Notification events without delivery status: Track
push_sent, push_delivered, push_opened — the gap between sent and delivered reveals delivery issues.
Analytics Cost Optimization
Analytics costs grow linearly with tracked users. Strategies to manage cost:
- Event sampling: Sample low-value events (scroll, hover) at 10% rate. Keep high-value (purchase, login) at 100%. Adjust sample rate per event type in the tracking plan.
- Property pruning: Remove unused properties from events after 30 days. Each property adds to storage and query cost.
- User property limits: Firebase allows 500 user properties — stay under 200 to leave room for growth. Purge unused properties quarterly.
- Data retention: Set retention to 12 months for event data, 24 months for user properties. Raw event data older than retention is expensive to store and rarely queried.
- Batch export: Use BigQuery export (Firebase) or warehouse sync (Mixpanel/Amplitude) for deep analysis. Avoid expensive live queries on large datasets.
- Cold storage: Archive event data older than 6 months to cold storage (S3 Glacier, GCS Archive). Reheat only when needed for specific analysis.
Diagnostic Decision Tree for Missing Events
Event not appearing in dashboard?
├── Was the SDK initialized? → Check initialization log, verify API key
├── Is consent granted? → Check consent manager status for the event category
├── Is the event being filtered? → Check debug/release build config, environment tag
├── Was the device offline? → Check offline queue flush on reconnection
├── Is the event name correct? → Verify exact string matches tracking plan
├── Are properties within limits? → Max 25 properties, values <100 chars, no PII filter
├── Is the user rate limited? → Check per-user event rate limits (100/s typical)
└── Is the provider experiencing an outage? → Check status dashboard
References
- references/analytics-privacy.md — Analytics Privacy
- references/analytics-reporting.md — Analytics Dashboards and Reporting
- references/analytics-sdks.md — Analytics SDKs
- references/analytics-setup.md — Analytics Setup
- references/event-tracking.md — Event Tracking & Privacy
- references/mobile-analytics.md — Mobile Analytics Implementation
- references/mobile-analytics-event-tracking.md — Mobile Analytics Event Tracking
- references/mobile-analytics-privacy-compliance.md — Mobile Analytics Privacy and Compliance
- references/analytics-fundamentals.md — Analytics Fundamentals
- references/analytics-advanced.md — Advanced Analytics Patterns
- references/analytics-debugging.md — Analytics Debugging & Testing
Handoff
Hand off to mobile-crash-reporting skill when Crashlytics integration is needed, or to mobile-networking when custom analytics server endpoint is required.
Implementation Patterns
Observer Pattern for Event Handling
`
interface EventObserver {
onEvent(event: T): Promise;
}
class EventBus {
private observers: Set<EventObserver> = new Set();
subscribe(observer: EventObserver): void {
this.observers.add(observer);
}
unsubscribe(observer: EventObserver): void {
this.observers.delete(observer);
}
async emit(event: T): Promise {
const results = Array.from(this.observers).map(o => o.onEvent(event));
await Promise.allSettled(results);
}
}
`
Configuration-Driven Approach
config: defaults: timeout: 30s retryCount: 3 overrides: production: timeout: 60s retryCount: 5 development: timeout: 300s retryCount: 1
Production Considerations
Deployment Checklist
Monitoring and Alerting
| Metric |
Threshold |
Severity |
Action |
| Error rate |
> 1% over 5min |
Critical |
Page on-call |
| p99 latency |
> 2s over 5min |
Warning |
Investigate |
| Throughput drop |
> 50% over 1min |
Critical |
Check upstream |
| Queue depth |
> 1000 over 1min |
Warning |
Scale consumers |
| Disk usage |
> 85% |
Warning |
Clean or expand |
| Memory usage |
> 90% heap |
Critical |
Restart or scale |
Anti-Patterns
| Anti-Pattern |
Symptom |
Root Cause |
Solution |
| Premature optimization |
Complex code for no measured benefit |
Guessing instead of profiling |
Measure first, optimize based on data |
| Copy-paste reuse |
Duplicate code across codebase |
Lack of abstraction |
Extract shared logic into libraries |
| Gold-plating |
Features with no current requirement |
Over-engineering |
YAGNI — build what's needed now |
| Magical thinking |
Assumptions without validation |
Skipping error handling |
Handle all failure modes explicitly |
Performance Optimization
Caching Strategy
Cache hierarchy: L1 (in-memory local) → L2 (distributed Redis/Memcached) → L3 (CDN/Edge).
Cache invalidation: TTL-based (simple, stale), event-based (complex, fresh), write-through (consistent, higher write latency), write-behind (fast writes, eventual consistency).
Resource Pooling
- Database connections: Pool of reusable connections (HikariCP, pgBouncer)
- HTTP connections: Keep-alive + connection pooling for external calls
- Thread pool: Bounded thread pools for async task execution
Profiling Methodology
- Establish baseline with production traffic profile
- Profile CPU with sampling profiler (pprof, perf, async-profiler)
- Profile memory with heap dumps and allocation tracking
- Profile I/O with strace/perf trace for syscall analysis
- Profile latency with distributed tracing (OpenTelemetry)
- Identify bottleneck, formulate hypothesis, implement fix
- Re-profile to verify improvement, repeat
Security Considerations
Threat Modeling (STRIDE)
- Spoofing: Identity validation, authentication
- Tampering: Integrity checks, digital signatures
- Repudiation: Audit logs, non-repudiation
- Information disclosure: Encryption, access control
- Denial of service: Rate limiting, resource quotas
- Elevation of privilege: Principle of least privilege
Supply Chain Security
- Dependency scanning: Snyk, Dependabot, Trivy
- SBOM generation: CycloneDX or SPDX format
- Signed commits: GPG or SSH commit signing
- Artifact verification: Checksum validation, signature verification
Secrets Management
- Secrets never in code — always in secrets manager (Vault, AWS Secrets Manager)
- Rotation policy: Rotate database credentials every 90 days
- Access audit: Log every secrets access, alert on anomalies
- Encryption at rest and in transit for all secrets
- Principle of least privilege: each service gets only its own secrets
Rules
- Default-deny security posture — allow only explicitly required access.
- All inputs validated, all outputs encoded, all errors handled.
- Defend in depth — multiple layers of security controls.
- Fail securely — errors default to safe behavior.
- Log security-relevant events for audit and investigation.
- Keep dependencies updated — automate vulnerability scanning.
- Design for observability from day one, not as an afterthought.
- Document all architectural decisions with rationale.
- Review code for security, performance, and correctness before merging.
1---2name: mobile-analytics3description: Use this skill when the user says 'analytics', 'event tracking', 'Firebase Analytics', 'Mixpanel', 'Amplitude', 'app analytics', 'event logging', 'user properties', 'screen tracking', 'funnel analytics', 'user behavior', 'telemetry'. Track events, screen views, and user properties in mobile apps with privacy compliance. Do NOT use for: server-side analytics or web analytics.4license: MIT5---67# Mobile Analytics89## Purpose10Guide for implementing mobile app analytics: event tracking, screen views, user properties, and privacy compliance.1112## Agent Protocol1314### Trigger15Phrases: "analytics", "event tracking", "Firebase Analytics", "Mixpanel", "Amplitude", "app analytics", "event logging", "user properties", "screen tracking", "funnel analytics", "user behavior", "telemetry"1617### Input Context18- Analytics provider (Firebase, Mixpanel, Amplitude, or custom)19- Event taxonomy and naming conventions20- Privacy requirements (GDPR, CCPA, ATT)21- Existing navigation system for automatic screen tracking2223### Output Artifact24Analytics module: provider initialization, event tracking service, screen view auto-tracker, user properties manager, consent management, privacy controls.2526### Response Format27No preamble. No postamble. No explanations. No filler/hedging/transitions. Compress output — why use many token when few do trick.2829### Completion Criteria30- Events fire with correct schema and reach analytics dashboard31- Screen views tracked automatically via navigation listener32- User properties set on login/update33- GDPR consent flow blocks non-essential events34- ATT prompt appears on iOS 14.5+35- User deletion API removes all stored analytics data3637### Max Response Length386000 tokens3940## Architecture4142### Analytics Service Layer Pattern43```44AnalyticsService (Facade)45├── Consent checking before forwarding46├── Property enrichment (device, session, version)47├── Rate limiting and batching48├── Multi-provider routing49│50├── Provider A (Firebase)51├── Provider B (Mixpanel)52└── File/Log Fallback53```5455### Decision Tree: Provider Selection56```57What is your budget?58├── $0/month → Firebase Analytics59│ Sufficient for: event tracking, basic funnels, 500 user properties60│ Not sufficient for: behavioral cohorts, advanced retention, A/B testing61├── $500-2000/month MTU → Mixpanel or Amplitude62│ Choose Mixpanel: better for product-led growth, viral loops63│ Choose Amplitude: better for behavioral analytics, predictive models64└── Enterprise → Custom server + warehouse65 Full data control, custom dashboards, no per-event cost66```6768### Decision Tree: SDK Initialization Strategy69```70Init timing?71├── Before app root renders → Firebase, Mixpanel, Amplitude (they start collecting immediately)72│ Must: wrap in try/catch, never crash on init failure73├── After first frame → Non-critical analytics (custom server, niche providers)74│ Screen view events from first frame won't be captured75└── Lazy init on first user action → Privacy-first approach76 Show consent dialog first, init only after consent77```7879### Decision Tree: Identity Strategy80```81User lifecycle?82├── Anonymous first, then sign up → Identity stitching83│ On signup: alias anonymous ID to user ID84├── Login required to use app → User ID immediately85│ Set user ID on auth success, reset on logout86└── No auth → Device ID87 Use advertising ID (ATT on iOS) or vendor ID88```8990## Workflow91921. **Analytics SDK selection** — Three categories of analytics providers. Free tier: Firebase Analytics (Google) — unlimited events, 500 user properties per app, basic segmentation (by device, country, version), funnels in Google Analytics 4, no cost. Good for most apps. Paid tiers: Mixpanel and Amplitude — advanced user profiling, behavioral cohorts, retention analysis, A/B testing, revenue tracking, funnel analysis with time-to-convert, custom dashboards, data warehouses export. Pricing based on MTU (monthly tracked users). Custom server: full data control, no third-party dependency, implement your own event ingestion, storage, dashboard, GDPR compliance certainty, higher engineering cost. Recommendation: start with Firebase Analytics (free + sufficient), migrate to Mixpanel/Amplitude when advanced segmentation needed.93942. **Event naming taxonomy** — Consistent naming convention is critical for usable analytics. Event names: `snake_case`, max 40 characters. Pattern: `{domain}_{action}_{object}` or `{screen}_{action}`. Examples: `profile_edit_name`, `cart_add_item`, `settings_toggle_notifications`, `checkout_payment_complete`. Reserved prefixes: `screen_`, `error_`, `funnel_`, `perf_`. Event properties: up to 25 per event, typed (string, number, boolean), keys also `snake_case`, max 40 chars per key, max 100 chars per string value. Never pass PII in properties. Use enum or constant class for event names and property keys to avoid typos and ensure discoverability. Version event schemas in a tracking plan document shared across team.95963. **Automatic screen tracking** — Hook into navigation system to dispatch `screen_view` events automatically. iOS: swizzle or `UINavigationControllerDelegate` to catch push/pop transitions. Android: `NavController.OnDestinationChangedListener` for Jetpack Navigation, or `FragmentManager.FragmentLifecycleCallbacks`. Cross-platform: React Navigation state listener, Flutter Navigator observer. Screen view event should include: `screen_name` (human-readable, e.g., "Profile"), `screen_class` (class name, e.g., "ProfileViewController"), `screen_route` (route pattern, e.g., "/user/:id"). Never manually fire screen view events — use the automatic hook. If a screen doesn't appear in dashboard, fix the navigation hook, not by adding manual calls.97984. **User properties and identity** — Set user properties at login/signup and on change. Properties: plan type, subscription status, days since install, push enabled, language, region, A/B test cohort. Use `setUserId` for cross-session user identity — use a stable identifier (not email or phone). Anonymize user ID for GDPR compliance (hash-based, server-generated). Identity stitching: when user signs up after anonymous usage, call `identify()` with new ID and alias anonymous events to new ID. Property limits: Firebase allows up to 500 user properties, Mixpanel/Amplitude have higher limits. Set all known properties at login to establish baseline for funnels and retention cohorts.991005. **Custom event tracking** — Feature usage: `{feature}_{action}` with relevant properties (e.g., `search_filter_applied` with filter type, result count). Non-fatal errors: `error_{domain}` with `error_message`, `error_code`, `screen_name`. Performance: `perf_screen_load` with `duration_ms`, `perf_api_latency` with `endpoint`, `status_code`. Conversion funnels: `funnel_{name}_{step}` with step number, step name. Use a single `AnalyticsService` facade class that all features call — never call provider SDKs directly. The facade handles consent checking, provider routing, property enrichment (device info, app version, session ID). This enables changing providers without modifying feature code.1011026. **Consent management and GDPR compliance** — GDPR: consent dialog on first launch (opt-in required for non-essential events). Categories: essential (app lifecycle, crashes — always tracked), functional (user preferences, feature usage), analytics (behavioral tracking), personalization (advertising, content recommendation). CCPA: "Do Not Sell My Info" toggle in app settings. ATT (iOS 14.5+): `ATTrackingManager.requestTrackingAuthorization()` before accessing IDFA — required for personalized analytics, ad attribution, and cross-app tracking. Flow: Launch -> check consent status -> if not decided -> show consent dialog -> user choices -> persist preferences -> configure analytics provider accordingly. Android: Google Consent Management SDK for serving consent dialogs. For EU users, analytics must be stopped until consent is granted.1031047. **Data retention and deletion** — Configure data retention in analytics provider: Firebase Analytics max 24 months, Mixpanel/Amplitude configurable (up to unlimited on enterprise). Implement user data deletion: (a) Call provider's deletion API (`analytics.deleteUserData()`), (b) Clear local analytics cache/queue, (c) Reset analytics user ID (generate new anonymous ID). For custom server: implement `DELETE /api/analytics/user/{userId}` endpoint that removes all events, properties, and profiles. Trigger deletion from app settings with confirmation dialog. Audit compliance: maintain a record of what data is collected, where it's stored, and how it can be deleted. Review at each major release.1051068. **Event batching and offline queue** — Mobile analytics must handle unreliable network conditions. Implement an event queue that persists events to local storage when the device is offline and flushes when connectivity returns. Batch configuration: batch size of 25-50 events or 60-second interval (whichever comes first). Queue limits: cap at 500 events in the offline queue to avoid excessive storage usage. Oldest events are dropped first if the cap is exceeded. Flush priority: critical events (purchases, errors) flush on an expedited timer of 10 seconds. Implement exponential backoff for failed flushes: 2s, 4s, 8s, 16s, max 60s. On app background: force flush all queued events synchronously.1071089. **A/B test integration** — When using Mixpanel or Amplitude, analytics and experimentation are coupled. Configure experiments in the analytics provider dashboard with targeting rules (country, user property, cohort). The SDK returns the variant assignment for each active experiment. Log experiment exposure as a dedicated event with `experiment_id`, `variant_name`, and `user_properties` so analysis can filter by experiment cohort. Never hardcode experiment logic — use a feature flag abstraction layer that consults the analytics provider's experiment API. Roll out gradually: 1% -> 5% -> 25% -> 50% -> 100%, monitoring core metrics at each step for regression.10911010. **Data quality monitoring** — Analytics is only useful if the data is correct. Implement event schema validation: assert that every event has the expected properties with correct types. Log schema violations to a separate monitoring dashboard. Track event volume anomalies: if an event fires 10x more or less than the daily average, alert the team. Run daily data quality checks: expected events received, null property ratios, unexpected NaN or negative values. Monitor event latency: 95th percentile time from event fire to dashboard appearance should be under 5 minutes for real-time providers and under 2 hours for batch-export providers.111112## Analytics Provider Comparison113114| Feature | Firebase | Mixpanel | Amplitude | Custom |115|---------|----------|----------|-----------|--------|116| Cost | Free | Paid (MTU) | Paid (MTU) | Engineering |117| Event limit | Unlimited | 20M/mo (starter) | 10M/mo (starter) | Unlimited |118| User properties | 500 | Unlimited | Unlimited | Unlimited |119| Funnels | Basic | Advanced | Advanced | Custom |120| Retention | Yes | Yes | Yes | Custom |121| A/B testing | No | Yes | Yes | Custom |122| Data export | BigQuery | Warehouse sync | Warehouse sync | Direct |123| Real-time | Near-real-time | Real-time | Real-time | Configurable |124| Offline support | Built-in | Built-in | Built-in | Custom |125126## Best Practices127128- Single `AnalyticsService` facade — never call provider SDKs from feature code129- Event names: `snake_case`, <=40 chars — consistent naming prevents analytics debt130- Properties limited to 25 per event — no unbounded property bags131- No PII in event properties: no email, name, phone, government ID132- Screen views tracked automatically via navigation listener — never manual133- Provide opt-out mechanism accessible from app settings134- Version event schemas in a tracking plan — share across team, review each sprint135- Instrument events for features you actively analyze, not everything possible136- Test analytics in CI: integration tests should verify events fire with correct schema137- Set user properties at login to establish baseline for all downstream analysis138- Batch events for efficiency, flush on background for completeness139- Monitor data quality with automated schema validation and volume anomaly alerts140- Keep a changelog of event schema changes to track analytics debt141142## Common Pitfalls143144- **Event naming inconsistency**: `user_logged_in` vs `login_complete` creates split funnels. Define naming convention upfront and enforce with lint rules.145- **Over-tracking**: Too many events dilute signal and increase cost. Track what you analyze, not everything possible.146- **Consent fragment**: Consent must apply to all provider SDKs — checking consent for Firebase but not Adjust is a compliance gap.147- **ATT prompt timing**: Prompt immediately on first launch = user denies. Contextual prompt (before personalized feature) has higher acceptance.148- **Missing events after refactor**: Event calls get lost during code churn. Integration tests should verify events fire.149- **Identity fragmentation**: Anonymous events before login, identified events after login — without identity stitching, funnels break at the login step.150- **Property cardinality explosion**: Using unique values (timestamps, UUIDs) as event properties creates unbounded cardinality that breaks segmentation.151- **Delayed analytics SDK init**: If the SDK initializes after the first screen renders, the initial screen_view event is lost. Init analytics before app root renders.152- **Debug mode leaks**: Debug/release analytics keys committed to the wrong build config results in test data polluting production dashboards.153- **Event schema drift**: After months of development, event payloads drift from the original tracking plan. Regular audits catch drift before dashboards break.154155## Performance156157- Event batching: 25-50 events or 60-second interval reduces network calls by 95% vs per-event dispatch158- Payload size: average event ~500 bytes JSON compressed (device info, timestamp, properties)159- Network overhead: batched flush of 50 events ~5KB per request — negligible for cellular160- Storage: offline queue capped at 500 events ~250KB max local storage161- CPU impact: analytics SDK adds 1-3ms on the main thread per event fire — use background thread for serialization162- Memory: analytics libraries add 2-8MB to the app binary (Firebase ~5MB, Mixpanel ~3MB, Amplitude ~4MB)163- Startup delay: async analytics SDK init avoids blocking TTI — init in background, flush after first frame164165## Rules166167- Never store user PII in event properties or user property names168- Screen views must be tracked automatically via navigation listener — explicit manual screen events are forbidden169- Every analytics event must pass through the AnalyticsService facade — direct SDK calls from feature code are not permitted170- Event naming must use `snake_case` with max 40 characters per name171- Event properties must be typed (string, number, boolean) with max 25 per event172- Identity stitching must be implemented when users transition from anonymous to authenticated state173- All analytics tracking must respect the user's consent choices for each data category174- ATT prompt on iOS 14.5+ must appear before accessing IDFA for any purpose175- Event schemas must be versioned in a tracking plan document that is reviewed with every feature release176- Data retention policies must be configured in every analytics provider used by the app177- User data deletion API must remove all analytics data across all providers and local storage178- Analytics SDK initialization must not block the first render — lazy-init or async-init pattern required179- Offline event queue must have a bounded size with oldest-drop policy to prevent unbounded storage growth180- Production and debug analytics keys must be in separate build configurations — never ship debug keys to production181182## Deferred Deep Linking & Analytics Attribution183184Deep linking attribution requires mapping installs back to the marketing source that drove them. The attribution chain works as follows: (1) user taps an analytics-tracked link containing campaign parameters, (2) link redirects through the attribution SDK's click tracker, (3) if the app isn't installed, the Store redirect saves the click data to the SDK's server, (4) on first app launch, the attributions SDK queries its server for pending attribution data, (5) the analytics SDK then sets user properties for `install_source`, `campaign_id`, `ad_network`, and `click_timestamp`. This allows analytics queries to segment by acquisition channel. The attribution window is typically 7-30 days after click. Implement attribution as a separate concern from analytics — use an attribution SDK (Adjust, Branch, AppsFlyer) alongside your analytics SDK.185186## Server-Side Analytics Validation187188Event data flowing from mobile apps should be validated server-side before entering the analytics pipeline. Implement a validation proxy between the mobile client and your analytics provider: (1) validate event schema (required properties present, correct types, no PII leaking), (2) validate property cardinality (no more than 25 unique values for partition keys), (3) reject events exceeding 32KB payload limit, (4) apply rate limits per user (100 events/second max), (5) filter bot traffic and test device IDs. Use a serverless function or dedicated validation service. Log rejected events to a separate dead-letter queue for auditing and debugging. Schema validation catches tracking plan drift before it pollutes dashboards.189190## A/B Test Architecture Decision Tree191192```193Experimentation needs?194├── No experimentation → Skip. Don't add A/B framework until needed.195├── Simple feature flags → Firebase Remote Config / LaunchDarkly196│ └── Boolean flags, gradual rollout, kill switch197├── Product A/B tests → Mixpanel Experiments / Amplitude Experiment198│ └── Full statistical engine, sample size calculator, MVT199└── Enterprise experimentation → Google Optimize / Optimizely200 └── Server-side, personalization, audience targeting201```202203## Sampling Strategy Decision Tree204205```206Event volume?207├── <10M events/month → No sampling. Track everything.208├── 10M-100M events/month → Adaptive sampling209│ └── High-value events: 100% (purchase, login, error)210│ └── Low-value events: 10% (scroll, hover, background)211└── >100M events/month → Fixed-rate sampling per event type212 └── Set sample rates per event: critical=100%, important=50%, verbose=5%213 └── Document sampling rate per event in tracking plan214```215216## Production Considerations217218### Analytics Failure Modes219220| Failure | Symptom | Mitigation |221|---------|---------|------------|222| SDK init crash | App crashes on launch | Wrap init in try/catch, never block launch |223| Queue overflow | Events lost after queue cap | Set bounded queue (max 500), oldest-drop |224| Network timeouts | Events stuck in queue | Exponential backoff flush (2s→60s), flush on app bg |225| Schema drift | Dashboard values look wrong | CI schema validation, tracking plan audit |226| Provider outage | No events for hours | Multi-provider routing, failover to log file |227| Consent lost | Essential events also stop | Separate essential from non-essential queues |228229### Troubleshooting Checklist230231- Verify events appear in provider debug view after 5 minutes232- Check device logs for analytics SDK errors (look for provider name)233- Confirm consent status is correctly persisted between app launches234- Validate event payload size is under 32KB235- Ensure analytics SDK initialized before navigation system236- Test on airplane mode: events should queue and flush when reconnected237- Check user property cardinality — high-cardinality properties break segmentation238239### CI/CD Integration240241- Run analytics schema validation as CI step using a local validation script242- Maintain a tracking plan YAML checked into the repo243- CI validates all new/edited events against the tracking plan244- Integration tests assert events fire with correct properties245- Periodic (nightly) data quality jobs compare actual event counts vs. expected246- Deploy analytics config changes separately from app releases (remote config)247248## Code Examples249250### Firebase Analytics Swift251```swift252import FirebaseAnalytics253254final class AnalyticsService {255 static let shared = AnalyticsService()256 private var isInitialized = false257258 func initialize() {259 FirebaseApp.configure()260 isInitialized = true261 }262263 func logEvent(_ name: String, parameters: [String: Any]? = nil) {264 guard isInitialized, ConsentManager.shouldTrack(.analytics) else { return }265 var enriched = parameters ?? [:]266 enriched["app_version"] = App.version267 enriched["session_id"] = SessionManager.current.id268 Analytics.logEvent(name, parameters: enriched)269 }270271 func setUserProperty(_ value: String?, forName name: String) {272 guard ConsentManager.shouldTrack(.analytics) else { return }273 Analytics.setUserProperty(value, forName: name)274 }275}276```277278### Mixpanel Kotlin279```kotlin280class MixpanelAnalyticsProvider(private val context: Context) : AnalyticsProvider {281 private lateinit var mixpanel: MixpanelAPI282283 override fun initialize(token: String) {284 mixpanel = MixpanelAPI.getInstance(context, token)285 }286287 override fun trackEvent(name: String, properties: Map<String, Any>?) {288 mixpanel.track(name, properties)289 }290291 override fun identify(userId: String) {292 mixpanel.identify(userId)293 }294295 override fun alias(anonymousId: String, userId: String) {296 mixpanel.alias(anonymousId, userId)297 }298299 override fun setUserProperties(properties: Map<String, Any>) {300 val updater = mixpanel.people301 properties.forEach { (key, value) -> updater.set(key, value) }302 }303304 override fun flush() {305 mixpanel.flush()306 }307}308```309310### Amplitude React Native311```typescript312import { init, track, identify, Identify } from '@amplitude/analytics-react-native';313314export class AmplitudeService {315 private initialized = false;316317 async init(apiKey: string, userId?: string) {318 await init(apiKey, userId, {319 flushIntervalMillis: 30000,320 flushQueueSize: 30,321 optOut: !ConsentManager.consentGiven,322 }).promise;323 this.initialized = true;324 }325326 track(eventName: string, properties?: Record<string, any>) {327 if (!this.initialized || !ConsentManager.shouldTrack('analytics')) return;328 track(eventName, properties);329 }330331 setUserId(id: string) {332 identify(new Identify().set('userId', id));333 }334335 setUserProperty(name: string, value: string | number | boolean) {336 identify(new Identify().set(name, value));337 }338}339```340341### Event Taxonomy Decision Tree342```343Event type?344├── User action → `{domain}_{verb}_{object}`345│ e.g., `cart_add_item`, `profile_edit_photo`, `search_submit_query`346│ Properties: object_id, object_type, result_count, duration_ms347├── Screen view → `screen_{name}`348│ e.g., `screen_home`, `screen_product_detail`, `screen_checkout_payment`349│ Properties: referrer, previous_screen, route350├── System event → `{domain}_{event}`351│ e.g., `session_start`, `push_received`, `sync_completed`352│ Properties: source, trigger, duration_ms, success353├── Error / non-fatal → `error_{domain}`354│ e.g., `error_network_timeout`, `error_api_validation`, `error_ui_render`355│ Properties: error_code, error_message, screen, retry_count356└── Performance → `perf_{metric}`357 e.g., `perf_screen_load`, `perf_api_latency`, `perf_db_query`358 Properties: duration_ms, byte_size, endpoint, cache_hit359```360361### Event Naming Convention Comparison362363| Convention | Example | Pros | Cons |364|------------|---------|------|------|365| `snake_case` | `cart_add_item` | Readable, standard in SQL/analytics DBs | Requires conversion for JS clients |366| `camelCase` | `cartAddItem` | Matches JS/TS code style | Awkward in SQL queries |367| `SCREAMING_SNAKE` | `CART_ADD_ITEM` | Clearly separates from code | Verbose, looks like constants |368| dot-notation | `cart.add.item` | Hierarchical, good for grouping | Special handling in BigQuery column names |369| `{object}:{action}` | `cart:add_item` | Clear domain boundary | Colon requires escaping in some systems |370371Recommendation: `snake_case` for all analytics events — it's SQL-friendly, readable, and consistent across platforms.372373### Anonymous Event Identity Strategy374```375User state at event time?376├── No user ID available → Log with device_id + session_id377│ device_id: vendor identifier (iOS) or Advertising ID (Android with ATT)378│ session_id: generated on each app launch, rotated on background >30min379├── User logs in mid-session → Call identify() with user_id380│ Server aliases anonymous session to user_id for stitching381├── User logs out → Reset analytics user ID382│ Generate new anonymous ID, do NOT reuse previous383└── Multi-account → Each login creates a new analytics identity chain384 Previous account events retain old user_id, no cross-account linking385```386387## Analytics Implementation Checklist388389- [ ] Provider SDK initialized before app root renders (async init)390- [ ] Event names use `snake_case`, max 40 chars391- [ ] Properties typed, max 25 per event, max 100 chars per string value392- [ ] No PII in any event property393- [ ] Screen views tracked via navigation listener (not manual)394- [ ] Consent management implemented respecting GDPR/CCPA categories395- [ ] ATT prompt (iOS 14.5+) with contextual timing396- [ ] Identity stitching on anonymous-to-authenticated transition397- [ ] User properties set on login/update398- [ ] Offline queue with bounded size (max 500), oldest-drop policy399- [ ] Event batching: 25-50 events or 60s interval400- [ ] Exponential backoff for failed flushes (2s→60s)401- [ ] Production/debug keys in separate configs402- [ ] Schema validation in CI403- [ ] Event schema changelog in tracking plan404- [ ] Data retention configured per provider405- [ ] User deletion API integrated406- [ ] Debug mode verifies events reach dashboard407408### Analytics Implementation Anti-Patterns & Patterns409410- **Tracking plan as an afterthought**: Without a documented tracking plan, events drift within weeks. Write the plan before instrumenting any code.411- **Server-side events duplicated as client events**: Payment confirmation fires both from server and client. Server events are authoritative for revenue; client events provide UX context.412- **Funnel analysis without time boundaries**: "Users who do X and Y" without time constraints includes users who did Y weeks after X. Set a session window (same session) or time window (7 days).413- **Sampling before understanding volume**: Many providers sample by default at the free tier. Know your sample rate — 10% of events means 10x confidence interval on metrics.414- **One massive tracking plan document**: A single doc for 500 events becomes unmanageable. Split by domain area (Orders, Accounts, Content) with separate event tables.415- **Not tracking property type information**: "event properties are strings" loses numeric/boolean type data in analytics warehouses. Enforce types with schema validation.416- **Events on every scroll/keystroke**: At scale, high-frequency events cause data quality issues and cost. Debounce scroll events (every 3s, not every frame). Batch input events.417- **Notification events without delivery status**: Track `push_sent`, `push_delivered`, `push_opened` — the gap between sent and delivered reveals delivery issues.418419### Analytics Cost Optimization420421Analytics costs grow linearly with tracked users. Strategies to manage cost:422- **Event sampling**: Sample low-value events (scroll, hover) at 10% rate. Keep high-value (purchase, login) at 100%. Adjust sample rate per event type in the tracking plan.423- **Property pruning**: Remove unused properties from events after 30 days. Each property adds to storage and query cost.424- **User property limits**: Firebase allows 500 user properties — stay under 200 to leave room for growth. Purge unused properties quarterly.425- **Data retention**: Set retention to 12 months for event data, 24 months for user properties. Raw event data older than retention is expensive to store and rarely queried.426- **Batch export**: Use BigQuery export (Firebase) or warehouse sync (Mixpanel/Amplitude) for deep analysis. Avoid expensive live queries on large datasets.427- **Cold storage**: Archive event data older than 6 months to cold storage (S3 Glacier, GCS Archive). Reheat only when needed for specific analysis.428429### Diagnostic Decision Tree for Missing Events430```431Event not appearing in dashboard?432├── Was the SDK initialized? → Check initialization log, verify API key433├── Is consent granted? → Check consent manager status for the event category434├── Is the event being filtered? → Check debug/release build config, environment tag435├── Was the device offline? → Check offline queue flush on reconnection436├── Is the event name correct? → Verify exact string matches tracking plan437├── Are properties within limits? → Max 25 properties, values <100 chars, no PII filter438├── Is the user rate limited? → Check per-user event rate limits (100/s typical)439└── Is the provider experiencing an outage? → Check status dashboard440```441442## References443 - references/analytics-privacy.md — Analytics Privacy444 - references/analytics-reporting.md — Analytics Dashboards and Reporting445 - references/analytics-sdks.md — Analytics SDKs446 - references/analytics-setup.md — Analytics Setup447 - references/event-tracking.md — Event Tracking & Privacy448 - references/mobile-analytics.md — Mobile Analytics Implementation449 - references/mobile-analytics-event-tracking.md — Mobile Analytics Event Tracking450 - references/mobile-analytics-privacy-compliance.md — Mobile Analytics Privacy and Compliance451 - references/analytics-fundamentals.md — Analytics Fundamentals452 - references/analytics-advanced.md — Advanced Analytics Patterns453 - references/analytics-debugging.md — Analytics Debugging & Testing454455## Handoff456Hand off to mobile-crash-reporting skill when Crashlytics integration is needed, or to mobile-networking when custom analytics server endpoint is required.457## Implementation Patterns458459### Observer Pattern for Event Handling460`461interface EventObserver<T> {462 onEvent(event: T): Promise<void>;463}464465class EventBus<T> {466 private observers: Set<EventObserver<T>> = new Set();467 subscribe(observer: EventObserver<T>): void {468 this.observers.add(observer);469 }470 unsubscribe(observer: EventObserver<T>): void {471 this.observers.delete(observer);472 }473 async emit(event: T): Promise<void> {474 const results = Array.from(this.observers).map(o => o.onEvent(event));475 await Promise.allSettled(results);476 }477}478`479480### Configuration-Driven Approach481`482config:483 defaults:484 timeout: 30s485 retryCount: 3486 overrides:487 production:488 timeout: 60s489 retryCount: 5490 development:491 timeout: 300s492 retryCount: 1493`494495## Production Considerations496497### Deployment Checklist498- [ ] Configuration validated against schema before startup499- [ ] Health check endpoints registered and monitored500- [ ] Graceful shutdown with draining period (30s timeout)501- [ ] Resource limits configured (CPU, memory, file descriptors)502- [ ] Log level set appropriate for environment503- [ ] Metrics endpoint secured and exposed504- [ ] Rate limiting configured per-tier505- [ ] TLS certificates valid and auto-renewing506- [ ] Database migrations run as separate deployment step507- [ ] Feature flags ready for gradual rollout508509### Monitoring and Alerting510| Metric | Threshold | Severity | Action |511|--------|-----------|----------|--------|512| Error rate | > 1% over 5min | Critical | Page on-call |513| p99 latency | > 2s over 5min | Warning | Investigate |514| Throughput drop | > 50% over 1min | Critical | Check upstream |515| Queue depth | > 1000 over 1min | Warning | Scale consumers |516| Disk usage | > 85% | Warning | Clean or expand |517| Memory usage | > 90% heap | Critical | Restart or scale |518519## Anti-Patterns520521| Anti-Pattern | Symptom | Root Cause | Solution |522|-------------|---------|------------|----------|523| Premature optimization | Complex code for no measured benefit | Guessing instead of profiling | Measure first, optimize based on data |524| Copy-paste reuse | Duplicate code across codebase | Lack of abstraction | Extract shared logic into libraries |525| Gold-plating | Features with no current requirement | Over-engineering | YAGNI — build what's needed now |526| Magical thinking | Assumptions without validation | Skipping error handling | Handle all failure modes explicitly |527528## Performance Optimization529530### Caching Strategy531Cache hierarchy: L1 (in-memory local) → L2 (distributed Redis/Memcached) → L3 (CDN/Edge).532Cache invalidation: TTL-based (simple, stale), event-based (complex, fresh), write-through (consistent, higher write latency), write-behind (fast writes, eventual consistency).533534### Resource Pooling535- Database connections: Pool of reusable connections (HikariCP, pgBouncer)536- HTTP connections: Keep-alive + connection pooling for external calls537- Thread pool: Bounded thread pools for async task execution538539### Profiling Methodology5401. Establish baseline with production traffic profile5412. Profile CPU with sampling profiler (pprof, perf, async-profiler)5423. Profile memory with heap dumps and allocation tracking5434. Profile I/O with strace/perf trace for syscall analysis5445. Profile latency with distributed tracing (OpenTelemetry)5456. Identify bottleneck, formulate hypothesis, implement fix5467. Re-profile to verify improvement, repeat547548## Security Considerations549550### Threat Modeling (STRIDE)551- Spoofing: Identity validation, authentication552- Tampering: Integrity checks, digital signatures553- Repudiation: Audit logs, non-repudiation554- Information disclosure: Encryption, access control555- Denial of service: Rate limiting, resource quotas556- Elevation of privilege: Principle of least privilege557558### Supply Chain Security559- Dependency scanning: Snyk, Dependabot, Trivy560- SBOM generation: CycloneDX or SPDX format561- Signed commits: GPG or SSH commit signing562- Artifact verification: Checksum validation, signature verification563564### Secrets Management565- Secrets never in code — always in secrets manager (Vault, AWS Secrets Manager)566- Rotation policy: Rotate database credentials every 90 days567- Access audit: Log every secrets access, alert on anomalies568- Encryption at rest and in transit for all secrets569- Principle of least privilege: each service gets only its own secrets570571## Rules572- Default-deny security posture — allow only explicitly required access.573- All inputs validated, all outputs encoded, all errors handled.574- Defend in depth — multiple layers of security controls.575- Fail securely — errors default to safe behavior.576- Log security-relevant events for audit and investigation.577- Keep dependencies updated — automate vulnerability scanning.578- Design for observability from day one, not as an afterthought.579- Document all architectural decisions with rationale.580- Review code for security, performance, and correctness before merging.