Open Drawer of current (topmost) route's Scaffold

Viewed 94

I'm writing an audio player. Like most media players (Youtube, Spotify, etc), I want a "remote" overlay on the screen while media is playing. No matter what the user is doing, they should be able to control the media.

I accomplished that with a Stack under MaterialApp

  MaterialApp(
    title: 'MyApp',
    navigatorObservers: [gRouteObserver],
    routes: appRoutes,
    builder: (context, child) {
      return Stack(children: [
        child!,
        Positioned(
            bottom: 55,
            width: MediaQuery.of(context).size.width,
            child: StatefulBuilder(builder: (context, _setState) {
              gPlayer.widgetSBRefresher = _setState;
              return gPlayer.started ? gPlayer.widget : const SizedBox(height: 0);
            }))
      ]);
    });

gPlayer.widget references this

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

  @override
  State<MiniPlayer> createState() => MiniPlayerState();
}

class MiniPlayerState extends State<MiniPlayer> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(context) {
    super.build(context);
    return Align(
        alignment: Alignment.bottomCenter,
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Container(
            color: Colors.black,
            child: Padding(
              padding: const EdgeInsets.all(2.0),
              child: Column(
                children: [
                    Material(
                    child: Row(
                      mainAxisSize: MainAxisSize.max,
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        AvatarAlone(id: gPlayer.current!.owner),
                        Expanded(
                          child: Padding(
                            padding: const EdgeInsets.all(5.0),
                            child: Text(gPlayer.playing
                              ? "Now Playing"
                              : "Paused"),
                          ),
                        ),
// here is the code I'll
// be talking about -->
                        IconButton(
                          color: Colors.white,
                          iconSize: 20,
                          icon: const Icon(MyIcons.bookmark),
                          onPressed: gPlayer.bookmarkBuilder,
                        ),
                        InkWell(child: Icon(gPlayer.playing ? MyIcons.pauseCircle : MyIcons.playCircle, size: 50), onTap: gPlayer.playPause)
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
        ));
  }

  refresh() {
    setState(() {});
  }
}

I used a code comment to point out this icon button.

IconButton(
  color: Colors.white,
  iconSize: 20,
  icon: const Icon(MyIcons.bookmark),
  onPressed: gPlayer.bookmarkBuilder,
),

So, when this widget is open and the app is on the home route ("/"), I can do

bookmarkBuilder() {
  Scaffold.of(gScaffApp.currentContext!).openDrawer();
}

and it will open the drawer.

I've attached the same drawers to all my routes' scaffolds.

When other routes are up, with their own scaffolds, I want bookmarkBuilder to open the drawer on the topmost route. But I can't quite figure out how.

1 Answers

So I have a working solution to this, but I don't love it.

I created a global variable, gScaffs, with gScaffApp as the first element.

List<GlobalKey<ScaffoldState>> gScaffs = [gScaffApp];

My secondary routes all use the same base scaffold widget

class _CardScaffoldState extends State<CardScaffold> {
  @override
  initState() {
    super.initState();
    gScaffs.add(GlobalKey());
  }

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async => false,
      child: Scaffold(key: gScaffs.last,
        drawer: DrawerBookmarks()
        ...

And the dispose method looks like this.

  @override
  dispose() {
    super.dispose();
    gScaffs.removeLast();
  }

And then, in my bookmarkBuilder function, I have this.

It's not clear to me why, but gScaffApp needs the drawer triggered one way, while the CardScaffolds need the drawer triggered the other way.

  bookmarkBuilder() {
    if (gScaffApp == gScaffs.last) {
      Scaffold.of(gScaffApp.currentContext!).openDrawer();
    } else {
      gScaffs.last.currentState!.openDrawer();
    }
  }
Related