> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zixflow.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Mobile App Analytics

> Mobile-specific tracking patterns — screens, features, purchases, and push.

Imagine you've built a mobile app — users install it, sign in, browse content, and eventually make a purchase or complete a key action. This guide walks through tracking the full app experience using the Zixflow Flutter SDK: from first install and screen navigation to feature usage, push notifications, and offline sessions.

***

## SDK Integration

### Flutter SDK Setup

**pubspec.yaml:**

```yaml theme={null}
dependencies:
  zixflow: ^1.0.0
```

**Initialize on app launch:**

```dart theme={null}
// lib/main.dart
import 'package:zixflow/zixflow.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Configure SDK
  const config = ZixflowConfig(
    siteId: 'your_workspace_id',
    apiKey: 'your_api_key',
    region: Region.US,
    autoTrackScreenViews: true,        // Auto-track screen events
    autoTrackDeviceAttributes: true,   // Capture OS, app version, etc.
    backgroundQueueMinNumberOfTasks: 10,  // Batch events
    backgroundQueueSecondsDelay: 30,      // Flush every 30s
  );
  
  await Zixflow.instance.initialize(config);
  
  runApp(MyApp());
}
```

**SDK behavior:**

* Events batched in memory (max 20 events or 30 seconds)
* On app background: Immediate flush
* On network failure: Persist to SQLite, retry with exponential backoff
* Battery-conscious: Batching reduces network calls by 90%

***

## App Lifecycle Events

### App Installed

**When:** First app launch after install (SDK auto-tracks)

**What's tracked automatically:**

```javascript theme={null}
{
  "event": "App Installed",
  "properties": {
    "app_version": "2.1.0",
    "build_number": "403",
    "install_date": "2026-05-04T10:00:00Z",
    "install_source": "App Store"  // iOS: App Store, Android: Play Store, APK
  },
  "context": {
    "os": { "name": "iOS", "version": "17.4.1" },
    "device": { "model": "iPhone 14 Pro", "manufacturer": "Apple" },
    "locale": "en-US",
    "timezone": "America/New_York"
  }
}
```

**Manual tracking (if you need attribution):**

```dart theme={null}
// After retrieving install attribution from AppsFlyer/Branch/Adjust
track('App Installed', {
  'install_source': 'App Store',
  'attribution_campaign': 'spring_2026_ios',
  'attribution_channel': 'google_uac',
  'attribution_ad_group': 'sneakers_lookalike',
  'install_cost': 2.45  // CPI from attribution provider
});
```

***

### App Opened

**When:** App moves from background to foreground

**Auto-tracked by SDK** (if `autoTrackAppLifecycle: true`):

```javascript theme={null}
{
  "event": "App Opened",
  "properties": {
    "cold_start": true,  // vs warm start (from background)
    "push_notification_triggered": false,  // Did user tap notification?
    "deeplink_url": null,  // If opened via deep link
    "session_id": "sess_abc-123",
    "time_since_last_open_seconds": 3600
  }
}
```

**Manual tracking (if you want custom logic):**

```dart theme={null}
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  DateTime? _lastBackgroundTime;
  
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }
  
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      // App opened
      final sessionGapMinutes = _lastBackgroundTime != null
          ? DateTime.now().difference(_lastBackgroundTime!).inMinutes
          : null;
      
      Zixflow.instance.track('App Opened', {
        'cold_start': _lastBackgroundTime == null,
        'session_gap_minutes': sessionGapMinutes,
        'previous_screen': _lastScreen,  // Where user was before backgrounding
      });
    } else if (state == AppLifecycleState.paused) {
      _lastBackgroundTime = DateTime.now();
    }
  }
}
```

***

### App Backgrounded

**When:** User switches to another app or locks screen

```dart theme={null}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
  if (state == AppLifecycleState.paused) {
    // Calculate session duration
    final sessionDuration = DateTime.now().difference(_sessionStartTime);
    
    Zixflow.instance.track('App Backgrounded', {
      'session_duration_seconds': sessionDuration.inSeconds,
      'screens_viewed': _screensViewedThisSession,
      'events_tracked': _eventsThisSession,
      'last_screen': _currentScreen,
      'actions_completed': _actionsCompletedThisSession,  // Purchases, signups, etc.
    });
    
    // Flush event queue immediately (don't wait for batch)
    Zixflow.instance.flush();
  }
}
```

**Analytics use cases:**

* Session duration distribution
* Screens per session
* Features used per session
* Session → Conversion rate

***

## Screen Tracking

### Automatic Screen Tracking (Recommended)

**Enable in SDK config:**

```dart theme={null}
const config = ZixflowConfig(
  autoTrackScreenViews: true,  // Auto-track on Navigator route changes
  // ...
);
```

**What's tracked:**

```javascript theme={null}
{
  "type": "screen",
  "name": "ProductDetailScreen",
  "properties": {
    "route": "/products/nike-air-max-90",
    "previous_screen": "HomeScreen",
    "screen_index": 3,  // 3rd screen in this session
    "session_id": "sess_abc-123"
  },
  "timestamp": "2026-05-04T10:15:30.123Z"
}
```

***

### Manual Screen Tracking (Granular Control)

**Use case:** You want to add screen-specific properties (product ID, category, etc.)

```dart theme={null}
// lib/screens/product_detail_screen.dart

class ProductDetailScreen extends StatefulWidget {
  final String productId;
  
  const ProductDetailScreen({required this.productId});
  
  @override
  _ProductDetailScreenState createState() => _ProductDetailScreenState();
}

class _ProductDetailScreenState extends State<ProductDetailScreen> {
  late Product _product;
  DateTime? _screenEnteredAt;
  
  @override
  void initState() {
    super.initState();
    _screenEnteredAt = DateTime.now();
    _loadProduct();
  }
  
  Future<void> _loadProduct() async {
    _product = await ProductService.getProduct(widget.productId);
    
    // Track screen view with product context
    Zixflow.instance.screen(
      name: 'Product Detail',
      properties: {
        'product_id': _product.id,
        'product_name': _product.name,
        'product_category': _product.category,
        'product_price': _product.price,
        'inventory_status': _product.inStock ? 'in_stock' : 'out_of_stock',
      },
    );
  }
  
  @override
  void dispose() {
    // Track time spent on screen
    if (_screenEnteredAt != null) {
      final timeOnScreen = DateTime.now().difference(_screenEnteredAt!);
      Zixflow.instance.track('Screen Exited', {
        'screen_name': 'Product Detail',
        'product_id': _product.id,
        'time_on_screen_seconds': timeOnScreen.inSeconds,
        'interaction_count': _interactionCount,  // Taps, swipes, etc.
      });
    }
    super.dispose();
  }
}
```

***

## Feature Usage Events

### Feature Discovered

**When:** User views feature for first time (onboarding, tutorial, tooltip)

```dart theme={null}
void _showFeatureTutorial() {
  setState(() => _tutorialVisible = true);
  
  Zixflow.instance.track('Feature Discovered', {
    'feature_name': 'ar_try_on',
    'discovery_method': 'tooltip',  // vs 'onboarding', 'organic', 'search'
    'days_since_signup': _daysSinceSignup,
    'onboarding_step': 3,
  });
}
```

**Analytics:** Feature discovery rate, time to discovery

***

### Feature Used

**When:** User actively engages with feature

```dart theme={null}
void _launchARTryOn() {
  Zixflow.instance.track('Feature Used', {
    'feature_name': 'ar_try_on',
    'product_id': widget.productId,
    'is_first_use': !_userProfile.hasUsedAR,
    'days_since_discovery': _daysSinceDiscovered,
  });
  
  // Launch AR experience
  Navigator.push(context, MaterialPageRoute(
    builder: (context) => ARTryOnScreen(product: _product),
  ));
  
  // Update profile attribute
  Zixflow.instance.setProfileAttributes({
    'has_used_ar': true,
    'ar_first_used_at': DateTime.now().toIso8601String(),
  });
}
```

***

### Search Performed

**When:** User submits search query

```dart theme={null}
class SearchScreen extends StatefulWidget {
  @override
  _SearchScreenState createState() => _SearchScreenState();
}

class _SearchScreenState extends State<SearchScreen> {
  final TextEditingController _searchController = TextEditingController();
  List<Product> _results = [];
  
  Future<void> _performSearch(String query) async {
    final results = await ProductService.search(query);
    
    setState(() => _results = results);
    
    Zixflow.instance.track('Search Performed', {
      'query': query,
      'results_count': results.length,
      'search_type': 'full_text',  // vs 'barcode_scan', 'voice', 'visual'
      'filters_applied': _activeFilters,
      'sort_by': _sortOrder,
    });
    
    // Track zero-results searches (product gap opportunity)
    if (results.isEmpty) {
      Zixflow.instance.track('Search No Results', {
        'query': query,
        'suggestions_shown': _suggestionEngine.getSuggestions(query),
      });
    }
  }
  
  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: _searchController,
      onSubmitted: _performSearch,
      decoration: InputDecoration(hintText: 'Search products...'),
    );
  }
}
```

***

### Content Shared

**When:** User shares via native share sheet

```dart theme={null}
void _shareProduct() async {
  Zixflow.instance.track('Content Shared', {
    'content_type': 'product',
    'product_id': _product.id,
    'product_name': _product.name,
    'share_method': 'native_sheet',  // vs 'copy_link', 'qr_code'
  });
  
  final result = await Share.share(
    'Check out ${_product.name}!\n${_product.url}',
    subject: _product.name,
  );
  
  // Track share outcome
  if (result.status == ShareResultStatus.success) {
    Zixflow.instance.track('Content Shared Success', {
      'product_id': _product.id,
      'platform': result.raw,  // 'WhatsApp', 'Messages', 'Twitter', etc.
    });
  }
}
```

**Analytics:** Viral coefficient, most-shared products

***

## Push Notification Events

### Push Permission Requested

**When:** App asks for notification permission

```dart theme={null}
void _requestPushPermission() async {
  Zixflow.instance.track('Push Permission Requested', {
    'prompt_type': 'soft_ask',  // Pre-permission priming
    'days_since_install': _daysSinceInstall,
    'screen': 'onboarding_step_3',
  });
  
  final granted = await _showPermissionDialog();
  
  if (granted) {
    _requestSystemPermission();
  } else {
    Zixflow.instance.track('Push Permission Soft Declined', {
      'reason': _userDeclineReason,  // From survey
    });
  }
}
```

***

### Push Permission Granted/Denied

**When:** User responds to system permission prompt

```dart theme={null}
void _requestSystemPermission() async {
  final settings = await FirebaseMessaging.instance.requestPermission(
    alert: true,
    badge: true,
    sound: true,
  );
  
  if (settings.authorizationStatus == AuthorizationStatus.authorized) {
    // Permission granted
    Zixflow.instance.track('Push Permission Granted', {
      'prompt_number': _promptAttemptCount,
      'days_since_install': _daysSinceInstall,
    });
    
    // Get device token and register
    final token = await FirebaseMessaging.instance.getToken();
    Zixflow.instance.registerDeviceToken(token!);
    
    // Update profile
    Zixflow.instance.setProfileAttributes({
      'push_enabled': true,
      'push_enabled_at': DateTime.now().toIso8601String(),
    });
    
    // Note: This call works correctly whether the user is identified or still anonymous.
    // If the user hasn't signed in yet, the device token is stored under their anonymousId
    // and automatically linked to their user profile when they call identify() at sign-up.
    // No extra re-registration step is needed after sign-up.
    
  } else {
    // Permission denied
    Zixflow.instance.track('Push Permission Denied', {
      'prompt_number': _promptAttemptCount,
      'days_since_install': _daysSinceInstall,
      'status': settings.authorizationStatus.toString(),
    });
  }
}
```

**Analytics:** Permission grant rate, optimal timing for prompt

***

### Push Received

**When:** Push notification arrives (app in foreground or background)

```dart theme={null}
// Configure FCM listener
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
  // App in foreground
  Zixflow.instance.track('Push Received', {
    'campaign_id': message.data['campaign_id'],
    'message_id': message.messageId,
    'title': message.notification?.title,
    'app_state': 'foreground',
  });
  
  // Show in-app notification (optional)
  _showInAppNotification(message);
});
```

***

### Push Opened

**When:** User taps notification (SDK auto-tracks)

**Auto-tracked:**

```javascript theme={null}
{
  "event": "Push Opened",
  "properties": {
    "campaign_id": "camp_spring_sale",
    "message_id": "msg_abc-123",
    "title": "Your cart is waiting!",
    "deep_link": "app://cart",
    "time_to_open_seconds": 120,  // Time since delivery
    "app_state": "background"  // App was backgrounded when tapped
  }
}
```

**Custom handling:**

```dart theme={null}
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
  Zixflow.instance.track('Push Opened', {
    'campaign_id': message.data['campaign_id'],
    'deep_link': message.data['deep_link'],
  });
  
  // Navigate to deep link destination
  _navigateToDeepLink(message.data['deep_link']);
});
```

***

## Offline Behavior

### Event Queueing

**SDK handles offline automatically:**

1. **Network unavailable:** Events persisted to SQLite
2. **Battery optimization:** Batch 20 events per network call
3. **Retry logic:** Exponential backoff (1s, 2s, 4s, 8s, 16s)
4. **Max retries:** 10 attempts, then discard (prevents infinite queue growth)

**Developer control:**

```dart theme={null}
// Force immediate flush (critical events)
Zixflow.instance.track('Purchased', {...});
Zixflow.instance.flush();  // Send immediately, don't batch

// Check network status
final networkAvailable = await Connectivity().checkConnectivity();
if (networkAvailable == ConnectivityResult.none) {
  // Show "Offline mode" indicator
  _showOfflineBanner();
}
```

***

### Offline Analytics

**Track offline sessions separately:**

```dart theme={null}
class ConnectivityService {
  bool _isOnline = true;
  DateTime? _offlineSince;
  
  void _onConnectivityChange(ConnectivityResult result) {
    final wasOffline = !_isOnline;
    _isOnline = result != ConnectivityResult.none;
    
    if (_isOnline && wasOffline) {
      // Back online
      final offlineDuration = DateTime.now().difference(_offlineSince!);
      
      Zixflow.instance.track('Back Online', {
        'offline_duration_seconds': offlineDuration.inSeconds,
        'events_queued': Zixflow.instance.getQueueSize(),
      });
      
      // Flush queued events
      Zixflow.instance.flush();
      
    } else if (!_isOnline) {
      // Went offline
      _offlineSince = DateTime.now();
      Zixflow.instance.track('Went Offline', {
        'network_type': result.toString(),
        'screen': _currentScreen,
      });
    }
  }
}
```

**Analytics:** Offline usage patterns, feature availability gaps

***

## Performance Monitoring

### Screen Load Time

```dart theme={null}
class ProductDetailScreen extends StatefulWidget {
  @override
  _ProductDetailScreenState createState() => _ProductDetailScreenState();
}

class _ProductDetailScreenState extends State<ProductDetailScreen> {
  DateTime? _loadStartTime;
  
  @override
  void initState() {
    super.initState();
    _loadStartTime = DateTime.now();
    _loadProduct();
  }
  
  Future<void> _loadProduct() async {
    final product = await ProductService.getProduct(widget.productId);
    
    final loadTime = DateTime.now().difference(_loadStartTime!);
    
    // Track performance
    Zixflow.instance.track('Screen Loaded', {
      'screen_name': 'Product Detail',
      'product_id': widget.productId,
      'load_time_ms': loadTime.inMilliseconds,
      'data_source': product.cachedLocally ? 'cache' : 'api',
      'image_count': product.images.length,
    });
    
    setState(() => _product = product);
  }
}
```

***

### API Call Performance

```dart theme={null}
class ApiClient {
  Future<Response> get(String endpoint) async {
    final startTime = DateTime.now();
    
    try {
      final response = await http.get(Uri.parse(endpoint));
      final duration = DateTime.now().difference(startTime);
      
      Zixflow.instance.track('API Call Completed', {
        'endpoint': endpoint,
        'status_code': response.statusCode,
        'duration_ms': duration.inMilliseconds,
        'response_size_bytes': response.body.length,
        'success': response.statusCode \< 400,
      });
      
      return response;
      
    } catch (e) {
      final duration = DateTime.now().difference(startTime);
      
      Zixflow.instance.track('API Call Failed', {
        'endpoint': endpoint,
        'duration_ms': duration.inMilliseconds,
        'error_type': e.runtimeType.toString(),
        'error_message': e.toString(),
      });
      
      rethrow;
    }
  }
}
```

**Analytics:** P50/P95/P99 latency by endpoint, error rates

***

## Implementation Checklist

### Phase 1: Core Tracking (Week 1)

* [ ] SDK integration (initialization, config)
* [ ] App Installed, Opened, Backgrounded
* [ ] Screen tracking (auto or manual)
* [ ] User identification on signup/login
* [ ] Device token registration

### Phase 2: Feature Instrumentation (Week 2)

* [ ] Feature Used events (top 5 features)
* [ ] Search Performed
* [ ] Content Shared
* [ ] Purchase flow events (Product Viewed, Added to Cart, Purchased)
* [ ] Profile attribute updates (plan type, feature usage)

### Phase 3: Push Notifications (Week 3)

* [ ] Permission request flow (soft ask + system prompt)
* [ ] Push Received, Opened tracking
* [ ] Deep link handling
* [ ] Campaign: Onboarding push sequence
* [ ] Campaign: Cart abandonment push

### Phase 4: Optimization (Week 4)

* [ ] Performance monitoring (screen load, API calls)
* [ ] Offline behavior handling
* [ ] Event batching tuning
* [ ] Retention analysis (DAU/MAU, cohorts)
* [ ] Push notification A/B testing

***

**Next:** SaaS product tracking patterns → [SaaS Product Tracking](/documentation/events/scenarios/saas)
