# Flutter SDK

`kontala_flutter` captures what people do in your Flutter app on iOS and Android, the way the website tag captures a site.
It records every screen they see, every control they tap, who signs in, what forms and searches sent, how far they scrolled, where they struggled, how fast each screen was, and your app's own events.

## Requirements

- Flutter 3.35 or later.
- iOS 13 or later.
- Android 7.0 (API 24) or later.

On the web, macOS, Windows and Linux the SDK does nothing.
For a website, [install the website tag](/docs/data-capture/install-the-website-tag) instead.

## 1. Find your write key

Open the property in Kontala and find its **Events API** card.
It shows the property's write key (`pk_live_...`) and the Events API URL to send to.

The write key is public: every copy of your app carries it, as every page carries the tag's.
Never put a server key (`ksk_live_...`) in an app.

## 2. Add the package

```bash
flutter pub add kontala_flutter
```

Or add it to `pubspec.yaml` yourself:

```yaml
dependencies:
  kontala_flutter: ^0.4.0
```

The package depends on nothing but Flutter, so it cannot cause a version conflict with your other packages.

## 3. Start it

Call `Kontala.init` in `main()`, before `runApp`, and give every navigator an observer:

```dart
import 'package:kontala_flutter/kontala_flutter.dart';

void main() {
  Kontala.init(
    writeKey: 'pk_live_...',
    endpoint: Uri.parse('https://konta.la/v1/events'),
  );
  runApp(MaterialApp(
    navigatorObservers: [Kontala.navigatorObserver()],
    home: const HomePage(),
  ));
}
```

Use the write key and endpoint from your property's Events API card.
Nothing else is configured per screen.

### Apps using go_router

`MaterialApp.router` ignores `navigatorObservers`, so pass the observer to the router instead:

```dart
final router = GoRouter(
  observers: [Kontala.navigatorObserver()],
  routes: [
    ShellRoute(
      observers: [Kontala.navigatorObserver()],
      builder: (context, state, child) => AppShell(child: child),
      routes: [/* ... */],
    ),
  ],
);
```

Give each `ShellRoute` and `StatefulShellBranch` with its own navigator a new observer.
An observer serves one navigator, so call `Kontala.navigatorObserver()` once per navigator.

## 4. Platform setup

### iOS

No setup is needed.
The SDK ships an iOS privacy manifest, which Xcode adds to your app's privacy report.

If you want the IDFA, your app shows the App Tracking Transparency prompt, never the SDK.
Add `NSUserTrackingUsageDescription` to `Info.plist`, then call this when your app chooses:

```dart
await Kontala.requestTrackingAuthorization();
```

### Android

No setup is needed.
The SDK adds the `INTERNET` permission and the `AD_ID` permission, so declare advertising ID use in Play Console.

If your app must not use the advertising ID, for example in Google Play's Families programme, remove the permission in your own manifest:

```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
  <uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove" />
</manifest>
```

## 5. Check that it works

Start the app with debug logging on:

```dart
Kontala.init(
  writeKey: 'pk_live_...',
  endpoint: Uri.parse('https://konta.la/v1/events'),
  options: KontalaOptions(debug: true),
);
```

The log shows every event as it is captured, and every event the API refused, with its reason.

A debug or profile build, and any build in a simulator or emulator, marks its events as a developer's.
Kontala leaves them out of visitor counts by default, as it does for a development server.

## Optional setup

### Name screens and mark controls

| Widget | Does |
| --- | --- |
| `KontalaScreen(name:, pageType:, properties:)` | Declares the screen a route shows: its name, page type and data |
| `KontalaEvent(type:)` | Declares the click type of a control, or of every control under it |
| `KontalaMask` | A tap under it is sent as its role alone: no id, label, text or class |
| `KontalaIgnore` | Nothing under it is captured |

Text under Flutter's `SensitiveContent` is never read, and a text field never sends what is typed into it.

### Identify signed-in users

```dart
Kontala.identify(user: 'customer-42', org: 'acme', traits: {'plan': 'pro'});
```

Every later event carries the user and organisation.
Call `Kontala.identify()` with neither to log out, or `Kontala.reset()` to also forget the install and session.
When your app restores a signed-in session at launch, call `identify` in `main()` so the first screen view carries it.

### Track your own events

```dart
Kontala.track('wishlisted', {'product_id': '42'});
```

Custom events arrive while the property's data layer captures the Events API's events.

### Consent

The SDK starts with consent `granted`.
To wait for your consent banner instead, start undecided and answer when the visitor chooses:

```dart
Kontala.init(
  writeKey: 'pk_live_...',
  endpoint: Uri.parse('https://konta.la/v1/events'),
  options: KontalaOptions(consent: KontalaConsent.unknown),
);

// Later, from your banner:
Kontala.consent(KontalaConsent.granted);
```

`Kontala.optOut(true)` stops all capture, sending and storage on the device, across launches.
See [Consent](/docs/data-capture/consent) for how Kontala treats each state.

### Web pages in the app

If your app shows pages of your website in a WebView, the SDK shares its visitor and visit with them, so one person is counted once.
For the closest link, also add the package for your WebView:

```bash
flutter pub add kontala_flutter_webview
```

```dart
final controller = WebViewController()..setJavaScriptMode(JavaScriptMode.unrestricted);
final attachment = await KontalaWebViewFlutter.attach(controller);
await controller.loadRequest(Uri.parse('https://shop.example.com/size-guide'));
```

For `flutter_inappwebview`, use `kontala_flutter_inappwebview` instead:

```dart
InAppWebView(
  initialUserScripts: UnmodifiableListView([KontalaInAppWebView.userScript]),
  initialUrlRequest: URLRequest(url: WebUri('https://shop.example.com/size-guide')),
  onWebViewCreated: (controller) => attachment = KontalaInAppWebView.attach(controller),
)
```

Attach a WebView before its first page loads, and call `detach()` on the attachment when the WebView goes.
The attached page takes the app's consent and signed-in user.

## Widget tests

In a widget test, leave `Kontala.init` out, or call `Kontala.stop()` before the test ends.
Otherwise flutter_test fails the test for leaving a timer pending.

## Troubleshooting

- **Every screen is `/`.** Add `Kontala.navigatorObserver()` to your app's navigators.
- **No taps arrive.** Turn clicks on for the property in Kontala.
- **No custom events arrive.** The property's data layer is off, or its capture rules do not include the Events API.
- **Every row reads as a developer's.** It is a debug or profile build, or a simulator or emulator.
- **Nothing arrives at all.** The write key is wrong or revoked, or the app cannot reach the endpoint. Events wait in the app's queue until it can.

## App store privacy forms

What the SDK sends is yours to declare in the App Store privacy label and the Google Play data safety form:

| App Store privacy label | Google Play data safety form | What the SDK sends |
| --- | --- | --- |
| Identifiers: Device ID | Device or other IDs | A random install id; on consented events, the IDFV and (if authorised) IDFA, or Android ID, App Set ID and advertising ID |
| Identifiers: User ID | Personal info: User IDs | The ids `Kontala.identify` names |
| Contact info: Email address | Personal info: Email address | The email a login names |
| Usage data: Product interaction | App activity: App interactions | Screens, taps, swipes, scroll depth, time on screen and your events |
| Search history | App activity: In-app search history | What a search looked for |
| User content: Other user content | App activity: Other user-generated content | What your app's forms sent |
| Diagnostics: Performance data | App info and performance: Other app performance data | How fast each screen was |
| Diagnostics: Other diagnostic data | App info and performance: Diagnostics | Uncaught errors, and rage and dead taps |
| Location: Coarse location | Location: Approximate location | Country, region and city, derived from each request's address |

Each is used for analytics and linked to the user where your app identifies them.
None is used for tracking: the IDFA goes to Kontala alone and is never joined with another company's data.
