showStreamSheet<T> function

Future<T?> showStreamSheet<T>({
  1. required BuildContext context,
  2. required StreamSheetScrollableWidgetBuilder builder,
  3. Color? backgroundColor,
  4. Color? barrierColor,
  5. String? barrierLabel,
  6. String? barrierOnTapHint,
  7. ShapeBorder? shape,
  8. BorderRadiusGeometry? borderRadius,
  9. BoxConstraints? constraints,
  10. double? elevation,
  11. Clip? clipBehavior,
  12. bool enableDrag = true,
  13. bool isDismissible = false,
  14. bool? showDragHandle,
  15. bool useNestedNavigation = false,
  16. bool useRootNavigator = false,
  17. bool? requestFocus,
  18. Offset? anchorPoint,
  19. StreamSheetDragStartCallback? onDragStart,
  20. StreamSheetDragEndCallback? onDragEnd,
  21. RouteSettings? settings,
})

Shows a Stream-styled modal sheet that slides up from the bottom of the screen.

The builder receives a ScrollController that should be attached to the topmost scrollable inside the sheet so dragging it past its top edge dismisses the sheet (scroll-aware drag-to-dismiss). Non- scrollable regions can also be dragged down to dismiss.

When useNestedNavigation is true, the sheet hosts its own Navigator. Routes pushed from inside the sheet stay within it, and a pop from the first nested route dismisses the entire sheet. The whole sheet can also be popped at once via StreamSheetRoute.popSheet.

Pushing a sheet from within another sheet stacks it: the underlying sheet scales down slightly while the new one rests on top, and the auto-implied StreamSheetHeader leading icon flips from cross to back-chevron.

The sheet honours system insets — its top is clamped to a minimum of MediaQueryData.viewPadding top so the header / drag handle clear the status bar, and horizontal insets are honoured for landscape and notched displays. The bottom inset is never applied, so the sheet visually meets the bottom edge of the screen.

Style:

Each visual property follows the same resolution chain — the per-call argument wins, then the ambient StreamSheetThemeData (from StreamTheme.sheetTheme / StreamSheetTheme), then the built-in fallback below.

  • shape takes precedence over borderRadius when provided. When both are null, the sheet's top corners are rounded with StreamRadius.xxxxl and the bottom corners are square.
  • backgroundColor defaults to StreamColorScheme.backgroundElevation1.
  • elevation drives the shadow cast around the sheet. Defaults to 1.
  • clipBehavior clips content against the border radius (defaults to Clip.none).
  • constraints caps the sheet's size — most useful on tablet / desktop. The sheet is bottom-aligned within the constraints. Defaults to BoxConstraints(maxWidth: 640).

Dismissal:

  • enableDrag (defaults to true) lets users pop the sheet by dragging it down. The drag handle stays draggable even when enableDrag is false, so the affordance never lies.
  • isDismissible (defaults to false) lets users pop the sheet by tapping the modal barrier. Pair with a non-null barrierColor to give users a visible target.
  • showDragHandle (defaults to true) reserves a drag handle at the top of the sheet content.
  • onDragStart / onDragEnd fire when the user starts / stops a drag gesture; isClosing on onDragEnd indicates whether the gesture will pop the sheet.

Routing:

  • useRootNavigator (defaults to false) selects which Navigator the sheet is pushed onto. Pass true to escape any nested navigators and push to the root.
  • requestFocus controls whether the sheet's first focusable descendant requests focus when the route activates.
  • anchorPoint is forwarded to a DisplayFeatureSubScreen so the sheet avoids display features like foldable hinges.
  • settings sets the RouteSettings of the underlying PageRoute.

Returns a Future that resolves to the value (if any) passed to Navigator.pop when the sheet is closed.

{@tool snippet}

A simple sheet with a title and a confirm action:

final result = await showStreamSheet<String>(
  context: context,
  builder: (sheetContext, scrollController) => Column(
    mainAxisSize: MainAxisSize.min,
    children: [
      StreamSheetHeader(
        title: const Text('Edit profile'),
        trailing: StreamButton.icon(
          icon: Icon(context.streamIcons.checkmark),
          onPressed: () => Navigator.of(sheetContext).pop('saved'),
        ),
      ),
      // ... sheet contents
    ],
  ),
);

{@end-tool}

See also:

Implementation

Future<T?> showStreamSheet<T>({
  required BuildContext context,
  required StreamSheetScrollableWidgetBuilder builder,
  Color? backgroundColor,
  Color? barrierColor,
  String? barrierLabel,
  String? barrierOnTapHint,
  ShapeBorder? shape,
  BorderRadiusGeometry? borderRadius,
  BoxConstraints? constraints,
  double? elevation,
  Clip? clipBehavior,
  bool enableDrag = true,
  bool isDismissible = false,
  bool? showDragHandle,
  bool useNestedNavigation = false,
  bool useRootNavigator = false,
  bool? requestFocus,
  Offset? anchorPoint,
  StreamSheetDragStartCallback? onDragStart,
  StreamSheetDragEndCallback? onDragEnd,
  RouteSettings? settings,
}) {
  assert(debugCheckHasMaterialLocalizations(context));

  final localizations = MaterialLocalizations.of(context);
  final sheetTheme = StreamSheetTheme.of(context);
  final defaults = _StreamSheetDefaults(context);
  final effectiveBuilder = useNestedNavigation ? _wrapWithNestedNavigator<T>(builder) : builder;

  // The nearest StreamSheetRoute above the current context, if any. We keep
  // the reference around so the new sheet can identify itself as a stacked
  // sheet (used by the auto-imply leading icon in [StreamSheetHeader] and
  // by [StreamSheetRoute.isStacked]).
  final parentSheet = _StreamSheetScope.maybeOf(context)?.route;

  final navigator = Navigator.of(context, rootNavigator: useRootNavigator);

  // Capture the calling context's [InheritedTheme]s (StreamTheme, Theme,
  // Directionality, MediaQuery overrides, etc.) and re-apply them inside
  // the route. The sheet is pushed onto the navigator's overlay, which
  // is typically *outside* any local theme overrides — without this
  // wrap, sheets pushed from a sub-tree with a custom theme would render
  // with the root theme.
  final capturedThemes = InheritedTheme.capture(from: context, to: navigator.context);

  return navigator.push<T>(
    StreamSheetRoute<T>(
      settings: settings,
      requestFocus: requestFocus,
      builder: effectiveBuilder,
      backgroundColor: backgroundColor,
      barrierColor: barrierColor ?? sheetTheme.barrierColor ?? defaults.barrierColor,
      barrierLabel: barrierLabel ?? localizations.scrimLabel,
      barrierOnTapHint: barrierOnTapHint ?? localizations.scrimOnTapHint(localizations.bottomSheetLabel),
      shape: shape,
      borderRadius: borderRadius,
      constraints: constraints,
      elevation: elevation,
      clipBehavior: clipBehavior,
      enableDrag: enableDrag,
      isDismissible: isDismissible,
      showDragHandle: showDragHandle,
      anchorPoint: anchorPoint,
      onDragStart: onDragStart,
      onDragEnd: onDragEnd,
      parentSheet: parentSheet,
      capturedThemes: capturedThemes,
    ),
  );
}