Skip to content
Docs
Browse Developers

Flutter SDK

Install kontala_flutter from pub.dev and capture screens, taps and events in your Flutter app on iOS and Android.

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 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

flutter pub add kontala_flutter

Or add it to pubspec.yaml yourself:

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:

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:

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:

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:

<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:

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

WidgetDoes
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
KontalaMaskA tap under it is sent as its role alone: no id, label, text or class
KontalaIgnoreNothing 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

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

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

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

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

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 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:

flutter pub add kontala_flutter_webview
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:

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 labelGoogle Play data safety formWhat the SDK sends
Identifiers: Device IDDevice or other IDsA random install id; on consented events, the IDFV and (if authorised) IDFA, or Android ID, App Set ID and advertising ID
Identifiers: User IDPersonal info: User IDsThe ids Kontala.identify names
Contact info: Email addressPersonal info: Email addressThe email a login names
Usage data: Product interactionApp activity: App interactionsScreens, taps, swipes, scroll depth, time on screen and your events
Search historyApp activity: In-app search historyWhat a search looked for
User content: Other user contentApp activity: Other user-generated contentWhat your app's forms sent
Diagnostics: Performance dataApp info and performance: Other app performance dataHow fast each screen was
Diagnostics: Other diagnostic dataApp info and performance: DiagnosticsUncaught errors, and rage and dead taps
Location: Coarse locationLocation: Approximate locationCountry, 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.

Was this helpful?