DEV Community

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

Posted on

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

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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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 {

}
Enter fullscreen mode Exit fullscreen mode

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) {

  }
}

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

and then import it to your code and create DeviceInfoPlugin:

final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
Enter fullscreen mode Exit fullscreen mode

Then, get IOS or Android platform information.

final IosDeviceInfo iosDeviceInfo = await deviceInfo.iosInfo;
Enter fullscreen mode Exit fullscreen mode

or

final AndroidDeviceInfo androidDeviceInfo = await deviceInfo.androidInfo;
Enter fullscreen mode Exit fullscreen mode

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,
  );
}

Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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');
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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);

Enter fullscreen mode Exit fullscreen mode

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);
      }
    };

Enter fullscreen mode Exit fullscreen mode

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,

Discussion (5)

Collapse
maks profile image
Maksim Lin

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...

Collapse
venkatd profile image
Venkat Dinavahi

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.

Collapse
maks profile image
Maksim Lin

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/...

Thread Thread
venkatd profile image
Venkat Dinavahi • Edited

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.

Collapse
ngothanhtai profile image
Walter Ngo

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