Browse Developers
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 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
| 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
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.
Consent
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
/. AddKontala.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.