themedSystemNavigationBar static method

SystemUiOverlayStyle themedSystemNavigationBar(
  1. BuildContext? context, {
  2. bool? useDivider,
  3. double opacity = 1,
  4. bool noAppBar = false,
  5. bool invertStatusIcons = false,
  6. FlexSystemNavBarStyle systemNavBarStyle = FlexSystemNavBarStyle.background,
  7. Color? systemNavigationBarColor,
  8. Color? systemNavigationBarDividerColor,
  9. Brightness nullContextBrightness = Brightness.light,
})

Returns a SystemUiOverlayStyle that by default has a system navigation bar style that matches the current theme.

For its default behavior it requires a build context with access to the inherited theme. This static function is a convenience wrapper for making a SystemUiOverlayStyle that follows current theme. For very customized styles consider using SystemUiOverlayStyle directly.

By default when calling themedSystemNavigationBar with context, it creates a SystemUiOverlayStyle where the system navigator bar uses current theme's ColorScheme.background as its background color and icon colors that match this background, without any divider.

The background color can be modified with systemNavBarStyle that can use: system, surface, background, scaffoldBackground or transparent options as background color options. It defaults to background. See FlexSystemNavBarStyle for more info.

In default Flutter M2 themes, the surface, background, scaffoldBackground and in light theme, even system are all the same color. For such themes this convenience property does not make so much sense. However, if you use FlexColorScheme and its primary color surface blending, or M3 kye color seed generated ColorSchemes, these colors may not be the same. This offers a convenient way to switch the background color of your system navigation bar in a way that matches your theme's surface branded background color and to choose which one of them to use.

This helper always sets systemNavigationBarContrastEnforced to false, to try to avoid the system scrim on Android version where it is supported. This is done because the selected background color is the scrim itself when used with the opacity parameter and we never want an extra scrim. If we set opacity very low and loose contrast due to that, it is because it is the usage intent.

An optional divider on the navigation bar is also available. Based on Flutter SDK docs, the divider on the navigation bar, is only respected on Android P (= Pie = SDK API 28 = Android 9) or higher. The divider can be turned on by setting useDivider to true. This produces a divider on top of the system navigation bar that in light theme mode uses color 0xFF2C2C2C and in dark mode and 0xFFDDDDDD.

You can modify the default color of the divider with the optional systemNavigationBarDividerColor. The call to set and use the divider color is only made once a none null or true value has been given to useDivider.

Android SDK < 29 does not respect provided alpha value on the color of the divider color, and calling it with null again will not remove it.

Be aware that once you have enabled the divider by setting it to true that there is no convenient way to get rid of it. You can set the value to false, but that will just make the divider same color as your current nav bar background color to make it invisible, it is still there, but this implementation trick works well.

Important: The divider is actually a layer behind the system navigation bar background, that is 1 dp higher. When using colors with opacity on the background and the divider, one have to consider the sum of the opacity for both colors to get the effective translucent color.

Use and support for the opacity value on the system navigation bar is supported starting from Flutter 2.5.

By default themedSystemNavigationBar does not set any system overlay for the status bar. In Flutter SDK the top status bar has its own built in SystemUiOverlayStyle as a part of AppBar and AppBarTheme.

FlexColorScheme also manages the SystemUiOverlayStyle for the status bar via it. However, if your screen has no AppBar you can use the property noAppBar and invertStatusIcons to affect the look of the status icons when there is no AppBar present on the page, this is useful e.g. for splash and intro screens.

Implementation

static SystemUiOverlayStyle themedSystemNavigationBar(
  BuildContext? context, {
  /// Use a divider line on the top edge of the system navigation bar.
  ///
  /// On Android 11 (SDK30) there was an issue when using the system
  /// divider, see: https://github.com/flutter/flutter/issues/100027
  /// This issue was found to be resolved on in tests onFlutter 3.7.7
  /// 15.3.2023. Keeping this references around to the issue in case some
  /// related issues appear. The system navigation bar on different Android
  /// versions is a complicated topic.
  ///
  /// Based on Flutter SDK docs, the divider on the navigation bar, is on
  /// respected on Android P (= Pie = SDK API 28 = Android 9) or higher. But
  /// based on our findings it does not work until Android 10 (SDK29 or
  /// higher.
  ///
  /// Important: The divider is actually a layer behind the system navigation
  /// bar background, that is 1dp higher. When using colors with opacity on
  /// the background and the divider, one have to consider the sum of the
  /// opacity for both colors to get the effective translucent color.
  ///
  /// Defaults to null.
  ///
  /// Keeping it null, by omission or passing null, always  omits the call
  /// to set any divider color in the created [SystemUiOverlayStyle].
  final bool? useDivider,

  /// Opacity value for the system navigation bar.
  ///
  /// The opacity value is applied to the provided `systemNavigationBarColor`
  /// or if it is null, to the color determined by `systemNavBarStyle`.
  ///
  /// Defaults to 1, fully opaque.
  ///
  /// This feature is supported starting from Flutter 2.5.
  /// Be aware that it only works on Android SDK >= 29. Earlier there were
  /// some issues on Android SDK < 29 before this PR landed in stable:
  /// https://github.com/flutter/engine/pull/28616
  ///
  /// This issue is a good source for more information on current state
  /// of transparent navigation bars in Flutter on Android:
  /// https://github.com/flutter/flutter/issues/90098.
  final double opacity = 1,

  /// Set this to true if you do not use a Material AppBar and want
  /// a uniform background where the status bar's icon region is.
  ///
  /// If your page does not have an [AppBar] you can also use this
  /// [AnnotatedRegion] helper to remove the top status bar scrim color
  /// on the top icon status bar, set [noAppBar] to true to do so.
  ///
  /// A typical use case would be pages like splash screens and intro
  /// screens that don't use an AppBar. The Material AppBar uses its own
  /// [SystemUiOverlayStyle] so don't use this with an AppBar, set the style
  /// on the AppBar or its theme instead. However, if you don't have an
  /// [AppBar] on screen, this is a convenient way of to remove the top
  /// system icon scrim for a more clean full screen look on Android.
  final bool noAppBar = false,

  /// Set to true to invert top status bar icons like, battery, network,
  /// wifi icons etc. in relation to their normal theme brightness related
  /// color.
  ///
  /// Defaults to false.
  ///
  /// This setting works well together with the [noAppBar] flag to make an
  /// even cleaner looking splash screen by making the
  /// top status bar icons less visible or even invisible.
  ///
  /// On a white background the status icons will be invisible, and if a
  /// fully black background is used in dark mode, they will be invisible
  /// in dark mode too. This can be used to create clean screen with no
  /// app bar and no status icons.
  ///
  /// For no status bar and and system navigation bar, you can also try using:
  /// `SystemChrome.setEnabledSystemUIOverlays(<SystemUiOverlay>[]);` to
  /// remove the top status bar and bottom navigation bar completely. When
  /// using that method there are however issues with them showing up again
  /// on navigation and when keyboard becomes visible, or app is restored
  /// from being in the background while using another app. You
  /// also have to manage putting the overlays back yourself manually with
  /// `SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values)` when
  /// moving away from the screen that had removed them. Using an
  /// `AnnotatedRegion` with `themedSystemNavigationBar` and both `noAppBar`
  /// and `invertStatusIcons` set to true, you can avoid these issues. You are
  /// however limited to using background white, in light mode and black in
  /// dark mode, if you want the status bar to be totally invisible and
  /// navigation bar to blend in with the background completely.
  final bool invertStatusIcons = false,

  /// The [FlexSystemNavBarStyle] used to determine the background color
  /// for the system navigation bar. Used when systemNavigationBarColor
  /// is null and context is not null, so theme colors corresponding to it
  /// can be used for the background color.
  ///
  /// Defaults to [FlexSystemNavBarStyle.background].
  final FlexSystemNavBarStyle systemNavBarStyle =
      FlexSystemNavBarStyle.background,

  /// Background color of the system navigation bar. If null the theme of
  /// context `colorScheme.background` will be used as background color.
  ///
  /// The point with this static helper is to give you a background color
  /// themed system navigation bar automatically. If you for some reason
  /// want a different color you can still override it this property.
  ///
  /// If `context` is null, `nullContextBrightness` will be used as brightness
  /// value and it will determine if the background is white (for
  /// Brightness.light) or black (for Brightness.dark) if this property is
  /// also null. The null context is mostly used for simple unit testing
  /// with no context, but can also be used to make a `SystemUiOverlayStyle`
  /// with this helper without having a context.
  final Color? systemNavigationBarColor,

  /// Optional color for the system navigation bar divider. A divider will
  /// only be present if `useDivider` is true and in this color if a
  /// value was given to it.
  ///
  /// If a color is not given the `Color(0xFF2C2C2C)` will be used in
  /// dark mode and the color `Color(0xFFDDDDDD)` will be used in light mode,
  /// as the divider color for the system navigation bar.
  ///
  /// Based on Flutter SDK docs, the divider on the navigation bar, is on
  /// respected on Android P (= Pie = SDK API 28 = Android 9) or higher. But
  /// based on our findings it does not work until Android 10 (SDK29 or
  /// higher.
  ///
  /// Important: The divider is actually a layer behind the system navigation
  /// bar background, that is 1dp higher. When using colors with opacity on
  /// the background and the divider, one have to consider the sum of the
  /// opacity for both colors to get the effective translucent color.
  final Color? systemNavigationBarDividerColor,

  /// Brightness used if context is null, mostly used for simple unit testing,
  /// with no context present. However, it can also be used to make a
  /// `SystemUiOverlayStyle` without having a context.
  ///
  /// Defaults to Brightness.light.
  final Brightness nullContextBrightness = Brightness.light,
}) {
  double usedOpacity = opacity;
  if (usedOpacity < 0) usedOpacity = 0;
  if (usedOpacity > 1) usedOpacity = 1;
  // If the systemNavBarStyle is FlexSystemNavBarStyle.transparent
  // set opacity to fully transparent.
  if (systemNavBarStyle == FlexSystemNavBarStyle.transparent) {
    usedOpacity = 0.0;
  }
  // If opacity is specified, we need to enable SystemUiMode.edgeToEdge to
  // be able to see content scrolling behind the transparent bar. We only do
  // so when we have any opacity specified.
  if (usedOpacity < 1) {
    unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge));
  }

  // If context was null, use nullContextBrightness as brightness value.
  final bool isDark = context != null
      ? Theme.of(context).brightness == Brightness.dark
      : nullContextBrightness == Brightness.dark;
  // Get the defined effective background color for the used style.
  // This is not pretty, but wanted a final for the flexBackground.
  final Color flexBackground = (context != null)
      ? systemNavBarStyle == FlexSystemNavBarStyle.system
          ? (isDark ? Colors.black : Colors.white)
          : systemNavBarStyle == FlexSystemNavBarStyle.background
              ? Theme.of(context).colorScheme.background
              : systemNavBarStyle == FlexSystemNavBarStyle.surface
                  ? Theme.of(context).colorScheme.surface
                  : systemNavBarStyle ==
                          FlexSystemNavBarStyle.scaffoldBackground
                      ? Theme.of(context).scaffoldBackgroundColor
                      : Theme.of(context).scaffoldBackgroundColor
      : (isDark ? Colors.black : Colors.white);
  // If a systemNavigationBarColor color is given, it will always be used,
  // If it is not given, we use above flexBackground.
  final Color background = systemNavigationBarColor ?? flexBackground;

  // A divider will be applied if `useDivider` is true and it will
  // use provided `systemNavigationBarDividerColor` if a value was given,
  // or fallback to suitable theme mode default divider colors if no
  // color was given.
  //
  // The logic below is intended to keep the `dividerColor` used in the
  // [SystemUiOverlayStyle] as null as long as `useDivider` is null. As soon
  // as it is not, it will set a Divider color, also with `false` value. With
  // false it will be set to resulting background color in order to hide the
  // divider by making it background colored. This is a way to remove
  // the divider if it has been enabled earlier, since you cannot remove it
  // with a null color value after it has been enabled with any known
  // `SystemUiOverlayStyle` and `SystemChrome` call. The false value then
  // provide means to at least hide it again, but it will still be there.
  //
  // Worth noticing is also that the opacity does not have any effect on
  // divider color in SDK<29 of Android. We apply some opacity anyway because
  // if you are using transparent system navigation bar on Android API30 or
  // higher it does work, and it looks nicer when it has some transparency
  // if the navbar is also transparent.
  Color? dividerColor;
  // If we have opacity on the navbar, we should have some on the divider too
  // when we have a divider, we use some, but not a lot, we do want to keep
  // visible and not fade away with background opacity, since a divider was
  // requested.
  final double dividerOpacity = usedOpacity < 1 ? 0.5 : 1;
  if (useDivider == null || !useDivider) {
    // To be able to take away the divider wi have to make it transparent.
    dividerColor = Colors.transparent;
  } else {
    // Requested a divider, but have no given color, use defaults.
    if (systemNavigationBarDividerColor == null) {
      dividerColor = isDark
          ? const Color(0xFF2C2C2C).withOpacity(dividerOpacity)
          : const Color(0xFFDDDDDD).withOpacity(dividerOpacity);
    } // We should have a divider, with a given color.
    else {
      dividerColor =
          systemNavigationBarDividerColor.withOpacity(dividerOpacity);
    }
  }

  // Determine effective AppBar background color, so we can compute
  // its brightness need for status icons. This was used earlier as
  // workarounds to https://github.com/flutter/flutter/issues/100027
  // It may no longer be needed.
  late Color appBarColor;
  if (context == null) {
    // This is for testing, we set it to same as navbar.
    appBarColor = background;
  } else {
    final ThemeData theme = Theme.of(context);
    final ColorScheme colorScheme = theme.colorScheme;
    final AppBarTheme appBarTheme = AppBarTheme.of(context);
    appBarColor = appBarTheme.backgroundColor ??
        (colorScheme.brightness == Brightness.dark
            ? colorScheme.surface
            : colorScheme.primary);
  }
  final Brightness appBarBrightness =
      ThemeData.estimateBrightnessForColor(appBarColor);

  // Making finals for each SystemUiOverlayStyle property, these were used
  // to modify the logic when needed to experiment with work around for:
  // https://github.com/flutter/flutter/issues/100027
  // The issue has now been solved and we could potentially remove some
  // of this, but keeping it place for now as everything finally worked
  // as intended on Android 9 to 13 in tests 15.3.20222 on Flutter 3.7.7.
  // Also it makes the returned SystemUiOverlayStyle call look very clean.
  final Color? statusBarColor = noAppBar ? Colors.transparent : null;
  final Brightness? statusBarBrightness =
      noAppBar ? (isDark ? Brightness.dark : Brightness.light) : null;
  final Brightness? statusBarIconBrightness = noAppBar
      ? invertStatusIcons
          ? (isDark ? Brightness.dark : Brightness.light)
          : (isDark ? Brightness.light : Brightness.dark)
      : invertStatusIcons
          ? appBarBrightness
          : null;
  final Color sysNavigationBarColor = background.withOpacity(usedOpacity);
  final Color sysNavigationBarDividerColor =
      invertStatusIcons ? Colors.transparent : dividerColor;
  final Brightness systemNavigationBarIconBrightness = invertStatusIcons
      ? (isDark ? Brightness.dark : Brightness.light)
      : (isDark ? Brightness.light : Brightness.dark);

  return SystemUiOverlayStyle(
    // The top status bar settings.
    systemStatusBarContrastEnforced: false,
    statusBarColor: statusBarColor,
    statusBarBrightness: statusBarBrightness,
    statusBarIconBrightness: statusBarIconBrightness,
    // The bottom navigation bar settings.
    systemNavigationBarContrastEnforced: false,
    systemNavigationBarColor: sysNavigationBarColor,
    // Divider setting.
    systemNavigationBarDividerColor: sysNavigationBarDividerColor,
    // Bottom system navigation bar icon or swipe bar navigator color.
    systemNavigationBarIconBrightness: systemNavigationBarIconBrightness,
  );
}