Troubleshooting

Troubleshoot and resolve edge cases regarding known limitations and bundling.

If you need help solving issues with Sentry's Flutter SDK, you can read the edge cases documented here. If you need additional help, you can ask on GitHub. Customers on a paid plan may also contact support.

Starting May 1, 2024, Apple requires all apps submitted to the App Store to provide a list of privacy-related APIs they use, including the reasons under which they use it. If you received an email from Apple with the message "ITMS-91053: Missing API declaration", your app doesn't fulfill the requirements. To solve this, follow our Apple Privacy Manifest guide.

  • If you enable the split-debug-info and obfuscate features, you must upload debug symbols.
  • Issue titles might be obfuscated as we rely on the runtimeType, but they may not be human-readable. See the Obfuscate Caveat.
  • Layout related errors are only caught by FlutterError.onError in debug mode. In release mode, they are removed by the Flutter framework. See Flutter build modes.
  • Use inbound filters to exclude unhandled errors that are caught outside of your application in release builds. The SDK cannot filter these directly due to obfuscated stack traces.
  • If your app runs on Windows and uses a Flutter version below 3.3.0, you need to set the version and build number manually, see this issue on GitHub. To do so:
    • Use Dart defines to build the app: flutter build windows --dart-define=SENTRY_RELEASE=my_app@1.0.0+1
    • Or, set the release on SentryOptions options.release = 'my_app@1.0.0+1' during SDK initialization.

The Sentry Flutter SDK includes the Sentry Android SDK, which bundles multiple native libraries for multiple ABIs. Building a Flutter app for a specific ABI using the --target-platform argument, for example an ARM 32bit apk, looks like this, which should also include the --split-per-abi flag:

Copied
flutter build apk --target-platform=android-arm --split-per-abi

The configuration for symbolication of Native events (C/C++) is documented in our Android Native Development Kit content. If you are having issues with symbolication in Flutter, check that your configuration is correct, as discussed in our Flutter content that covers Uploading for Android NDK

Flutter split-debug-info and obfuscate flags are supported on iOS/macOS. They require compiling your app using Flutter, version 3.7.0 and above and the Sentry Flutter SDK, version 6.10.0 and above.

Source Context support requires compiling your app using the split-debug-info build parameter on Flutter 3.10.0 and above. You must also upload debug symbols with the upload_sources option enabled.

If you are using the Sentry Dart Plugin to upload Debug Symbols, refer to the points below to resolve potential issues.

A Sentry auth_token can be generated at the Organization Auth Tokens settings page.

Dart's --obfuscate option is required to be paired with --split-debug-info to generate a symbol map. See Dart docs for more information.

The --split-debug-info option requires setting an output directory. The directory must be an inner folder of the project's folder. See Flutter docs for more information.

Flutter's build web command requires setting the --source-maps parameter to generate source maps. See Flutter GitHub Issue for more information.

There is an issue with the Sentry Flutter SDK where apps can crash if taking screenshots in the background.

This is an issue with Flutter itself and should be fixed in Flutter 3.15.

For prior versions, you can work around this by configuring the SDK only to take screenshots when the app is in the resumed state. To do this, set SentryFlutterOptions.attachScreenshotOnlyWhenResumed to true.

Multi-view embedding was introduced in Flutter 3.24. In the Flutter docs you will find a detailed guide about the usage of this feature.

Using Sentry in a multi-view application is possible, but there are some limitations. The following features do not currently support multi-view:

  • Screenshots via the SentryScreenshotWidget (which is part of the SentryWidget)
  • User interaction integration via the SentryUserInteractionWidget (which is part of the SentryWidget)
  • Window & Device events via the WidgetsBindingIntegration

To prevent the WidgetsBindingIntegration from loading by default, you will need to remove the integration.

Copied
// ignore: implementation_imports
import 'package:sentry_flutter/src/integrations/widgets_binding_integration.dart';
...
SentryFlutter.init(
  (options) {
    ...
    final integration = options.integrations
        .firstWhere((element) => element is WidgetsBindingIntegration);
    options.removeIntegration(integration);
  },
  // Init your App.
  appRunner: appRunner,
);

Copy the main.dart into the lib folder of your existing project. This file already contains the code of the multi_view_app.dart from the flutter documentation. Next, copy the flutter_bootstrap.js and the index.html into the web folder.

Make sure you are using Flutter 3.24 or newer and run the application.

Now you should be able to see 2 instances of the same application side by side, with different ViewIds in the body.

main.dart
Copied
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_flutter/src/integrations/widgets_binding_integration.dart';
import 'dart:ui' show FlutterView;

// ATTENTION: Change the DSN below with your own to see the events in Sentry. Get one at sentry.io
const String exampleDsn =
    'https://e85b375ffb9f43cf8bdf9787768149e0@o447951.ingest.sentry.io/5428562';

Future<void> main() async {
  await SentryFlutter.init(
    (options) {
      options.dsn = exampleDsn;
      final integration = options.integrations
          .firstWhere((element) => element is WidgetsBindingIntegration);
      options.removeIntegration(integration);
    },
    // Init your App.
    appRunner: () => runWidget(
      MultiViewApp(
        viewBuilder: (BuildContext context) => DefaultAssetBundle(
          bundle: SentryAssetBundle(),
          child: const MyApp(),
        ),
      ),
    ),
  );
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorObservers: [
        SentryNavigatorObserver(),
      ],
      home: Scaffold(
        body: Center(
          child: Text(
              'Sentry Flutter Example (ViewId:${View.of(context).viewId})'),
        ),
      ),
    );
  }
}

// multi_view_app.dart

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/// Calls [viewBuilder] for every view added to the app to obtain the widget to
/// render into that view. The current view can be looked up with [View.of].
class MultiViewApp extends StatefulWidget {
  const MultiViewApp({super.key, required this.viewBuilder});

  final WidgetBuilder viewBuilder;

  
  State<MultiViewApp> createState() => _MultiViewAppState();
}

class _MultiViewAppState extends State<MultiViewApp>
    with WidgetsBindingObserver {
  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _updateViews();
  }

  
  void didUpdateWidget(MultiViewApp oldWidget) {
    super.didUpdateWidget(oldWidget);
    // Need to re-evaluate the viewBuilder callback for all views.
    _views.clear();
    _updateViews();
  }

  
  void didChangeMetrics() {
    _updateViews();
  }

  Map<Object, Widget> _views = <Object, Widget>{};

  void _updateViews() {
    final Map<Object, Widget> newViews = <Object, Widget>{};
    for (final FlutterView view
        in WidgetsBinding.instance.platformDispatcher.views) {
      final Widget viewWidget = _views[view.viewId] ?? _createViewWidget(view);
      newViews[view.viewId] = viewWidget;
    }
    setState(() {
      _views = newViews;
    });
  }

  Widget _createViewWidget(FlutterView view) {
    return View(
      view: view,
      child: Builder(
        builder: widget.viewBuilder,
      ),
    );
  }

  
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return ViewCollection(views: _views.values.toList(growable: false));
  }
}
Help improve this content
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").