raygun4flutter

Pub Version CI

The world's best Flutter Crash Reporting solution.

Introduction

Library organisation

The file lib/raygun4flutter.dart provides the main API entry point for Flutter users.

Requirements

  • Dart SDK 3.3.0+

As of release 1.0.0 we've started to improve support for Flutter Desktop and Web. The package seems to be working fine with these targets but we'd appreciate any additional feedback on Github.

Installation

1. Depend on it

Run this command:

$ flutter pub add raygun4flutter

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):

dependencies:
  raygun4flutter: ^x.y.z

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

2. Import it

Now in your Dart code, you can use:

import 'package:raygun4flutter/raygun4flutter.dart';

Check the "Installing" tab on https://pub.dev/packages/raygun4flutter/install for more info.

3. Platform-specific notes

General

If your application comprises hybrid code both using Flutter and native Android or iOS elements, please be aware that the Raygun4Flutter package will only track and report crashes from Flutter and Dart.

If you have a requirement to track crash reports across various layers and parts of your application written in different technologies you might need to implement the respective native providers for Android, iOS or other platforms of choice as well.

Android-specific

In your app's AndroidManifest.xml (usually located in your Flutter project's /android/src/main directory), make sure you have granted Internet permissions. Inside the <manifest> element add:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

if it doesn't exist yet.

Setup and usage

Initialisation and version tracking

Call Raygun.init() with an apiKey argument to initialise RaygunClient on application start, for example, from your initState method.

class _MyAppState extends State<MyApp> {

  @override
  void initState() {
    super.initState();
    Raygun.init(apiKey:'12345');
  }

}

The .init() method can also accept an optional version argument. If this is supplied, the version of your app will be tracked across Raygun crash reports:

Raygun.init(apiKey:'12345', version:'1.4.5');

As an additional convenience way to set the version, a method .setVersion() is available. Typical use cases would most likely fall back to setting the app version in the .init() method call when you setup the library.

Capturing and sending errors

To be able to capture errors inside Flutter, you need to add a custom FlutterError.onError handler to your main method. That redirects Flutter errors to Raygun.

Note: This only works when the app is running in "Release" mode.

  FlutterError.onError = (details) {
    // Default error handling
    FlutterError.presentError(details);

    // Raygun error handling
    Raygun.sendException(
      error: details.exception,
      stackTrace: details.stack,
    );
  };

You can also catch Dart errors outside of the code controlled by the Flutter framework by calling to runApp from a runZonedGuarded and redirecting captured errors to Raygun. For example: errors that happen in asynchronous code.

Note: This works both in "Release" and "Debug" modes.

  runZonedGuarded<Future<void>>(() async {
    runApp(const MyApp());
  }, (Object error, StackTrace stackTrace) {
    Raygun.sendException(
      error: error,
      stackTrace: stackTrace,
    );
  });

Sending errors manually

Call Raygun.sendException(error, tags, customData, stackTrace) to send errors to Raygun.

For example:

try {
  // code that crashes
} catch (error) {
  Raygun.sendException(error: error);
}

All arguments but error are optional. This method is mainly a convenience wrapper around the more customisable .sendCustom() method that obtains the class name and the message from the error object.

Sending custom errors manually

Call Raygun.sendCustom(className, reason, tags, customData, stackTrace) to send custom errors to Raygun with your own customised className and reason. As with .sendException(), tags, customData and stackTrace are optional.

You can also provide an optional innerError Exception object that will be attached to the error report.

For example:

Raygun.sendCustom(
  className: 'MyApp',
  reason: 'test error message',
  tags: ['API','Tag2'],
  customData: {
    'custom1': 'value',
    'custom2': 42,
  },
  stackTrace: StackTrace.current,
  innerError: Exception('Error!'),
);

Crash reports will be sent to the Raygun backend after the crash occurred. Should a user's device be offline, Raygun4Flutter will store up to 64 crash reports locally and try to send them when the device comes back online.

Other functionality

Setting tags

Raygun.setTags() sets a list of global tags that will be logged with every exception. This will be merged with other tags passed into manually created crash reports via sendException() and sendCustom().

Raygun.setTags(['Tag1','Tag2']);

Setting custom data

Raygun.setCustomData() sets a global map of key-value pairs that, similar to tags, that will be logged with every exception. This will be merged with other custom data passed into manually created crash reports via sendException() and sendCustom().

Raygun.setCustomData({
  'custom1': 'value',
  'custom2': 42,
});

Sending breadcrumbs

Breadcrumbs can be sent to Raygun to provide additional information to look into and debug issues stemming from crash reports. Breadcrumbs can be created in two ways.

Simple string:

Call Raygun.recordBreadcrumb(message), where message is just a string:

Raygun.recordBreadcrumb('test breadcrumb');

Using RaygunBreadcrumbMessage:

Create your own RaygunBreadcrumbMessage object and send more than just a message with Raygun.recordBreadcrumb(RaygunBreadcrumbMessage).

The structure of the type RaygunBreadcrumbMessage is as shown here:

 RaygunBreadcrumbMessage({
    required this.message,
    this.category,
    this.level = RaygunBreadcrumbLevel.info,
    this.customData,
    this.className,
    this.methodName,
    this.lineNumber,
  });

Breadcrumbs can be cleared with Raygun.clearBreadcrumbs().

Getting/setting/cancelling the error before it is sent

This provider has an onBeforeSend API to support accessing or mutating the candidate error payload immediately before it is sent, or cancelling the sending action.

This is provided as the public callback Raygun.onBeforeSend, which takes an OnBeforeSendCallback.

Example:

Raygun.onBeforeSend = (payload) {
  // e.g. override the payload error message with a new message
  payload.details.error!.message = 'New Error Message';
  
  // important: return the payload to continue the sending action
  return payload;
};

To cancel the sending action, return null:

Raygun.onBeforeSend = (payload) {
  return null;
};

Set the Raygun.onBeforeSend to null to remove your custom callback:

Raygun.onBeforeSend = null;

Affected Customers

Raygun supports tracking the unique customers who encounter bugs in your apps.

By default, a device-derived UUID is transmitted. You can also add the currently logged in customer's data like this using an object of type RaygunUserInfo:

Raygun.setUser(
  RaygunUserInfo(
    identifier: '1234',
    firstName: 'FIRST',
    fullName: 'LAST',
    email: 'test@example.com',
  ),
);

To clear the currently logged in customer, call setUser(null).

There is an additional convenience method that offers a shortcut to just track your customer by an indentifier only. If you use an email address to identify the user, please consider using setUser instead of setUserId as it would allow you to set the email address into both the identifier and email fields of the crash data to be sent.

Raygun.setUserId('1234');

Call with null to clear the user identifier: setUserId(null)

Custom endpoints

Raygun supports sending data from Crash Reporting to your own endpoints. If you want to set custom endpoints, could can do so by setting them after you've initialised Raygun:

Raygun.setCustomCrashReportingEndpoint(url)

Please note that setting a custom endpoint will stop Crash Report or Real User Monitoring data from being sent to the Raygun backend.

Obfuscation

Flutter supports code obfuscation with the --obfuscate parameter, this option generates symbol files that can be used to decode the obfuscated stack traces, as described in the section Obfuscate Dart code from the Flutter SDK.

Note that Flutter Web uses sourcemaps instead, which is described in the next section.

To decode obfuscated Flutter stacktraces with Raygun, you will have to copy the stacktrace into a file and run the flutter symbolize command manually. In the future, Raygun aims to support this functionality directly in the website.

For example, a file named stack.txt. It will look similar to this:

at *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** (file:///***:null)
at pid: 29207, tid: 29228, name 1.ui (unparsed:null)
at android arch: arm64 comp: yes sim: no (os::null)
at build_id: '63956a45f09b3f8410d244053eebd180' (unparsed:null)
at isolate_dso_base: 708882d000, vm_dso_base: 708882d000 (unparsed:null)
at isolate_instructions: 7088903b40, vm_instructions: 70888ed000 (unparsed:null)
at #00 abs 00000070889bcc93 virt 000000000018fc93 _kDartIsolateSnapshotInstructions+0xb9153 (unparsed:null)
at #01 abs 00000070889464fb virt 00000000001194fb _kDartIsolateSnapshotInstructions+0x429bb (unparsed:null)
... etc ...

Then, run the decode command passing in the copied stacktrace and the symbol file corresponding to the architecture. For example:

flutter symbolize -i stack.txt -d out/android/app.android-arm64.symbols

The decoded output will be similar to this:

at *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** (file:///***:null)
at pid: 29207, tid: 29228, name 1.ui (unparsed:null)
at android arch: arm64 comp: yes sim: no (os::null)
at build_id: '63956a45f09b3f8410d244053eebd180' (unparsed:null)
at isolate_dso_base: 708882d000, vm_dso_base: 708882d000 (unparsed:null)
at isolate_instructions: 7088903b40, vm_instructions: 70888ed000 (unparsed:null)
at #0      _MyAppState.build.<anonymous closure> (/[redacted]/raygun4flutter/example/lib/main.dart:89:17)
at #1      _InkResponseState.handleTap (//[redacted]/flutter/packages/flutter/lib/src/material/ink_well.dart:1170:21)
... etc ...

Flutter web source maps

Flutter web applications use dart2js to produce a single JavaScript file main.dart.js.

However, when reporting errors to Raygun, the reported stack traces correspond to the JavaScript generated code and not the original Dart code.

Source maps help to convert generated JavaScript code back into Dart source code. Raygun uses them to take obfuscated stack trace errors that point to generated JavaScript code and translate them to locations in Dart code.

To generate the Flutter web source maps, compile your project with the --source-maps option.

For example:

flutter build web --source-maps

This will generate the source map file, e.g. main.dart.js.map inside the build/web folder.

Then, upload this file to the Js Source Map Center in your Raygun project's Application Settings and configure them appropriately.

You can find more information regarding source maps within our documentation site - Source Maps for JavaScript.

Uploading source maps with Raygun CLI

Raygun CLI is a command-line tool for Raygun.

Install with:

dart pub global activate raygun_cli

To upload Flutter web sourcemaps to Raygun, navigate to your project root and run the sourcemap command with the platform armument (-p) to flutter and set the uri, app-id and access token parameters.

  • uri is the full URI where your application main.dart.js will be installed to.
  • app-id the Application ID in Raygun.com.
  • token is an access token from https://app.raygun.com/user/tokens.
raygun-cli sourcemap -p flutter --uri=https://example.com/main.dart.js --app-id=APP_ID --token=TOKEN

The input-map argument is optional for Flutter web projects. raygun-cli will try to find the main.dart.js.map file in build/web/main.dart.js.map automatically.

For more information see raygun-cli on pub.dev

Comprehensive sample app

For a working sample app across multiple platforms, check the Flutter project in the example directory.

Known issues

  1. setMaxReportsStoredOnDevice is not exposed and is currently set to 64.

Libraries

raygun4flutter