Bird Watching: Using Sentry in Flutter
24 Jan 2019
Error reporting is an essential component of deploying a modern mobile app into production. As an app developer, you need to be proactive about being notified of errors or worse, crashes of your app occuring for end users and you need to do so without having to rely on users to manually send reports of these errors or crashes to you. Luckily this is now a well established and mature field with a number of service providers available for developers that don't need or want to "roll their own" solution.
In the case of Flutter, while there are several choices. I choose to use Sentry, not just because it is the recommended as an example on the Flutter website but also because I found it much easier to use than Crashlytics, it has a pure Dart and fully open source client first-party package (which at the time of my evaluation Crashlytics did not, though happily it now does have). What's even more impressive is that should I wish to self-host in the future, the Sentry backend is also available as open source and doing so is well documented!
Yet when I evaluated using Sentry for a Flutter app in late 2019, the Sentry client package was missing what I consider a critical feature for mobile apps: "context".
So what do I mean by context?
Well it's all the useful information that gives you the context for the error or crash. In the case of mobile apps, this is things like the OS, device model, device hardware (such as cpu, screen size, screen ratio), device state (battery, memory, network), etc. For Sentry, this is called the "Contexts Interface" and is part of every event payload sent to the backend by the Sentry client. Because of the various use-caes for Sentry, this is considered an optional part of an event by Sentry, but by not being implemented in the Sentry client, it meant the error/crash reports submitted were going to be of very limited use, apart from simply identifying the error that had occured and its accompanying stack trace.
A detour: the open-source process
So in order to make use of Sentry I would first need Contexts Interface support added to the Dart Sentry package. As I contemplated needing to spend time to contribute this, I was very happy to discover that Wilko Manger has already done so a few months prior and had contributed a PR already.
While it had not yet been merged into the Flutter team's Sentry repo, thanks to Pub's support for using Git repos as sources for packages, it was simple to first test out using the context branch and then fork my own repo with that branch for use in my app.
Note: Whilst Pub supports using Git repos as sources for packages it is an extremely BAD idea to use repos that you do not control.
Given how easy it is to for a Git repo, I suggest to always only use repos that you control for any apps that will be used in production.
However, I am a firm believer in not maintaining minor forks and instead upstreaming whenever possible, so I took on the task of helping get the PR to the state needed to get it merged, which I'm happy to report it now is 🎉
To make use of the new Contexts support make sure you are using at least version
3.0.1 of the Sentry package in your
dependencies: sentry: ^3.0.1
Actually making use of Sentry in your Flutter app is straight forward and well documented in its package Readme as well as the tutorial on the Flutter website, though for some of fine details, referring to the excellant example app is invaluable and I am again impressed with how much effort the Flutter team puts into its documentation.
For those interested in seeing it all in one place and an example of how to fill out the context fields, my implementation can be seen in the new Flutter-based version of my SketchNotes app: error_reporting.dart.
For those building open source apps, one thing to be aware of is storage of the Sentry DSN. Whilst Sentry points out that the DSN is not a secret and that the worst case is that a third party would be able to send events into your configuration, it's still probably a good idea not to include the DSN in open source git repositories, if only to prevent inadvertant usage by people forking your repo.
In my case, I chose to store it as a environment variable in my CodeMagic CI configuration, but that then posed the challenge of how to then make use of it at run-time vs build-time within my app code.
The method I chose for this was some very basic code generation, similar to the technique described in this great article. However I chose to keep things as simple as possible and used a small shell script rather than Dart code to generate the required code:
#!/usr/bin/env sh set -e # exit on first failed commandset mkdir -p $FCI_BUILD_DIR/android/app/src/main/resources echo dsn=$SENTRY_DSN > $FCI_BUILD_DIR/android/app/src/main/resources/sentry.properties echo "const DSN='$SENTRY_DSN';" > $FCI_BUILD_DIR/lib/env.dart
Because the const is of course required for even when the I'm not using Sentry for local dev or debug builds, the shell script actually overwrites a placeholder version of the file that is stored under git control:
/// NOTE: this file will be OVER-WRITTEN at build time with values /// from environment variables on the build/CI server const DSN = 'PROVIDED_AT_BUILD_TIME';
Native error reporting
Whilst the above is suffcient to report errors and crashes that occur in Dart code, it will not do so for any native code errors. For that you need to also enable use of the Sentry client, specific to each platform you are deploying on.
For Android, I chose to use the stable Java client library vs their new beta Android specific client, so followed these instructions in Sentry's documentation.
I'm very happy so far with my choice of Sentry, the integration into my Flutter apps has been very easy and I've enjoyed makign a small contribution to the Dart client package. I also find Sentry's web console very nice to use, what many features that I have yet to make full use of and thier commitment to releasing both their backend as well as their client libraries as open source very commendable.
Infact I like using Sentry so much that I plan to also convert my other open source android app: MGit (a git client) over to using Sentry instead of the custom manual email based crash reporting I have had.
If you decide to use Sentry in your Flutter app, I hope this article helps you get up and running with it.