loading...
Cover image for How to capture errors and send platform-specific information to Sentry in Flutter?

How to capture errors and send platform-specific information to Sentry in Flutter?

mhadaily profile image Majid Hajian ・6 min read

If you don't use Sentry, this article still might be helpful. You may replace Sentry with your error tracking service solution.

One of the aspects of developing software is to handle errors and log them properly in order to mitigate them at the right time.

When we develop for Flutter, indeed, we have several options to capture and log the errors. One of the greatest tools which come with an easy-to-use dart package is Sentry.io.

It is not always easy to reproduce the errors that occur if there is no extra information about the platform or environment as recreating the exact same situation could be difficult and time-consuming.

So, in this article, I will share my experience in Flutter with Sentry by generating a custom event with extra platform-specific information for Android and iOS respectively to report to the Sentry.

So, you will learn:

  1. Get a DSN from Sentry
  2. Import the Sentry package
  3. Initialize SentryClient
  4. Generate Event with the platform-specific information
  5. Detect Debug mode
  6. Catch and report Dart Errors
  7. Catch and report Flutter Errors
  8. Summary

Let's get started!

1- Get a DSN from Sentry

I assume you already have a Sentry account or you are considering it.

To get a DSN, use the following steps:

  1. Create an account with Sentry.
  2. Log in to the account.
  3. Create a new app.
  4. Copy the DSN.

2- Import the Sentry package

Before you import the dart package to your code, you need to add the library to the pubspec.yaml:

dependencies:
  sentry: ^3.0.0+1

At the time of writing this article, the latest version is ^3.0.0+1 but you may upgrade if you read this later.

3- Initialize SentryClient

In your Dart code, import package:sentry/sentry.dart and create a SentryClient. for this purpose, I recommend creating a file named sentry_handler.dart and keep all relevant code in this file, it helps to keep the code organized.

/// sentry_handler.dart
import 'package:sentry/sentry.dart';

/// replace sentryDSN with your DSN
final SentryClient sentry = SentryClient(dsn: sentryDSN);

Next is to capture errors and stack traces, but before that, let's generate an even that contains all of the platform information while reporting the errors.

If you don't need this step you can simply skip it and instead use captureException which is default in Sentry dart package documentation.

4- Generate Event with the platform-specific information

Let's create a function named getSentryEvent where it returns an Event that is required by the capture method on sentry.

Future<Event> getSentryEnvEvent(dynamic exception, dynamic stackTrace) async {

}

Platform from dart:io provides information such as the operating system, the hostname of the computer, the value of environment variables, the path to the running program, and so on. You can get the name of the operating system as a string with the operatingSystem getter. You can also use one of the static boolean getters: isMacOS, isLinux, and isWindows. So, let's leverage this:

Future<Event> getSentryEnvEvent(dynamic exception, dynamic stackTrace) async {
  if (Platform.isIOS) {

  }

  if (Platform.isAndroid) {

  }
}

In order to get device information in Flutter, You can use Device Info dart package to help us getting device information with ease! Simply, add it to pubspec.yaml:

dependencies:
  device_info: ^0.4.1+4

and then import it to your code and create DeviceInfoPlugin:

final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();

Then, get IOS or Android platform information.

final IosDeviceInfo iosDeviceInfo = await deviceInfo.iosInfo;

or

final AndroidDeviceInfo androidDeviceInfo = await deviceInfo.androidInfo;

and finaly, create your Sentry event with these extra information so the final code will look like:

Future<Event> getSentryEnvEvent(dynamic exception, dynamic stackTrace) async {
/// return Event with IOS extra information to send it to Sentry
  if (Platform.isIOS) {
    final IosDeviceInfo iosDeviceInfo = await deviceInfo.iosInfo;
    return Event(
      release: '0.0.2',
      environment: 'production', // replace it as it's desired
      extra: <String, dynamic>{
        'name': iosDeviceInfo.name,
        'model': iosDeviceInfo.model,
        'systemName': iosDeviceInfo.systemName,
        'systemVersion': iosDeviceInfo.systemVersion,
        'localizedModel': iosDeviceInfo.localizedModel,
        'utsname': iosDeviceInfo.utsname.sysname,
        'identifierForVendor': iosDeviceInfo.identifierForVendor,
        'isPhysicalDevice': iosDeviceInfo.isPhysicalDevice,
      },
      exception: exception,
      stackTrace: stackTrace,
    );
  }

/// return Event with Andriod extra information to send it to Sentry
  if (Platform.isAndroid) {
    final AndroidDeviceInfo androidDeviceInfo = await deviceInfo.androidInfo;
    return Event(
      release: '0.0.2',
      environment: 'production', // replace it as it's desired
      extra: <String, dynamic>{
        'type': androidDeviceInfo.type,
        'model': androidDeviceInfo.model,
        'device': androidDeviceInfo.device,
        'id': androidDeviceInfo.id,
        'androidId': androidDeviceInfo.androidId,
        'brand': androidDeviceInfo.brand,
        'display': androidDeviceInfo.display,
        'hardware': androidDeviceInfo.hardware,
        'manufacturer': androidDeviceInfo.manufacturer,
        'product': androidDeviceInfo.product,
        'version': androidDeviceInfo.version.release,
        'supported32BitAbis': androidDeviceInfo.supported32BitAbis,
        'supported64BitAbis': androidDeviceInfo.supported64BitAbis,
        'supportedAbis': androidDeviceInfo.supportedAbis,
        'isPhysicalDevice': androidDeviceInfo.isPhysicalDevice,
      },
      exception: exception,
      stackTrace: stackTrace,
    );
  }

/// Return standard Error in case of non-specifed paltform
///
/// if there is no detected platform, 
/// just return a normal event with no extra information 
  return Event(
    release: '0.0.2',
    environment: 'production', 
    exception: exception,
    stackTrace: stackTrace,
  );
}

Awesome, now when you capture an error, not only you see stack traces but also you'll see extra information which might be helpful for debugging and reproducing the bug.

Alt Text

5- Detect Debug mode

With Sentry set up, you can begin to report errors. Since you don’t want to report errors to Sentry during development, first create a function that lets you know whether you’re in debug or production mode.

/// Whether the VM is running in debug mode.
///
/// This is useful to decide whether a report should be sent to sentry.
/// Usually reports from dev mode are not very
/// useful, as these happen on developers' workspaces
/// rather than on users' devices in production.
bool get isInDebugMode {
  bool inDebugMode = false;
  assert(inDebugMode = true);
  return inDebugMode;
}

I suggest adding isInDebugMode function to your utility file where you can use it globally throughout your application.

6- Catch and report Dart Errors

Next, use this isDebugMode in combination with the SentryClient to report errors when the app is in production mode.

/// Reports dart [error] along with its [stackTrace] to Sentry.io.
Future<void> reportError(Object error, StackTrace stackTrace) async {
  if (isInDebugMode) {
    // In development mode, simply print to console.
    print('No Sending report to sentry.io as mode is debugging DartError');
    // Print the full stacktrace in debug mode.
    print(stackTrace);
    return;
  } else {
    try {
      // In production mode, report to the application zone to report to Sentry.
      final Event event = await getSentryEnvEvent(error, stackTrace);
      print('Sending report to sentry.io $event');
      await sentry.capture(event: event);
    } catch (e) {
      print('Sending report to sentry.io failed: $e');
      print('Original error: $error');
    }
  }
}

In your main dart file entry for Flutter, Where you call runApp, import reportError function and assign it to onError callback.

  // Runs the app in a zone to be able to capture and send events to sentry.
    runZoned<Future<void>>(() async {
      await SystemChrome.setPreferredOrientations(<DeviceOrientation>[DeviceOrientation.portraitUp]).then((_) {
        runApp(YouAwesomeApp());
      });
    }, onError: reportError);

6- Catch and report Flutter Errors

In addition to Dart errors, Flutter can throw errors such as platform exceptions that occur when calling native code. To capture Flutter errors, override the FlutterError.onError property. If you’re in debug mode, use a convenience function from Flutter to properly format the error. If you’re in production mode, send the error to the onError callback defined in the previous step.

So, In your main dart file entry for Flutter, Where you call runApp:

    FlutterError.onError = (FlutterErrorDetails details, {bool forceReport = false}) {
      if (isInDebugMode) {
        // In development mode, simply print to console.
        FlutterError.dumpErrorToConsole(details);
      } else {
        // In production mode, report to the application zone to report to Sentry.
        Zone.current.handleUncaughtError(details.exception, details.stack);
      }
    };

7- Summary

Since buggy apps lead to unhappy users and customers, it’s important to understand how often your users experience bugs and where those bugs occur. That way, you can prioritize the bugs with the highest impact and work to fix them. However, sometimes it's crucial to know the specific information about the running platform where the errors occurred in order to reproduce and debug and at the end of the day fix.

Adding more platform-specific information to your error tracking service event whether is Sentry or other services helps to find out the details to resolve the bugs and errors easier by reproducing in the exact same environment.

I hope this small tutorial can help you to manage your errors with better and more extra information.

If you found this article helpful, consider following me here or on Twitter and react to the article.

Your feedback is also highly appreciated.
Happy debugging,

Posted on by:

mhadaily profile

Majid Hajian

@mhadaily

I am passionate software developer with years of developing and architecting complex web and mobile applications. My main focus is Flutter, PWAs, and performance.

Discussion

markdown guide
 

Great article!
Good news is sending the platform information in the extras is no longer necessary as support for contexts interface has now been added to the Dart sentry package, using which I documented as part of a blog post I wrote recently: manichord.com/blog/posts/bird-watc...

 

Hi, thanks for your article, but should clarify that fetching this platform info is still necessary. Only difference is that we would want to store this info in the context property instead of the extra property.

 

Thanks for the feedback, yes I probably should place more emphasis on this in my article, but I do clearly link in my article to a class in my own open source app that demonstrates how to get the platform data to send to Sentry: github.com/maks/SketchNotes2/blob/...

Thanks, that source code is very helpful. I wonder if they'd be open to a PR integrating the package with device_info and package_info :)
I'll check if they're interested once we have it working in our app.

 

Thanks for the article. It really helps me to integrate Sentry to my Flutter application.