Flutter SDK

Updated at:

Integration guide for the Quick Tracking APM SDK Flutter plugin.

Stable app performance is critical to a good user experience. The lightweight Quick Tracking APM SDK Flutter plugin provides real-time, reliable, and comprehensive monitoring of page performance, page frame rate, Dart exceptions, and custom exceptions. It helps developers efficiently reproduce issues and understand the user paths and business context behind exceptions and jank.

1. Prerequisites

  • Android: Requires APM SDK v2.0.0 or later. Add the component library dependency to the dependencies section of your app module's build.gradle file:

    • api 'com.lydaas.qtsdk:apm-efs:2.0.4'

  • iOS: Requires APM SDK v2.0.0 or later. Add the following to the Podfile in your project's root directory:

    • pod 'UMEFS_P', '2.2.0'

  • Flutter Common Version: Add the following dependency to your project's pubspec.yaml file:

    • qt_common_sdk: ^2.1.2

  • Flutter APM version: Add the following dependency to your project's pubspec.yaml file:

    • qt_apm_sdk: ^2.6.1

Version dependencies

qt_common_sdk: ^2.1.2:

Depends on native iOS version 1.7.1.PX.

- Depends on native Android version 1.8.0.PX.

qt_apm_sdk: ^2.6.0:

|---Depends on native iOS

|-------UMAPM_P: 2.2.0

| -------UMEFS_P: 2.2.0

|---Dependency on native Android

|-------apm-crash:2.0.0

| -------apm-efs:2.0.4

Appkey

During SDK initialization, you must provide the Appkey parameter. The Appkey is a unique identifier for your application in Quick Tracking, generated when you create the app. To obtain or view it, see Application Management.

Data collection domain

Obtain it from the Data Collection module in the management console.

2. Integrate the Flutter APM plugin

The APM SDK depends on the statistical analysis SDK. You can integrate either the Flutter version or the native versions of the statistical analysis SDK. The Flutter APM and Flutter Common SDKs already include the Android and iOS SDKs. If your project is a pure Flutter app, you only need to integrate the Flutter SDKs.

  • For instructions on how to integrate the Flutter statistical analysis SDK, see qt_common_sdk.

  • For instructions on how to integrate the iOS statistical analysis SDK, see iOS SDK.

  • For instructions on how to integrate the Android statistical analysis SDK, see Android SDK.

Note:

Since the Flutter SDK includes native APM and Common SDK dependencies, do not add them again to your project. If your native project's CocoaPods or manual dependencies already include other Quick Tracking SDKs (such as native QTCommon or native APM), remove them to avoid conflicts.

Integration steps

Add the following dependencies to your project's pubspec.yaml file:

# Production dependencies
dependencies:
  qt_common_sdk: ^2.1.2 // Statistical analysis
  qt_apm_sdk: ^2.6.1 // Performance monitoring
Note

Note: If you need compatibility with Flutter v2.8.1, you can integrate qt_apm_sdk: ^2.3.0-flutter-2.8.1.

Import the package:

import 'package:qt_apm_sdk/qt_apm_sdk.dart';

Obfuscation configuration

By default, flutter build apk enables R8. If your application uses code obfuscation, add the following rules to prevent the Quick Tracking SDK from being incorrectly obfuscated, which can cause it to malfunction.

# Keep Quick Tracking SDK classes (replace the package name)
-keep class com.quick.qt.** { *; }
-keep class com.umeng.** { *; }
-keep class com.uc.** { *; }
-keep class com.efs.** { *; }
-dontwarn com.quick.qt.analytics.middle.DevLog

# Keep all Okio-related classes (OkHttp dependency)
-dontwarn okio.**
-dontwarn javax.annotation.**
-dontwarn okhttp3.**

-keepclassmembers class *{
     public<init>(org.json.JSONObject);
}
-keepclassmembers enum *{
      publicstatic**[] values();
      publicstatic** valueOf(java.lang.String);
}

The SDK uses reflection to access resource files (R.java) imported into your project. Obfuscation or optimization tools like Proguard may remove this file. If this occurs, add the following configuration rule and replace [your app's package name] with your app's package name:

-keep public class [your app's package name].R$*{
public static final int *;
} 

2.1 Initialization settings

final QuickTrackingFlutterApmSdk qtApmSdk = QuickTrackingFlutterApmSdk(
    name: '',
    bver: '',
    flutterVersion: 'your Flutter version',
    engineVersion: 'your Flutter engine version',
    enableLog: true,
    enableTrackingPageFps: true,
    enableTrackingPagePerf: true,
    errorFilter: {
      "mode": "ignore",
      "rules": [],
    },
    trackDomain: "your data collection domain",
    initFlutterBinding: MyApmWidgetsFlutterBinding.ensureInitialized,
    // onError: (exception, stack) {},
  );

Parameters:

Parameter

Description

Required

Type

name

Application or module name.

Yes

string

bver

Application or module version and build number.

Yes

string

flutterVersion

Flutter SDK version (default: empty string).

No

string

engineVersion

Flutter engine version.

No

string

enableLog

Enables SDK logging (default: false).

No

boolean

enableTrackingPageFps

Enables page frame rate monitoring (default: false).

No

boolean

enableTrackingPagePerf

Enables page performance monitoring (default: false).

No

boolean

errorFilter

Configures a blacklist and whitelist for captured exceptions.

No

map

trackDomain

The data collection domain.

Yes

string

initFlutterBinding

The override and initialization method for ApmWidgetsFlutterBinding.

No

function

onError

A callback that executes when an exception is thrown.

No

function

2.2 SDK initialization

  • Initializing the SDK enables its monitoring features.

  • Remove the original WidgetsFlutterBinding.ensureInitialized() call to prevent duplicate binding initialization errors.

  • The SDK handles binding initialization internally via the initFlutterBinding parameter.

  • Call any code that depends on ensureInitialized() within this callback.

  • You can use this callback to asynchronously fetch and set the application name and version number.

  • You can instantiate the SDK with empty strings for name and bver, and then assign their values within the init callback.

Complete initialization example

import 'package:qt_apm_sdk/qt_apm_sdk.dart';

void main() {
  final QuickTrackingFlutterApmSdk qtApmSdk = QuickTrackingFlutterApmSdk(
    name: '',
    bver: '',
    flutterVersion: '3.10.0',
    engineVersion: 'd44b5a94c9',
    enableLog: true,
    enableTrackingPageFps: true,
    enableTrackingPagePerf: true,
    errorFilter: {
      "mode": "ignore",
      // "rules": [RegExp('RangeError')],
      "rules": [],
    },
    trackDomain: "your_data_collection_domain",
    initFlutterBinding: MyApmWidgetsFlutterBinding.ensureInitialized,
    // onError: (exception, stack) {},
  );

  qtApmSdk.init(appRunner: (observer) async {
    // Ensure you remove the original `WidgetsFlutterBinding.ensureInitialized()` call
    // to prevent duplicate binding initialization errors.
    // The SDK handles binding initialization internally.
    
    // Code that depends on `ensureInitialized()` can be called here.
    
    // Asynchronously set the app name and version here.
    // For example, if you instantiate the SDK with empty strings for name and bver:
    qtApmSdk.name = 'app_demo';
    qtApmSdk.bver = '1.0.0+9';
    return MyApp(observer);
  });
}

class MyApmWidgetsFlutterBinding extends ApmWidgetsFlutterBinding {
  @override
  void handleAppLifecycleStateChanged(AppLifecycleState state) {
    // Add your custom implementation logic.
    // print('AppLifecycleState changed to $state');
    super.handleAppLifecycleStateChanged(state);
  }

  static WidgetsBinding ensureInitialized() {
    // Bind only after initialization is complete.
    if (WidgetsBinding.instance == null) {
      MyApmWidgetsFlutterBinding();
    }
    return WidgetsBinding.instance;
  }
}

2.3 Register the observer

Register NavigatorObserver in MyApp (StatelessWidget or StatefulWidget) by implementing the MyApp(this._navigatorObserver) constructor and adding ApmNavigatorObserver.singleInstance to navigatorObservers.

class MyApp extends StatelessWidget {
  MyApp([this._navigatorObserver]);
  final NavigatorObserver? _navigatorObserver;
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      routes: routes,
      initialRoute: "/",
      navigatorObservers: <NavigatorObserver>[
        _navigatorObserver ?? ApmNavigatorObserver.singleInstance
      ],
    );
  }
}

3. SDK API

3.1 Page monitoring

Page performance monitoring

Page performance monitoring is disabled by default. To enable it, set enableTrackingPagePerf to true.

Example:

final QuickTrackingFlutterApmSdk qtApmSdk = QuickTrackingFlutterApmSdk(
  ...
  enableTrackingPagePerf: true,
  ...
);

qtApmSdk.init(appRunner: (observer) async {
  qtApmSdk.name = 'qt_demo';
  qtApmSdk.bver = 'app_version+build_number';
  return MyApp(observer);
});

Page frame rate analysis

Page frame rate (FPS) indicates the number of frames rendered per second, a key metric for measuring smoothness and dynamic content performance. To use this feature, you must use an ApmScrollController instance and register it to monitor scrolling events.

import 'package:flutter/material.dart';
import 'package:qt_apm_sdk/qt_apm_sdk.dart';

class ScrollLazyLoadPage extends StatefulWidget {
  @override
  _ScrollLazyLoadPageState createState() => _ScrollLazyLoadPageState();
}

class _ScrollLazyLoadPageState extends State<ScrollLazyLoadPage> {
  List<String> imageUrls = [];
  int page = 1;

  // Use the APM scroll controller (ApmScrollController).
  final ScrollController _scrollController = ApmScrollController();
  bool isLoading = false;

  @override
  void initState() {
    super.initState();
    fetchData();

    _scrollController.addListener(() {
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
        fetchData();
      }
    });
  }

  Future<void> fetchData() async {
    if (!isLoading) {
      setState(() {
        isLoading = true;
      });

      // Simulating a delay of 2 seconds.
      await Future.delayed(const Duration(seconds: 2));

      final List<String> urls = [
        'https://img_01.jpg',
        'https://img_02.jpg',
        'https://img_03.jpg',
        ...
      ];

      try {
        setState(() {
          imageUrls.addAll(urls);
          isLoading = false;
        });
      } catch (e) {}
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Scroll Lazy Load Demo'),
      ),
      body: GridView.builder(
        controller: _scrollController,
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          mainAxisSpacing: 10,
          crossAxisSpacing: 10,
        ),
        itemCount: imageUrls.length + 1,
        itemBuilder: (context, index) {
          if (index == imageUrls.length) {
            return Center(
              child:
                  isLoading ? const CircularProgressIndicator() : const SizedBox.shrink(),
            );
          }
          return Card(
            child: Image.network(
              imageUrls[index],
              fit: BoxFit.cover,
            ),
          );
        },
      ),
    );
  }
}

3.2 Exception monitoring

Dart exceptions

The SDK automatically monitors Dart exceptions, including synchronous and asynchronous exceptions (managed by runZonedGuarded), framework exceptions, and custom-reported exceptions. You can view the reported data on the platform.

Note: You can set a daily limit on the number of Dart exceptions reported per device. The default is 20, and the maximum is 120 per day.

Custom exceptions

captureException (Type: Function)

Parameter

Description

Required

Type

exception

Exception summary.

Yes

Exception

stack

The exception's stack trace.

No

String

extra

Custom attributes.

No

Map<String, dynamic>

Example 1

import 'package:qt_apm_sdk/qt_apm_sdk.dart';

void main() async {

  Isolate isolate = await Isolate.spawn(runIsolate, []);

  // Listen for isolate exceptions.
  isolate.addErrorListener(RawReceivePort((pair) {
    var error = pair[0];
    var stacktrace = pair[1];
 
    // Manually capture the isolate exception.
    ExceptionTrace.captureException(
      exception: Exception(error),
      stack: stacktrace.toString());
   }).sendPort);
}

Example 2

import 'package:qt_apm_sdk/qt_apm_sdk.dart';

void main() {	
  try {     
   List<String> numList = ['1', '2'];     
   print(numList[5]);
  } catch (e) {
   // Manually capture and report a code execution exception.
   ExceptionTrace.captureException(
   exception: Exception(e), extra: {"user": '123'});
  }
}

3.3 ErrorFilter configuration

Use errorFilter to configure a blacklist or whitelist for captured exceptions. A whitelist reports only exceptions matching the rules, while a blacklist ignores those that match.

This is an optional parameter for filtering logs and includes the following properties:

Property

Description

Default

Type

mode

The matching mode.

  • ignore: Blacklist mode. Items that match a rule are not reported.

  • match: Whitelist mode. Only items that match a rule are reported.

ignore

Enum: ignore | match

rules

A collection of matching rules.

  • An array of rules.

  • The rules are combined with a logical OR; a match occurs if any single rule is satisfied.

[]: An empty blacklist reports all logs.

Array<string | RegExp>

Example:

void main() {
 QuickTrackingFlutterApmSdk(
   name: 'your_app_or_module_name',
   // Filter exceptions.
   errorFilter: {
     "mode": "match",
     "rules": [RegExp('RangeError')],
   },
   ...
 );
}

4. Native SDK integration

5. Remote configuration

Remote configuration allows you to set the device PV sampling rate and the maximum number of logs reported per device. Changes to the sampling rate take about 15 minutes to propagate from the server. Once active on the server, the new configuration is fetched by the SDK on the next app cold start and applied on the one after.

PV sampling rate: This setting controls which devices report data. When an app starts, an on-device calculation determines if the device meets the sampling criteria (e.g., a 5% rate). If the device is sampled, the SDK collects logs for exceptions, performance, and frame rates for that session.

Default rules:

  • Flutter monitoring: Enabled by default.

  • PV sampling rate: 5% by default, adjustable up to 100%. This rate also affects Flutter monitoring; if a device is not sampled, Flutter monitoring is also disabled for that session.

  • Page performance limit: The maximum number of page performance logs reported per device per day. Default: 200. Maximum: 1,000.

  • Dart exception limit: The maximum number of Dart exceptions reported per device per day. Default: 20. Maximum: 120.

6. SDK verification

Adjust sampling rate

Before verification, set the Flutter PV sampling rate to 100% to ensure all log samples are captured. You can adjust this setting for your production environment as needed.

image

View log panel

image

If you cannot find any data, go to Switch and sampling configuration and ensure that Flutter Monitoring is Enabled (it is enabled by default).

image