Private preview Grafana Cloud

Note

Observability for Mobile is currently in private preview. Grafana Labs offers support on a best-effort basis, and breaking changes might occur prior to the feature being made generally available.

Instrument Flutter Mobile applications

Note

The Grafana Faro Flutter SDK, part of Observability for Mobile, is in Private Preview. To request access, add your information to the Observability for Mobile preview wait list form.

The Grafana Faro Flutter SDK instruments Flutter applications on iOS and Android and sends real user monitoring telemetry to Grafana Cloud.

Use this guide to create a Mobile Observability application and install the Faro Flutter SDK. The examples use Faro Flutter SDK 0.17.0. The package requires Flutter 3.32.0 or later and Dart 3.8.0 or later.

The SDK doesn’t instrument Flutter web.

Grafana Cloud stores logs, events, exceptions, and measurements in Grafana Cloud Logs, and stores traces in Grafana Cloud Traces. Each signal includes sdk_name set to faro-mobile-flutter.

For configuration beyond this guide, refer to the Faro Flutter SDK on pub.dev.

Before you begin

Confirm the following before you instrument the application:

  • A Grafana Cloud account (sign up for free)
  • A Flutter application for iOS or Android

Create a Mobile Observability application

The application collects and analyzes telemetry from your mobile app. You use its name to tell it apart from your other applications.

To create the application, follow these steps:

  1. Sign in to your Grafana Cloud instance.
  2. Click Observability > Frontend.
  3. Click Create New > Mobile Application.
  4. Enter an Application name.
  5. Click Next.

The Install and initialize SDK step opens.

Install and initialize the SDK

Select Flutter. The snippet on this step includes the apiKey and collectorUrl for the application you just created. Copy those two values into the sample below.

apiKey is the last segment of collectorUrl. If the URL ends in /collect/123456789, the API key is 123456789.

Add the dependency

The wizard shows faro: <latest>. In pubspec.yaml, set that line to the current version:

YAML
dependencies:
  faro: ^0.17.0

Install the package:

Bash
flutter pub get

Initialize Faro

Keep your existing root widget. In the sample, that widget is MyApp. Replace your current main() with this one, and keep the same runApp call you already use.

appName is the name you entered in the wizard. appVersion is your mobile app version. appEnv is development, staging, or production.

dart
import 'package:faro/faro.dart';

void main() {
  Faro().runApp(
    optionsConfiguration: FaroConfig(
      appName: 'my-flutter-app',
      appVersion: '1.0.0',
      appEnv: 'production',
      apiKey: '<APP_KEY>',
      collectorUrl: 'https://<FARO_COLLECTOR_HOST>/collect/<APP_KEY>',
    ),
    appRunner: () {
      runApp(const MyApp());
    },
  );
}

Leave MyApp and the rest of your widget tree as they are.

This main() collects Flutter errors, sessions, CPU and memory measurements, and cold and warm start times. It sends every session. It doesn’t attach a user until you call setUser.

Add optional instrumentation

On Additional configurations, add any of the following to the main() above. Skip a block to leave that instrumentation off.

Each snippet is an edit to that main(), not a new file. A complete main() with every option is at the end of this section.

To track HTTP calls from Dart, add this import with the other imports:

dart
import 'dart:io';

Add this line as the first line inside main(), before Faro().runApp():

dart
HttpOverrides.global = FaroHttpOverrides(HttpOverrides.current);

If the app already sets HttpOverrides.global, keep that override and pass it into FaroHttpOverrides.

HTTP tracking covers clients in the Dart layer, including http and dio. It doesn’t cover HTTP calls made directly from Android or iOS code. The SDK redacts credentials in telemetry URLs and the values of common sensitive query parameters, including token, access_token, api_key, and password. The request the app sends still uses the original URI.

To track screen changes, add navigatorObservers to your existing MaterialApp. Leave home set to the widget you already use:

dart
return MaterialApp(
  navigatorObservers: [FaroNavigationObserver()],
  home: myExistingHome,
);

Each screen change sends a view_changed event. For GoRouter, set observers instead of navigatorObservers:

dart
GoRouter(
  observers: [FaroNavigationObserver()],
);

To track taps and gestures, wrap your existing widget inside appRunner. If that call is runApp(const MyApp()), replace it with:

dart
runApp(
  const FaroUserInteractionWidget(child: MyApp()),
);

To also track asset loading, use this runApp call instead of the previous one:

dart
runApp(
  FaroAssetTracking(
    child: const FaroUserInteractionWidget(child: MyApp()),
  ),
);

To keep telemetry while the device is offline, add this immediately before Faro().runApp():

dart
Faro().transports.add(
  OfflineTransport(
    maxCacheDuration: const Duration(days: 3),
  ),
);

Cached events older than maxCacheDuration are discarded when the device reconnects.

main() with every option

Use this main() when you want HTTP tracking, interactions, asset loading, and offline caching. It replaces the main() above and still runs your existing MyApp. Add navigatorObservers to your existing MaterialApp, as shown in the screen-change step.

dart
import 'dart:io';

import 'package:faro/faro.dart';

void main() {
  HttpOverrides.global = FaroHttpOverrides(HttpOverrides.current);

  Faro().transports.add(
    OfflineTransport(
      maxCacheDuration: const Duration(days: 3),
    ),
  );

  Faro().runApp(
    optionsConfiguration: FaroConfig(
      appName: 'my-flutter-app',
      appVersion: '1.0.0',
      appEnv: 'production',
      apiKey: '<APP_KEY>',
      collectorUrl: 'https://<FARO_COLLECTOR_HOST>/collect/<APP_KEY>',
    ),
    appRunner: () {
      runApp(
        FaroAssetTracking(
          child: const FaroUserInteractionWidget(child: MyApp()),
        ),
      );
    },
  );
}

Send custom data

Custom data is the last part of Install and initialize SDK. Call these methods from your app after Faro().runApp() has started.

Send an event when you need a signal the SDK doesn’t collect on its own. Event attributes are strings.

dart
Faro().pushEvent('checkout-button-tapped', attributes: {
  'startedInBackgroundCount': '1',
  'from': 'notification',
});

Send a log with a severity:

dart
Faro().pushLog('My warn message', level: LogLevel.warn);

Send a measurement when the value is a number:

dart
Faro().pushMeasurement({'cpu_usage': 350}, 'app_cpu_usage');

Report an error you have already caught. stackTrace comes from the catch clause:

dart
} catch (error, stackTrace) {
  Faro().pushError(
    type: 'my_custom_error',
    value: error.toString(),
    stacktrace: stackTrace,
  );
}

Associate telemetry with a user so you can filter sessions by the person using the app. Call setUser after authentication, and call setUser(FaroUser.cleared()) on logout.

dart
Faro().setUser(FaroUser(
  id: 'user-123',
  username: 'a.user',
  email: 'a.user@example.com',
));

// On logout:
Faro().setUser(FaroUser.cleared());

persistUser defaults to true, so the SDK restores the last user on the next launch.

Click Continue.

Enable alerting

The Enable alerting step opens. Alerting is optional.

Select the rules you want, then click Complete. You can change the thresholds later on the application settings page.

AlertWhen it fires
Errors CountThe number of errors increases day over day for the last 24 hours.
New ErrorsNew errors appear in the last 24 hours.
Slow App StartCold start time is greater than 5000 ms for the last 6 hours.
High Frozen FramesThe UI freezes more than 10 times in the last 6 hours.
Low Frame RateMore than 10 slow frames occur in the last 6 hours. A slow frame is below 60 fps.
High Network LatencyAPI response time exceeds 2000 ms for the last 6 hours.

Next steps