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

# Flutter

> Install the Apsio SDK in a Flutter app, a thin Dart layer over the iOS and Android SDKs.

API preview: names may change before 1.0.

`apsio_flutter` is a thin Dart layer over the [iOS](https://docs.apsio.io/sdks/ios.md) and [Android](https://docs.apsio.io/sdks/android.md)
SDKs, which do the work: native crashes, hangs and ANRs, app start, sessions, the store on the
device and delivery. The package adds what only Dart sees: Dart errors with their Dart stacks,
screen names from Flutter's navigation, and the API to Dart. This first version records no
session replay: see [What this version does not do](#what-this-version-does-not-do).

## Requirements

* Flutter 3.44 or later (Dart 3.10 or later). Flutter uses Swift Package Manager by default from
  3.44, and the Apple SDK is a Swift package only: there is no podspec.
* iOS 15 or later; Android 7.0 (API level 24) or later.
* No other platform: on web and desktop every call does nothing.

## Install

The package is not on pub.dev. It installs from its GitHub repository as a git dependency, in
`pubspec.yaml`:

```yaml theme={"theme":"css-variables"}
dependencies:
  apsio_flutter:
    git:
      url: https://github.com/Apsio/apsio-flutter-sdk.git
      ref: v0.1.0
```

That works once the native SDK versions the package pins are released: the Apple SDK `0.1.0`, and
the Android SDK `0.1.0` in [Apsio's Maven repository](https://docs.apsio.io/sdks/android.md#install). Neither is released
yet, so the package cannot be installed yet. Until then, try it with the
example app in [apsio-flutter-sdk](https://github.com/Apsio/apsio-flutter-sdk), which builds the
native SDKs itself.

* **iOS.** The plugin is a Swift package that pulls the Apple SDK from GitHub. The Apple SDK links
  [KSCrash](https://github.com/kstenerud/KSCrash) through Swift Package Manager: an app that also
  pulls KSCrash through CocoaPods (some crash reporters vendor it) gets duplicate symbols.
* **Android.** The plugin adds Apsio's Maven repository to the app's projects and depends on
  `io.apsio:apsio-android`. An app with `minSdk` under 26 turns on core library desugaring, as
  for the [Android SDK](https://docs.apsio.io/sdks/android.md#install).

## Start

Start Apsio first thing in `main`, and add the navigator observer:

```dart theme={"theme":"css-variables"}
import 'package:apsio_flutter/apsio_flutter.dart';
import 'package:flutter/material.dart';

Future<void> main() async {
  await Apsio.start(const ApsioKey.perPlatform(ios: 'a-…', android: 'a-…'));
  runApp(
    MaterialApp(
      navigatorObservers: [ApsioNavigatorObserver()],
      home: const HomePage(),
    ),
  );
}
```

An iOS app and an Android app are two apps in Apsio, each with its own key:
`ApsioKey.perPlatform` takes both, `ApsioKey('a-…')` one key for whichever platform the build runs
on. The app key is not a secret: it ships inside your app and only routes data to your project.

A start from Dart comes after the launch, so it records no app start and cannot catch a crash
that happens before Dart runs. To have both, start the native SDK first, natively, as on the
[iOS](https://docs.apsio.io/sdks/ios.md#start) and [Android](https://docs.apsio.io/sdks/android.md#start) pages, with `screens` off (screens come
from the observer). `Apsio.start` then adds the Dart handlers to it, and the native start's
options are the ones in use: give the options to the native start only. A debug build warns that
`Apsio.start`'s options are not applied when the native SDK was already started.

Set `FlutterError.onError` and `PlatformDispatcher.instance.onError` before `Apsio.start`: a
handler set after it replaces Apsio's.

## Options

`Apsio.start(key, options: ApsioOptions(...))`. Every automatic signal is on unless noted.

| Option | Default | What it does |
| - | - | - |
| `installationId` | `true` | A random id per install, so Apsio can count users. See [Privacy and consent](https://docs.apsio.io/concepts/privacy.md). |
| `firstPartyHosts` | `[]` | Hosts that receive a W3C `traceparent` header, for the requests the native SDKs see. |
| `crashes` | `true` | Native crash capture. |
| `flutterErrors` | `true` | Errors the framework catches (`FlutterError.onError`). |
| `uncaughtErrors` | `true` | Uncaught Dart errors (`PlatformDispatcher.onError`). |
| `network` | `true` | iOS: spans for `URLSession` requests. Never Dart's `HttpClient`. |
| `nativeScreens` | `false` | The native SDKs' automatic screens; screens come from the observer instead. |
| `metricKit` | `true` | MetricKit reports and hang diagnostics (iOS). |
| `anrs` | `true` | ANR detection (Android). |
| `appStart` | `true` | The `app.start` span (Android). |
| `redact` | `[]` | Attribute keys whose values stay on the device and are sent as `[REDACTED]`, from Dart and native records alike. |
| `battery` | off on iOS, on on Android | The battery level, charging state and drain. |
| `endpoint` | Apsio's ingest | Sends to another ingest URL, for a proxy. |
| `configEndpoint` | `endpoint` | Fetches the [remote configuration](https://docs.apsio.io/remote-configuration.md) from another URL. |

There is no replay option. After a native start, these options belong to the native start.

## What is recorded

Everything the native SDKs record (see [iOS](https://docs.apsio.io/sdks/ios.md#what-is-recorded) and
[Android](https://docs.apsio.io/sdks/android.md#what-is-recorded)) except replay, and from Dart:

* **Errors the framework catches** (`FlutterError.onError`), such as an exception in a build
  method, and **uncaught Dart errors** (`PlatformDispatcher.onError`), as handled errors with
  their Dart stacks. A Dart error does not end the process, so it is not a crash; native crashes
  are crashes. The app's own handlers still run.
* **Screens** from `ApsioNavigatorObserver`, or from `Apsio.trackScreen`.
* **Network requests** on iOS that go through `URLSession`, such as with
  `package:cupertino_http`, as client spans recorded by the Apple SDK. Requests made with
  `dart:io`'s `HttpClient`, and so with `package:http` on its default client, are not recorded.

On iOS the plugin ships its own privacy manifest beside the Apple SDK's; your App Store privacy
label is the same as for a native app
([App Store privacy labels](https://docs.apsio.io/concepts/privacy.md#app-store-privacy-labels)).

## Users, logs, errors and spans

The same API as the native SDKs:

```dart theme={"theme":"css-variables"}
Apsio.setUser(idHash);                           // HMAC of the id with an app secret; never an email
Apsio.clearUser();                               // when the user signs out
Apsio.setAttribute('plan', 'pro');               // on every record; null removes it
Apsio.log(Severity.info, 'Checkout opened', attributes: {'cart.items': 3});
Apsio.addBreadcrumb('Tapped pay');

try {
  await pay();
} catch (error, stack) {
  Apsio.recordError(error, stack: stack, fingerprint: 'checkout-card-declined');
}

final span = Apsio.startSpan('checkout.submit');
final child = Apsio.startSpan('checkout.validate'); // nests under the innermost open span
child.end();
span.end(error: true);                           // an error status
await Apsio.withSpan('checkout.submit', (s) => submit());

Apsio.setFeatureFlag('new-checkout', 'b');
Apsio.trigger('checkout_failed');                // fires the trigger event:checkout_failed
Apsio.setConsent(false);                         // stops collection, drops what is queued
await Apsio.flush();                             // sends what is queued now
```

* **The user hash** follows the native SDKs' rules: see
  [The user hash](https://docs.apsio.io/concepts/privacy.md#the-user-hash).
* **`recordError`** takes the stack from the `catch`; without one, an `Error`'s own stack is used,
  else the stack where it is called. `fingerprint` replaces the grouping.
* **Spans.** A span's parent is `parent` when given, otherwise the enclosing `withSpan`, which
  stays the parent across `await`s, otherwise the innermost span still open; `root: true` makes a
  root span. `withSpan` ends its span when the operation settles, with an error status if it
  throws. Spans started before `start` completes are not recorded, and Dart spans never parent
  native work, such as network requests.
* **Calls before and during `start`.** Every call is safe at any time: before `start` it does
  nothing, calls made while `start` runs are applied once it completes, in order, up to 256, and a
  failure inside the SDK is never thrown into the app. `setConsent` called before `start` applies
  from the start.
* **Attribute values** are strings, booleans, numbers and lists of strings; anything else is
  dropped, never sent as its string form.

## Screens

`ApsioNavigatorObserver` tracks the top route of a `Navigator` as the current screen. A route
pushed is a screen view and a screen load; a route shown again after a pop is a view only. Only
page routes are screens by default, so dialogs and bottom sheets are not.

Name your routes: a route with no `RouteSettings.name` has no screen. A path segment that holds a
value becomes `{id}`, so `Navigator.pushNamed(context, '/orders/8127')` is the screen
`/orders/{id}`, and a query or fragment is left out. To name routes yourself, give `screenName`;
return `null` to skip a route, and never put an id in a name:

```dart theme={"theme":"css-variables"}
ApsioNavigatorObserver(
  screenName: (route) => route is PageRoute ? route.settings.name : null,
)
```

`Apsio.trackScreen(name)` names a screen by hand, and `Apsio.reportFullyDrawn()` records the
current screen's time to full display (see [Screens](https://docs.apsio.io/concepts/performance.md#screens)).

## Dart errors

A Dart error is a log record at severity `ERROR` with `exception.type`, `exception.message` and
`exception.stacktrace`, plus `apsio.dart.runtime` (`aot` in release and profile builds, `jit` in
debug) and `apsio.dart.mechanism` (`manual`, `flutter_error` or `uncaught`).

* **Redaction** works as for a native handled error: `redact` covers its attributes, and listing
  `exception.message` covers the message and the record's body. The type and the stack are never
  redacted: a stack holds function names and file names, with no local paths.
* **Messages.** A value thrown that is neither an `Error`, an `Exception` nor a string is
  described by its type, never serialized. A `FormatException` leaves out its source, a
  `FlutterError` sends its one-line summary, and a URL in a message loses its query and fragment.
* **Isolates.** Errors in an isolate you spawn are not caught: send them to `Apsio.recordError`.

## Symbols for Dart stacks

How a Dart stack reads depends on how the app was built:

* **Without `--split-debug-info`**, the stack names its functions, files and lines
  (`#0 Checkout.pay (package:shop/checkout.dart:12:3)`). Apsio reads it as it is, with nothing
  to upload.
* **With `--split-debug-info`**, which `--obfuscate` requires, the stack holds the build's
  `build_id` and an address per frame instead. Apsio resolves those addresses with the build's
  debug file, the `app.<platform>-<arch>.symbols` file Flutter writes into the
  `--split-debug-info` directory: on Android an ELF file whose GNU build id is the stack's
  `build_id`, on iOS a Mach-O file whose UUID is the `build_id`. The names in that file stay
  readable under `--obfuscate`, so frames need no obfuscation map.

The [`apsio` CLI](https://docs.apsio.io/cli) cannot upload Flutter's `.symbols` files yet, so a stack from a build
with `--split-debug-info` keeps its addresses for now. It is grouped on its frames without
`exception.type`, which `--obfuscate` renames in every build, and the release's
[missing symbols](https://docs.apsio.io/concepts/symbols.md#missing-symbols) list the build's Dart code as `libapp.so`
(Android) or `App.framework` (iOS), with its debug id.

In your app's code are the frames outside the Dart SDK, Flutter itself, the packages in the pub
cache and `apsio_flutter`.

Native crashes in a Flutter app are native: upload [dSYMs](https://docs.apsio.io/symbols/upload.md) and
[Android mappings](https://docs.apsio.io/symbols/android.md) as for a native app.

## Privacy and consent

The native SDKs keep the data and apply consent, so everything in
[Privacy and consent](https://docs.apsio.io/concepts/privacy.md) holds for a Flutter app. `Apsio.setConsent(false)` stops
collection and drops what is queued, and the choice is kept across launches; called before
`start`, it applies from the start. If your app must ask before anything is collected, call
`start` only after the user agrees.

Keys listed in `redact` never leave the device, from Dart records and native ones alike, since the
native SDK redacts every record. Never put an email, a name or an id in a screen name, a span name
or a log message.

## What this version does not do

* **No session replay**, and no option for it. Flutter draws its own pixels, so the native view
  tree shows one flat surface, and a wireframe replay needs a walk of the widget tree that does
  not exist yet. Masking fails closed, so there is no replay rather than a partial one. If you
  start the native SDK yourself, leave its replay option off too.
* **No live inspection**, for the same reason: its wireframes need that walk.
* **No spans for Dart's HTTP requests.** `dart:io`'s `HttpClient` has its own sockets, so the
  native SDKs do not see it. `package:cupertino_http` goes through `URLSession`, which the Apple
  SDK does see.
* **Slow and frozen frames are not Flutter's.** The native SDKs count what their platform reports
  for the window; Flutter's frame timings do not reach them yet.
* **Dart stacks from a build with `--split-debug-info` are not symbolicated yet**: their debug
  files cannot be uploaded (see [Symbols for Dart stacks](#symbols-for-dart-stacks)). On iOS, a
  release stack's `build_id` has not yet been compared with the Mach-O UUID of its debug file on
  a real device, so that match is unproved.
* **No log bridge** for `print` or `debugPrint`: use `Apsio.log`.
* **Errors in isolates you spawn** are not caught: send them to `Apsio.recordError`.

## Next

* [Upload your dSYMs](https://docs.apsio.io/symbols/upload.md) and [Android mappings](https://docs.apsio.io/symbols/android.md), for native
  frames.
* [Remote configuration](https://docs.apsio.io/remote-configuration.md): sampling and capture without a release.
* The [API reference](https://github.com/Apsio/apsio-flutter-sdk/blob/main/docs/reference.md) of
  the package.


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