Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter draw Flushbar over any BuildContext

Tags:

flutter

I use flushbar to display in-app notifications to the user, but I think this issue applies to any widget that one wants to overlay over the current screen, such as a snackbar.

I want to be able to display a notification that is independent of the navigation between screens, i.e., if the user navigates between the screens the notification should stay across all screens. Because the flushbar notification is drawn over the current BuildContext only, as soon as the user closes the current screen, the notification disappears as well (because the notification widget is part of that screen's widget subtree).

Is there a way to display a widget (such as a notification) on top of the entire app, no matter the navigation?

EDIT1: Added example code.

import 'package:flushbar/flushbar_route.dart' as route;
import 'package:flushbar/flushbar.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: FirstScreen(),
    );
  }
}

class FirstScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Screen'),
      ),
      body: Center(
        child: RaisedButton(
          child: Text('go to second screen'),
          onPressed: () {
            Navigator.of(context).push(
              MaterialPageRoute(builder: (BuildContext context) => SecondScreen())
            );
          },
        ),
      ),
    );
  }
}

class SecondScreen extends StatelessWidget {

  final Flushbar _flushbar = Flushbar(message: 'Flushbar Notification');

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Screen'),
      ),
      body: Center(
        child: RaisedButton(
          child: Text('show flushbar'),
          onPressed: () {
            showFlushbar(_flushbar, context);
          },
        ),
      ),
    );
  }
}

Future showFlushbar(Flushbar instance, BuildContext context) {
  final _route = route.showFlushbar(
    context: context,
    flushbar: instance,
  );

  return Navigator.of(context, rootNavigator: true).push(_route);
}

The result will look like this (when going back to the first screen I would want the notification to stay on the screen):

example app demo

EDIT2: George's solution works. Additionally, using overlays might be a suitable solution for others as well (see this blog post), i.e., the overlay stays on-screen even during route navigation. However, the overlay solution is less elegant in my case because it doesn't allow the flushbar to be dismissible or animated (or at least it's not as straightforward).

Related question: Flutter - Application Wide Notifications

like image 708
chris Avatar asked Nov 17 '22 06:11

chris


1 Answers

Try displaying your Flushbar from the root Navigator.

Looking at the sources of flushbar lib, we can create our own Flushbar route by importing showFlushbar method from package:flushbar/flushbar_route.dart.

e.g.

import 'package:flushbar/flushbar_route.dart' as route;

// ...

Future showFlushbar(Flushbar instance) {
  final _route = route.showFlushbar(
    context: context,
    flushbar: instance,
  );

  return Navigator.of(context, rootNavigator: true).push(_route);
}

Update

Thank you for the added code in your question.

The ultimate solution, in my opinion, is creating another Navigator right inside of MaterialApp - some kind of a second 'sub-root' navigator.

Now you will be able to choose between 2 layers of Navigator depending on your needs.

Navigator.of(context, rootNavigator: true) will return the top-level Navigator - use it for your Flushbar or any other modal popups that you want to keep persistent above all screens.

Navigator.of(context), where rootNavigator is false by default. Use it to get the 'sub-root' Navigator to display new screens / pages.

e.g. placing another Navigator right in the MaterialApp.

MaterialApp(
  home: Navigator( // 'sub-root' navigator. Second in the hierarchy.
    onGenerateRoute: (_) => MaterialPageRoute(
      builder: (_) => FirstScreen(),
      settings: RouteSettings(isInitialRoute: true),
    ),
  ),
);

Let me know if this helped.

like image 163
George Avatar answered Jan 20 '23 21:01

George