'ModalBarrier scrim does not cover AppBar

I've got a MaterialApp which uses a builder with a scaffold in it. When I navigate from page to page the scaffold and app bar does not rebuild, but the body of the scaffold does:


import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

import 'package:raven_front/pages/pages.dart';

Future<void> main() async {
  runApp(RavenMobileApp());
}

class RavenMobileApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/splash',
      routes: pages.routes(context),
      builder: (context, child) {
        return SafeArea(
            child: Scaffold(
          extendBodyBehindAppBar: false,
          appBar: BackdropAppBar(),      // pretty much regular app bar
          body: child!,                  // pages of app
        ));
      },
    );
  }
}

but when I'm on a page where I need to show a bottom modal sheet, or alert box, or anything with a scrim, it doesn't apply to the app bar:

scrim ignores app bar

for example, I might make the ModalBottomSheet this way

    await showModalBottomSheet<void>(
        context: context,
        elevation: 1,
        barrierColor: AppColors.black38,     // not applied to app bar
        shape: components.shape.topRounded,
        builder: (BuildContext context) {
         ... 
        });

With my setup of having a builder in the MaterialApp, how can I get the scrim to cover everything?

I tried saving the context used in the MaterialApp (highest level) and using that in the modal sheet, but that errored saying that context doesn't have a Navigator. I'm hoping I can keep the current design but extend the scrim over the app bar somehow.



Solution 1:[1]

can you believe it, I had to roll my own. the other option was to abandon the builder material app design mentioned in the question. This is what I had to do:

app bar:

Stack(
  children: [
    appBar,
    AppBarScrim(),
  ])

app bar scrim

class AppBarScrim extends StatefulWidget {
  const AppBarScrim({Key? key}) : super(key: key);

  @override
  State<AppBarScrim> createState() => _AppBarScrimState();
}

class _AppBarScrimState extends State<AppBarScrim> {
  late List listeners = [];
  final Duration waitForSheetDrop = Duration(milliseconds: 50);
  bool applyScrim = false;

  @override
  void initState() {
    super.initState();
    listeners.add(streams.app.scrim.listen((bool value) async {
      if (applyScrim && !value) {
        await Future.delayed(waitForSheetDrop);
        setState(() {
          applyScrim = value;
        });
      }
      if (!applyScrim && value) {
        setState(() {
          applyScrim = value;
        });
      }
    }));
  }

  @override
  void dispose() {
    for (var listener in listeners) {
      listener.cancel();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
        onTap: () async {
          Navigator.of(components.navigator.routeContext!).pop();
          streams.app.scrim.add(false);
        },
        child: AnimatedContainer(
          duration: waitForSheetDrop,
          color: applyScrim ? Colors.black38 : Colors.transparent,
          height: applyScrim ? 56 : 0,
        ));
  }
}
showDialog( // and show bottom modal sheet...
  ..
  builder: (BuildContext context) {
    streams.app.scrim.add(true);                      // trigger
    return AlertDialog(...);
  }).then((value) => streams.app.scrim.add(false));   // remove trigger

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 MetaStack