How to capture errors and send platform-specific information to Sentry in Flutter? 
Tags: flutter, dart, ios, android
Published: 2019-12-16 18:15:09

>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](https://pub.dev/packages/sentry) 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](https://www.sentry.io).
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`:

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

```dart
/// 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](https://pub.dev/packages/sentry). 

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

```dart 
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: 

```dart
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](https://pub.dev/packages/device_info) to help us getting device information with ease! Simply, add it to `pubspec.yaml`:

```yaml
dependencies:
  device_info: ^0.4.1+4
```
and then import it to your code and create `DeviceInfoPlugin`:
```dart
final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
```

Then, get IOS or Android platform information. 
```dart
final IosDeviceInfo iosDeviceInfo = await deviceInfo.iosInfo;
```
or 
```dart
final AndroidDeviceInfo androidDeviceInfo = await deviceInfo.androidInfo;
```
and finaly, create your Sentry event with these `extra` information so the final code will look like: 

```dart
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](https://thepracticaldev.s3.amazonaws.com/i/60inrlvtg6am726pd6x4.png)

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

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

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

```dart
  // 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`: 

```dart
    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](https://twitter.com/mhadaily) and react to the article. 

Your feedback is also highly appreciated. 
Happy debugging,