8

I am trying to make an overlay such as that shown here: https://www.didierboelens.com/2018/06/how-to-create-a-toast-or-notifications-notion-of-overlay/ using OverlayEntry.

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

class ShowNotificationIcon {

    void show(BuildContext context) async {
        OverlayState overlayState = Overlay.of(context);
        OverlayEntry overlayEntry = new OverlayEntry(builder: _build);

        overlayState.insert(overlayEntry);
    }

    Widget _build(BuildContext context){
      return new Positioned(
        top: 50.0,
        left: 50.0,
        child: new Material(
            color: Colors.transparent,
            child: new Icon(Icons.warning, color: Colors.purple),
        ),
      );
    }
}

Invoked with:

ShowNotificationIcon _icon = new ShowNotificationIcon();

_icon.show(context);

However, when I try to navigate to other screens, the overlay remains in the screen.

How do I show the overlay only in the screen it is being called and not in the others?

Just in case, this is what I had tried inside my stateful widget:

  ShowNotificationIcon _icon = new ShowNotificationIcon();

  @override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _icon.show(context);
    });

    super.initState();
  }

  @override
  void dispose() {
    _icon.remove();
    super.dispose();
  }
Giraldi
  • 16,451
  • 6
  • 33
  • 52
  • There is a package call overlay_container https://github.com/MustansirZia/overlay_container/blob/master/lib/overlay_container.dart ,perhaps you can get idea from it – chunhunghan Jul 29 '19 at 09:06
  • if you just need to show notification. you can use package flushbar https://pub.dev/packages/flushbar – chunhunghan Jul 29 '19 at 09:10
  • @chunhunghan Thanks for the input but I'm looking for a way to create my own code. – Giraldi Jul 29 '19 at 09:21
  • @chunhunghan I'll have a look at `overlay_container`. thanks for the suggestion. – Giraldi Jul 29 '19 at 09:27

3 Answers3

17

This is typically performed using RouteAware+RouteObserver.

RouteObserver is an object that lets objects that implements RouteAware react to some changes related to routing, which includes:

  • a route has been pushed on the top of the current one
  • the route is back to being on the first plan again

You can then use these two events to hide/show your overlay


First, you'll need a RouteObserver.

This can be created as a global variable and needs to be passed to your Navigator. In a MaterialApp based app, it'll typically look like this:

final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();

void main() {
  runApp(MaterialApp(
    home: Container(),
    navigatorObservers: [routeObserver],
  ));
}

Then, your widget that owns the OverlayEntry can now implement RouteAware like so:

class RouteAwareWidget extends StatefulWidget {
  State<RouteAwareWidget> createState() => RouteAwareWidgetState();
}

// Implement RouteAware in a widget's state and subscribe it to the RouteObserver.
class RouteAwareWidgetState extends State<RouteAwareWidget> with RouteAware {

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    // routeObserver is the global variable we created before
    routeObserver.subscribe(this, ModalRoute.of(context) as PageRoute);
  }

  @override
  void dispose() {
    routeObserver.unsubscribe(this);
    super.dispose();
  }

  @override
  void didPush() {
    // Route was pushed onto navigator and is now topmost route.
  }

  @override
  void didPopNext() {
    // Covering route was popped off the navigator.
  }

  @override
  Widget build(BuildContext context) => Container();

}

At this point, you can use didPush and didPopNext to show/hide your OverlayEntry:

OverlayEntry myOverlay;

@override
void didPush() {
  myOverlay.remove();
}

@override
void didPopNext() {
  Overlay.of(context).insert(myOverlay);
}
jwehrle
  • 4,414
  • 1
  • 17
  • 13
Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • How do I use the `routeObserver` in the widget? Copying the code above gave me the `undefined_identifier` error for `routeObserver`. – Giraldi Jul 30 '19 at 05:07
  • It's a global that we defined in a previous step. You have to import it – Rémi Rousselet Jul 30 '19 at 06:47
  • 1
    Okay, so far it worked really well. But I used `didPushNext()` instead of `didPush()` as the latter is called when the current route has been pushed & the `OverlayEntry` has yet to be inserted. – Giraldi Jul 30 '19 at 07:46
  • However, I was hoping to find a more straightforward method like the Snackbar's `showSnackBar()` function. It doesn’t have to worry about removing it on route-changed. Is there a way to detect route changes form within the function itself? As in my example above, inside the `ShowNotificationIcon` class? – Giraldi Jul 30 '19 at 07:47
  • There is no such thing. You can build your own on the top of RouteAware oif you want to, but you'll need at least that. – Rémi Rousselet Jul 30 '19 at 08:02
  • It works on calling remove inside onPop() but the app becomes unresponsive after that – minato Nov 28 '22 at 08:43
2

you just need CompositedTransformTarget , CompositedTransformFollower and LinkLayout.

Thanks to these, if the widget to which the overlay is attached disappears, the overlay will also disappear.

final key = GlobalKey();
OverlayEntry? floatingEntry ;
final layerLink = LayerLink();

void hideEntry(){
  floatingEntry ?.remove();
}

void displayOverlay() {
    final overlay = Overlay.of(context);
    floatingEntry = OverlayEntry(builder: _buildFloatingButton);
    overlay!.insert(floatingEntry!);
  }


 Widget _buildFloatingButton(BuildContext context) {
    final render = key.currentContext!.findRenderObject() as RenderBox;
    final offset = render.localToGlobal(Offset.zero);
    final size = render.size;
    return Positioned(
      width: floatinSize,
      child: CompositedTransformFollower(
        link: layerLink,
        offset: Offset(0.0, -size.height / 2),
        showWhenUnlinked: false,
        child: Container(
          width: floatinSize,
          height: floatinSize,
          decoration: BoxDecoration(
            color: Get.theme.scaffoldBackgroundColor,
            shape: BoxShape.circle,
          ),
          padding: const EdgeInsets.all(10.0),
          child: Container(
            decoration: BoxDecoration(
              color: Get.theme.primaryColor,
              shape: BoxShape.circle,
            ),
            child: Icon(
              FontAwesomeIcons.plus,
              color: Get.theme.primaryColorDark,
            ),
          ),
        ),
      ),
    );
  }

 @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size;
    return Container(
      width: size.width,
      height: _navigationHeight,
      color: Get.theme.bottomNavigationBarTheme.backgroundColor,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
              CompositedTransformTarget(
               link:linkLayout,
               child:Container(key:key),
              )
         ],
      ),
    );
  }
  • This almost worked for me (using `showWhenUnlinked: false`), except that the overlay continues showing on top of the new page as it animates into view and then disappears once the new page has finished transitioning in. Using RouteAware + RouteObserver behaves better, with the overlay disappearing as soon as the new route begins to transition in. – dokkaebi Jul 29 '22 at 21:47
0

I would like to suggest use package flushbar. https://github.com/AndreHaueisen/flushbar
As the package said: Use this package if you need more customization when notifying your user. For Android developers, it is made to substitute toasts and snackbars.

You can also set flushbarPosition to TOP or BOTTOM

Flushbar(
      title: "Hey Ninja",
      message: "Lorem Ipsum is simply dummy text of the printing and typesetting industry",
      flushbarPosition: FlushbarPosition.TOP,
      flushbarStyle: FlushbarStyle.FLOATING,
      reverseAnimationCurve: Curves.decelerate,
      forwardAnimationCurve: Curves.elasticOut,
      backgroundColor: Colors.red,
      boxShadows: [BoxShadow(color: Colors.blue[800], offset: Offset(0.0, 2.0), blurRadius: 3.0)],
      backgroundGradient: LinearGradient(colors: [Colors.blueGrey, Colors.black]),
      isDismissible: false,
      duration: Duration(seconds: 4),
      icon: Icon(
        Icons.check,
        color: Colors.greenAccent,
      ),
      mainButton: FlatButton(
        onPressed: () {},
        child: Text(
          "CLAP",
          style: TextStyle(color: Colors.amber),
        ),
      ),
      showProgressIndicator: true,
      progressIndicatorBackgroundColor: Colors.blueGrey,
      titleText: Text(
        "Hello Hero",
        style: TextStyle(
            fontWeight: FontWeight.bold, fontSize: 20.0, color: Colors.yellow[600], fontFamily: "ShadowsIntoLightTwo"),
      ),
      messageText: Text(
        "You killed that giant monster in the city. Congratulations!",
        style: TextStyle(fontSize: 18.0, color: Colors.green, fontFamily: "ShadowsIntoLightTwo"),
      ),
    )..show(context);

enter image description here

chunhunghan
  • 51,087
  • 5
  • 102
  • 120
  • Thanks for the input but I'm trying to learn the way to program such feature. So, no third-party libraries. Any idea? – Giraldi Jul 29 '19 at 09:24
  • overlay_container package construct just like you did. perhaps you can see it. – chunhunghan Jul 29 '19 at 09:28
  • Okay, I'll have a look at it. Thanks for the suggestion – Giraldi Jul 29 '19 at 09:30
  • I had a look at it. However, it didn't have the two requirements I was looking for, that is: 1) It should be called as a function. 2) It should be hidden when navigating to the next screen. – Giraldi Jul 29 '19 at 10:00