# Flutter

Add ActivationPal to a Flutter app. The Dart SDK uses the same ingest API, event vocabulary, funnels, and dashboard as the native Swift SDK.

## 1. Add the SDK

Add the package:

```bash
flutter pub add activationpal
```

The package supports Flutter apps on iOS and Android. It buffers up to 500 events on-device, retries failed delivery, never blocks the UI, and includes the native iOS bridge needed for Apple Ads attribution.

## 2. Configure before `runApp`

Get the app slug and API key from `https://activationpal.com/dashboard/apps/new`, then:

```dart
import 'package:flutter/widgets.dart';
import 'package:activationpal/activationpal.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await ActivationPal.configure(
    app: 'myapp',
    key: 'ap_pk_YOUR_KEY',
  );
  runApp(const MyApp());
}
```

Await configuration so an event fired by the first widget cannot race SDK setup. Configuration sends `first_open` once per install and starts an `app_open` session automatically.

## 3. Join RevenueCat revenue

Configure RevenueCat first, then pass its current app user id and storefront country. This joins webhook purchases to the device timeline and fills revenue-by-country views.

```dart
import 'dart:io';
import 'package:purchases_flutter/purchases_flutter.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final revenueCatKey = Platform.isIOS ? 'appl_YOUR_KEY' : 'goog_YOUR_KEY';
  await Purchases.configure(PurchasesConfiguration(revenueCatKey));
  final userId = await Purchases.appUserID;
  final storefront = await Purchases.storefront;

  await ActivationPal.configure(
    app: 'myapp',
    key: 'ap_pk_YOUR_KEY',
    userId: userId,
    storefront: storefront?.countryCode,
  );
  runApp(const MyApp());
}
```

If the RevenueCat user changes later, call `ActivationPal.setUserId(await Purchases.appUserID)` after `logIn` or `logOut`.

## 4. Instrument events

```dart
// Onboarding: one call per screen, in order, index from 0.
ActivationPal.onboardingStep(0, id: 'goal', answer: 'lose_weight');
ActivationPal.onboardingStep(1, id: 'notifications');
ActivationPal.onboardingCompleted();

// Paywall.
ActivationPal.paywallShown('onboarding');
ActivationPal.paywallPlanSelected('yearly');
ActivationPal.paywallPurchased('yearly');
ActivationPal.paywallDismissed();

// Custom events: String, bool, int, and finite double props are accepted.
ActivationPal.track('workout_logged', {
  'type': 'run',
  'duration_min': 42,
});
```

The complete fixed vocabulary and naming rules are in [events.md](events.md).

## 5. Apple Search Ads attribution on iOS

Nothing extra is required. On the first open, the package's bundled iOS bridge requests Apple's AdServices attribution token and ActivationPal resolves it server-side. The Acquisition card can then separate Apple Ads installs from organic installs and attribute them to campaigns.

No tracking permission prompt is shown. Apple may return no attribution for an organic install or when attribution data is unavailable; the rest of ActivationPal continues normally.

## 6. Release context and verification

- Debug, TestFlight, and store builds are tagged `env=debug`, `testflight`, and `release` automatically. `environment:` can override this for a custom distribution channel.
- `installed_at` comes from the platform package metadata. Pass a known date with `installedAt:` only if the app maintains a more authoritative install timestamp.
- Events flush every 10 seconds, at 20 buffered events, and when the app backgrounds. Debug builds print successful flushes in the console.
- Run the app, trigger one event, wait about 10 seconds, then check the dashboard live feed.
