Giraldi
Giraldi

Reputation: 17291

How to make OverlayEntry disappear after navigating with pushNamed in Flutter

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();
  }

Upvotes: 10

Views: 14848

Answers (3)

Taylan YILDIZ
Taylan YILDIZ

Reputation: 51

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),
              )
         ],
      ),
    );
  }

Upvotes: 2

Rémi Rousselet
Rémi Rousselet

Reputation: 276891

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);
}

Upvotes: 18

chunhunghan
chunhunghan

Reputation: 54365

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

Upvotes: 0

Related Questions