Quick start for Flutter
Four steps and one line that is yours. There is no SDK of ours to install: this is the MIT licensed sentry_flutter package, unchanged, with its DSN pointed somewhere else.
Before you start
A project in Monitor, and its DSN.
Create the project, then open its Monitor settings screen and copy the DSN from the key card. It looks like https://<public key>@monitor.unrealops.ai/ingest/<project>. It is safe to commit: the key inside it ships in every copy of your app by design and can only write events, never read them.
Take one DSN for the project rather than one per environment. The environment is a label the app puts on the event, and it is set further down this page.
Point the app at it
Add the package
flutter pub add sentry_flutterNothing else is needed. There is no bundler plugin, no wizard to run and no second package.
Initialize before runApp
In main(), wrapping runApp through appRunner. That is what installs the Flutter error handler and the zone that catches unhandled asynchronous errors, so anything thrown after this point is reported without you calling anything.
import 'package:flutter/widgets.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn =
'https://<public key>@monitor.unrealops.ai/ingest/<project>';
// Nothing sets this for you. Without it every build reports no
// environment at all, and a tester's crash is indistinguishable
// from a customer's.
options.environment = const String.fromEnvironment(
'APP_ENV',
defaultValue: 'development',
);
// Zero to start with, so the first thing you see is a crash rather
// than an allowance already spent. Transactions are stored, and each
// one costs a whole event out of the same monthly pool your errors
// come from, so raise this deliberately and raise it a little.
options.tracesSampleRate = 0.0;
},
appRunner: () => runApp(const MyApp()),
);
}Throw once, on purpose
Run the app in debug and trigger this from a button. The SDK sends from a debug build too, so you do not have to cut a release to find out whether any of this worked.
import 'package:sentry_flutter/sentry_flutter.dart';
Future<void> sendFirstEvent() async {
await Sentry.captureException(
StateError('first event from the Flutter app'),
stackTrace: StackTrace.current,
);
}Watch it arrive
The issue appears in the project's issue list, usually within a second or two, with the stack trace, the breadcrumbs that led up to it, and the device, operating system, app and release context the SDK collected. If you have just created the project, the onboarding screen is already watching for it and will tell you when it lands.
Dart and Flutter exceptions arrive readable with no setup at all, which is most of what a Flutter app throws. Native iOS and Android crashes arrive as memory addresses, and an obfuscated release build arrives as mangled names. Both are fixed on the readable stack traces page, and neither is in the way of what you just did.
Set an environment on the first day
The SDK sets none by default, and the cost of leaving it that way grows quietly.
With no environment, every build you have ever shipped reports the same one, which is none. Your own crash from a simulator, a tester's crash from a beta and a customer's crash from the store are then a single undifferentiated list, and the issue filters have nothing to filter on. The fix costs one line at init and one flag at build time.
flutter build ipa --release --dart-define=APP_ENV=production
flutter build appbundle --release --dart-define=APP_ENV=productionThe value is a label on the event, not a property of the DSN. That is why one DSN is enough: the same binary can report as staging or production depending on how it was built or configured, and you never ship two addresses in one app.
let isTestFlight = Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"Releases need no configuration
There is nothing to create in Monitor and no finalize step.
The Flutter SDK reads your package and sends a release of the form com.example.app@1.4.2+31. Monitor creates the release the first time an event names one, which is what the Releases screen and the crash free rates per release are built from, and what makes an issue reopened on a newer build a regression rather than an old issue with a new event on it.
Set options.release yourself only if you want a different string. If you do, use the same string when you upload symbols, because that is what ties a build's crashes to its symbols.
What to do next
- Readable stack traces. The build flags that produce symbols, the upload command, and what gets stored. Do this before you ship a release build, because the flags are a build time decision and an obfuscated build with no symbols is unreadable afterwards.
- Moving from Sentry. If you already have Sentry, run both for a week on the same crashes before deciding anything.
- Add an address under Alerts on the Monitor settings screen. Nothing is emailed until you do: the platform does not guess at an address nobody gave it.
- If the first event never arrives, the troubleshooting page starts with exactly that symptom.