Small details that build taste in Flutter.

curated by Kamran BekirovKamran Bekirov

Show real screen names in analytics

Analytics tools often show a Flutter app as one screen. Every page gets counted as FlutterViewController.

Here's an example from Google (Firebase) Analytics:

Google Analytics screens report showing FlutterViewController and native activity names instead of app screens

That FlutterViewController row is the native view Flutter draws your whole app into. Analytics SDKs track native views, and your app only has this one. So the SDK can't detect your pages at all.

The native reporting is just noise, so turn it off first. For Firebase:

<!-- iOS: Info.plist -->
<key>FirebaseAutomaticScreenReportingEnabled</key>
<false/>
<!-- Android: AndroidManifest.xml -->
<meta-data
    android:name="google_analytics_automatic_screen_reporting_enabled"
    android:value="false" />

Then report screens from Flutter, where every screen is a route. Add your SDK's navigator observer:

MaterialApp(
  navigatorObservers: [
    FirebaseAnalyticsObserver(
      analytics: FirebaseAnalytics.instance,
    ),
  ],
)

It reports the route's settings.name on every push and pop. No name, no event. So the job is to name every route.

Named routes already have one. pushNamed('/cart') reports as /cart.

go_router fills it from the route's name or path. Pass the observer to GoRouter(observers: [...]) instead of MaterialApp. Two edge cases here. With a custom pageBuilder you build the page yourself, so set its name too:

GoRoute(
  path: '/product',
  pageBuilder: (context, state) {
    return MaterialPage(
      name: state.name ?? state.path,
      child: ProductPage(),
    );
  },
),

And a ShellRoute or StatefulShellRoute branch runs its own inner navigator, so it needs its own observers list.

Plain Navigator.push has no name, so pass it:

Navigator.of(context).push(
  MaterialPageRoute(
    settings: RouteSettings(
      name: 'product_details',
    ),
    builder: (_) {
      return ProductDetailsPage();
    },
  ),
);

Doing this on every push gets old. A small extension can derive the name from the widget, turning ProductDetailsPage into product_details:

Navigator.of(context).push(
  ProductDetailsPage().asPageRoute(),
);

Dialogs and bottom sheets are mostly not worth tracking as screens. If one is, log a screen view event manually when it opens. You can also pass routeSettings to showDialog and showModalBottomSheet, but check your SDK first. FirebaseAnalyticsObserver, for example, skips non-page routes by default, and its routeFilter parameter controls that:

FirebaseAnalyticsObserver(
  analytics: FirebaseAnalytics.instance,
  routeFilter: (route) {
    return route is PageRoute ||
        route is DialogRoute ||
        route is ModalBottomSheetRoute;
  },
)
Kamran Bekirov
Kamran Bekirov

Want this level of care in your Flutter apps?

Work With Me