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

# Flutter SDK

> Add Appeeky Analytics to a Flutter app for iOS and Android

The Flutter SDK records installs, sessions, screens and your own events, and links each install to the RevenueCat or Superwall customer. It sends the same data as the [React Native SDK](https://docs.appeeky.com/docs/analytics-sdk-reference.md), so the dashboard, attribution and revenue work the same way.

* No IDFA and no App Tracking Transparency prompt. The install id is a random id stored in the app.
* Events are queued on the device and sent in batches. They survive offline periods and app restarts.
* The client runs in Dart. The plugin's native code only supplies the Apple Search Ads token, SKAdNetwork conversion value updates, the Play Install Referrer, the URL that opened the app and device fields.
* Flutter 3.38 or later, iOS 13+ and Android 5.0 (API 21)+.

The source is on GitHub: [appeeky/analytics-flutter](https://github.com/appeeky/analytics-flutter).

***

## Setup

**1. Turn on Analytics for the app**

In Appeeky, open the app under **My Apps → Attribution** and click **Turn on attribution**. Copy the publishable key (`apk_pub_…`). See the [Quickstart](https://docs.appeeky.com/docs/analytics-quickstart.md) for the API call.

**2. Add the package**

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
flutter pub add appeeky_analytics
```

It depends on `http` and `shared_preferences`. On iOS it works with both CocoaPods and Swift Package Manager.

**3. Add the postback keys on iOS**

Add these keys to `ios/Runner/Info.plist`, so iOS sends copies of winning SKAdNetwork and AdAttributionKit postbacks to Appeeky:

```xml theme={"theme":{"light":"github-light","dark":"github-dark"}}
<key>NSAdvertisingAttributionReportEndpoint</key>
<string>https://appeeky.com</string>
<key>AdAttributionKit</key>
<dict>
  <key>AttributionCopyEndpoint</key>
  <string>https://appeeky.com</string>
</dict>
```

An app can name only one endpoint. Leave the keys out if another provider already receives the copies, and use your own registrable domain when you [self-host](https://docs.appeeky.com/docs/analytics-self-hosting.md).

Nothing has to be added to `AppDelegate`, `SceneDelegate` or the Android manifest. The plugin reads the URL that opened the app itself and never consumes it, so Flutter's own deep linking and deep link packages keep working.

**4. Initialize the SDK and link RevenueCat**

```dart theme={"theme":{"light":"github-light","dark":"github-dark"}}
import 'package:appeeky_analytics/appeeky_analytics.dart';
import 'package:flutter/material.dart';
import 'package:purchases_flutter/purchases_flutter.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  Appeeky.init(key: 'apk_pub_...');

  // Link the install to RevenueCat so purchases get a source.
  Appeeky.identify(await Purchases.appUserID);
  runApp(const MyApp());
}
```

Call `identify` again with the new id after `Purchases.logIn()`, and pass `null` on logout. With [Superwall](https://docs.appeeky.com/docs/analytics-superwall.md), pass the same user id you give `Superwall.shared.identify(userId: ...)`.

The SDK sends to `https://ingest.appeeky.com/v1/ingest`. For a self-hosted server, pass `endpoint: 'https://analytics.example.com/v1/ingest'`.

**5. Track screens**

Add the navigator observer to record a screen for each named page route:

```dart theme={"theme":{"light":"github-light","dark":"github-dark"}}
MaterialApp(
  navigatorObservers: [AppeekyNavigatorObserver()],
)
```

Dialogs, bottom sheets and routes without a name are skipped. Pass `nameExtractor` to map `RouteSettings` to your own names, or `routeFilter` to choose which routes count. With go\_router, name your routes and add the observer to `GoRouter(observers: [...])`.

***

## Methods

| Method | What it does |
| - | - |
| `Appeeky.init(key:, endpoint:, debug:, ...)` | Starts the SDK. Later calls return the first client. |
| `Appeeky.identify(userId)` | Links the install to a RevenueCat or Superwall user id. `null` on logout. |
| `Appeeky.screen(name, [properties])` | Records a screen view. |
| `Appeeky.track(name, [properties, value])` | Records a custom event, with optional properties and a number. |
| `Appeeky.getDeferredDeepLink()` | The smart link's destination on the first launch, once. |
| `Appeeky.trialStarted()`, `Appeeky.purchased(valueUsd)` | Raise the SKAdNetwork conversion value on iOS. |
| `Appeeky.adRevenue(valueUsd, network:, adUnit:, format:)` | Reports a paid ad impression in USD. |
| `Appeeky.flush()` | Sends queued events now. |
| `Appeeky.installId` | This install's random id, `null` until the SDK has loaded it. |
| `Appeeky.shutdown()` | Stops timers and the lifecycle observer. |

```dart theme={"theme":{"light":"github-light","dark":"github-dark"}}
Appeeky.screen('Paywall');
Appeeky.track('onboarding_completed', {'step': 3});
Appeeky.track('export', {'format': 'pdf'}, 1);
```

Event names may use letters, digits, `_`, `.`, `:` and `-`, up to 64 characters. Property values are strings (up to 512 characters), numbers, booleans or `null`.

### Ad revenue

```dart theme={"theme":{"light":"github-light","dark":"github-dark"}}
// google_mobile_ads paid event
onPaidEvent: (ad, valueMicros, precision, currencyCode) {
  if (currencyCode == 'USD') {
    Appeeky.adRevenue(valueMicros / 1e6, network: 'admob', adUnit: 'rewarded_home', format: 'rewarded');
  }
},
```

### Options

| Option | Default | Effect |
| - | - | - |
| `key` | required | Publishable key |
| `endpoint` | Appeeky hosted | A self-hosted server's ingest URL, ending in `/v1/ingest` |
| `debug` | `false` | Log sends, rejected batches and network errors |
| `resolveAdServicesOnDevice` | `true` | Exchange the AdServices token with Apple from the device |
| `flushInterval` | 10 s | How often queued events are sent while the app is open |
| `flushAt` | `20` | Send as soon as this many events are queued |
| `maxQueueSize` | `1000` | Oldest events are dropped beyond this |
| `sessionTimeout` | 30 min | Time in the background before a new session starts |
| `context` | | `DeviceContext` fields that override the detected ones |
| `getLaunchUrl` | | Fallback for the URL that opened the app, when the plugin cannot see it |
| `getAdServicesToken`, `getInstallReferrer` | built in | Replace the plugin's own sources |
| `disabled` | `false` | Collect and send nothing, for example after an opt-out |

***

## Automatic Events

| Event | When |
| - | - |
| `first_open` | First launch after install |
| `session_start` | Launch, or returning after `sessionTimeout` in the background |
| `app_background` | Going to the background, with `foreground_ms` |
| `identify` / `reset` | `identify(id)` / `identify(null)` |
| `ad_revenue` | `adRevenue(...)` |

***

## Attribution

| Source | How it is detected |
| - | - |
| Campaign link | The URL the app was first opened with carries `aky_c=<campaign>` or `utm_*` parameters. |
| Smart link | Android: the Play install referrer names the link. iOS: the link opened the app on its first launch. |
| Apple Search Ads | Automatic on iOS 14.3+, down to the campaign, ad group and keyword. |
| Google Play referrer | Automatic on Android, from the `utm_*` parameters. |
| Organic | Everything else. |

Attribution is collected once, on the first launch. Each source has 3 seconds to answer.

A [smart link](https://docs.appeeky.com/docs/analytics-smart-links.md) with an in-app destination can route the user on the first launch. This works on Android, through the install referrer:

```dart theme={"theme":{"light":"github-light","dark":"github-dark"}}
final destination = await Appeeky.getDeferredDeepLink();
if (destination != null) {
  navigatorKey.currentState?.pushNamed(Uri.parse(destination).path);
}
```

iOS has no deferred deep link through the App Store. Matching an App Store install back to a click would need device fingerprinting, which Apple does not allow.

### SKAdNetwork conversion values

On iOS the SDK keeps the conversion value up to date. Values only go up:

| Fine value | Coarse | Set by |
| - | - | - |
| 1 | low | First launch (automatic) |
| 2 | medium | Came back on a later day (automatic) |
| 8 | medium | `Appeeky.trialStarted()` |
| 16–63 | high | `Appeeky.purchased(valueUsd)`: 16 + $2 steps, capped at $94+ |

`purchased` and `trialStarted` only change the conversion value. Revenue comes from RevenueCat or Superwall, so nothing is counted twice.

***

## Privacy

The SDK sends the install id, app version and build, OS version, device model, locale and the events you track. It never reads advertising identifiers. In App Store Connect, declare **Usage Data → Product Interaction** and **Purchases** as not linked to the user's identity and not used for tracking. On Google Play, declare the same in the Data safety form (App activity, Purchase history). If your RevenueCat app user id is a real account id, declare them as linked instead. See [Privacy](https://docs.appeeky.com/docs/analytics-privacy.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.