# Routing Instrumentation | Sentry for Flutter

Sentry's routing instrumentation for Flutter automatically tracks and reports page navigation events in your app. It supports both [traditional Flutter routing](https://docs.flutter.dev/ui/navigation) and the [GoRouter](https://pub.dev/packages/go_router) package.

The routing instrumentation feature is shipped with Sentry's Flutter SDK automatically.

## [Prerequisites](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#prerequisites)

Before starting, ensure:

1. The Sentry Flutter SDK `9.1.0` or later is installed.
2. The Sentry Flutter SDK is initialized. Learn more [here](https://docs.sentry.io/platforms/dart/guides/flutter.md#configure).
3. Tracing is set up. Learn more [here](https://docs.sentry.io/platforms/dart/guides/flutter/tracing.md).

## [Configure](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#configure)

### [1. Add `SentryNavigationObserver`](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#1-add-sentrynavigationobserver)

Add an instance of `SentryNavigationObserver` to your application's `navigatorObservers`.

```dart
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';

Future<void> main() async {
  await SentryFlutter.init((options) {
    options.dsn = '___DSN___';
  }, appRunner: () => runApp(SentryWidget(child: MyApp())));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(

        navigatorObservers: [
          SentryNavigatorObserver(),
        ],

        ...
    );
  }
}
```

### [2. Define Route Names](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#2-define-route-names)

By default the application's main route name is `"/"`. The instrumentation sets the span `operation` to `navigation` and the span `name` to the provided route name. For transactions to be created and breadcrumbs to be added when navigation changes, you need to provide route names:

* Flutter routing: use `RouteSettings` and set the name in the constructor.
* GoRouter: use the `name` parameter to set the route name.

Make sure that you set the route name for all routes. If you do not set the route name, the SDK will not instrument performance insights such as [TTID](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#time-to-initial-display) or [TTFD](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#time-to-full-display) or create breadcrumbs for the route.

```dart
MaterialPageRoute(
  builder: (BuildContext context) => MyWidget(),
  settings: RouteSettings(name: 'My Widget'),
)
```

## [Time to Initial Display](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#time-to-initial-display)

Time to initial display (TTID) provides insight into how long it takes your Widget to launch and draw their first frame. This is measured by adding a span for navigation to a Widget. The SDK then sets the span operation to `ui.load.initial-display` and the span description to the Widget's route name, followed by initial display (for example, `MyWidget initial display`).

TTID is enabled by default.

## [Time to Full Display](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#time-to-full-display)

Time to full display (TTFD) provides insight into how long it would take your Widget to launch and load all of its content. This is measured by adding a span for each navigation to a Widget. The SDK then sets the span operation to `ui.load.full-display` and the span description to the Widget's route name, followed by full display (for example, `MyWidget full display`).

TTFD is disabled by default. To enable TTFD measurements, follow these steps:

### [1. Enable the `enableTimeToFullDisplayTracing` option in the SDK configuration](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#1-enable-the-enabletimetofulldisplaytracing-option-in-the-sdk-configuration)

```dart
await SentryFlutter.init(
  (options) {
    options.dsn = '___DSN___';

    options.enableTimeToFullDisplayTracing = true;

  }, appRunner: () => runApp(SentryWidget(child: MyApp())),
);
```

### [2. Report the TTFD span](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#2-report-the-ttfd-span)

There are two ways to report when your widget is fully displayed:

#### [1. Wrap with `SentryDisplayWidget`](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#1-wrap-with-sentrydisplaywidget)

Embed your target widget in `SentryDisplayWidget` and call `SentryDisplayWidget.of(context).reportFullyDisplayed()`.

#### [2. Call the API directly](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#2-call-the-api-directly)

Retrieve the current display span via `SentryFlutter.currentDisplay()` in `initState()` and call `currentDisplay.reportFullyDisplayed()` — no wrapper needed.

**Important for `StatelessWidget`:**

If you're navigating to a `StatelessWidget`, you must use the `SentryDisplayWidget` wrapper. `SentryDisplayWidget` automatically reports TTFD as soon as the build completes. You do not need to call `reportFullyDisplayed()` yourself.

```dart
Navigator.push(
  context,
  MaterialPageRoute(
    settings: const RouteSettings(name: 'MyWidget'),

    builder: (context) => SentryDisplayWidget(child: MyWidget()),

  ),
);

// Inside MyWidget’s State:
@override
void initState() {
  super.initState();

  // Do some long running work...
  Future.delayed(const Duration(seconds: 3), () {
    if (mounted) {
      SentryDisplayWidget.of(context).reportFullyDisplayed();
    }
  });

}
```

If the span finishes through the API, its status will be set to `SpanStatus.OK`.

If the span doesn't finish after 30 seconds, it will be finished by the SDK automatically, and its status will be set to `SpanStatus.DEADLINE_EXCEEDED`. In this case, its duration will match the TTID span.

## [Verify](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#verify)

### [1. Implement a Test Widget:](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#1-implement-a-test-widget)

Set up a new widget that executes an expensive operation.

```dart
import 'package:flutter/material.dart';
import 'package:sentry/sentry.dart';

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

  @override
  MyWidgetState createState() => MyWidgetState();
}

class MyWidgetState extends State<MyWidget> {
  @override
  void initState() {
    super.initState();
    Future.delayed(const Duration(seconds: 3), () {
      if (mounted) {
        SentryDisplayWidget.of(context).reportFullyDisplayed();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return ...
  }
}
```

### [2. Navigate to the Widget:](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#2-navigate-to-the-widget)

Use the navigator to transition to your widget. This should create and send a transaction named after the widget's route.

```dart
import 'package:flutter/material.dart';
import 'my_widget.dart';

/// Push to a new screen
Navigator.push(
  context,
  MaterialPageRoute(
    settings: const RouteSettings(name: 'MyWidget'),
    builder: (context) => SentryDisplayWidget(child: MyWidget()),
  ),
);
```

Log into [sentry.io](https://sentry.io) and open your project's performance page to see the transaction `MyWidget`.

## [Additional Configuration](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#additional-configuration)

### [Auto Finish Time](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#auto-finish-time)

Adjust the duration before a routing transaction automatically finishes. The default is 3 seconds.

```dart
SentryNavigatorObserver(
  autoFinishAfter: Duration(seconds: 5)
)
```

When configuring the `autoFinishAfter` parameter, consider the following behaviours:

* Started child spans will be attached to the navigation transaction - for example the `MyWidget` transaction.
* If child spans finish after the `autoFinishAfter` time, the transaction extends and finishes when all child spans finished.
* If child spans finish before the `autoFinishAfter` time, the transaction's end time will be set to the last child end time.

### [Breadcrumb Tracking Only](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#breadcrumb-tracking-only)

Set `enableAutoTransactions` to `false` if you only want to track navigation breadcrumbs. Enabled by default.

```dart
SentryNavigatorObserver(
  enableAutoTransactions: false,
)
```

### [Ignore Routes](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#ignore-routes)

Set `ignoreRoutes` if you want routes to be ignored and not processed by the navigation observer. Empty by default.

```dart
SentryNavigatorObserver(
  ignoreRoutes: ["/ignoreThisRoute", "/my/ignored/route"],
)
```

### [Override Transaction Name](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#override-transaction-name)

Set `setRouteNameAsTransaction` to `true` to override the transaction name with the route name. An existing transaction in the scope 'CustomTransaction' will be renamed to 'MyWidget' for example. Disabled by default.

```dart
SentryNavigatorObserver(
  setRouteNameAsTransaction: true,
)
```
