Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save mohamedrashad102/29d3c2d62d80b383d0335d96afdb37aa to your computer and use it in GitHub Desktop.

Select an option

Save mohamedrashad102/29d3c2d62d80b383d0335d96afdb37aa to your computer and use it in GitHub Desktop.

How to Show a Snackbar Above a Dialog in Flutter

If you've ever built a complex Flutter app, you've probably run into this annoying UX issue: You open a Dialog, perform an action (like a network request), and trigger a success or error Snackbar. But instead of appearing on top, the Snackbar hides behind the dark overlay of the Dialog!

Here is why this happens and how to fix it permanently.

The Problem: ScaffoldMessenger

By default, Flutter developers use this to show Snackbars:

ScaffoldMessenger.of(context).showSnackBar(...);

When you open a Dialog using showDialog(), Flutter pushes a new route onto the Navigator. ScaffoldMessenger lives below the Navigator in the rendering tree. Because the Dialog has a barrier color (the dim background), anything drawn by the Scaffold (like your Snackbar) gets covered by that shadow.

The Solution: Overlay API

To fix this, we completely bypass ScaffoldMessenger and inject our Snackbar directly into the highest possible layer of the app using Flutter's Overlay system.

Step 1: Create an Overlay Snackbar Manager

Instead of calling showSnackBar, we create a helper class that generates an OverlayEntry and inserts it into the rootOverlay.

import 'package:flutter/material.dart';

class GlobalSnackbar {
  static OverlayEntry? _currentEntry;

  static void show(BuildContext context, String message) {
    // 1. Remove the old snackbar if it exists
    _currentEntry?.remove();
    _currentEntry = null;

    // 2. Get the highest overlay in the app
    final overlay = Overlay.maybeOf(context, rootOverlay: true);
    if (overlay == null) return;

    // 3. Create the OverlayEntry
    _currentEntry = OverlayEntry(
      builder: (context) {
        return Positioned(
          bottom: 24.0,
          left: 24.0,
          right: 24.0,
          child: Material(
            color: Colors.transparent,
            child: CustomSnackbarWidget(
              message: message,
              onDismiss: () {
                _currentEntry?.remove();
                _currentEntry = null;
              },
            ),
          ),
        );
      },
    );

    // 4. Insert it!
    overlay.insert(_currentEntry!);
  }
}

Step 2: Handle Your Own Animations (CustomSnackbarWidget)

Because we aren't using the built-in SnackBar, we don't get free animations. We need to create a StatefulWidget that animates its own entrance and handles a dismissal timer.

class CustomSnackbarWidget extends StatefulWidget {
  final String message;
  final VoidCallback onDismiss;

  const CustomSnackbarWidget({
    Key? key,
    required this.message,
    required this.onDismiss,
  }) : super(key: key);

  @override
  State<CustomSnackbarWidget> createState() => _CustomSnackbarWidgetState();
}

class _CustomSnackbarWidgetState extends State<CustomSnackbarWidget>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<Offset> _slideAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300),
    );
    
    _slideAnimation = Tween<Offset>(
      begin: const Offset(0, 1), // Start below screen
      end: Offset.zero,
    ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));

    // Animate in
    _controller.forward();

    // Auto-dismiss after 3 seconds
    Future.delayed(const Duration(seconds: 3), dismiss);
  }

  void dismiss() async {
    if (mounted) {
      await _controller.reverse(); // Animate out
      widget.onDismiss();
    }
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SlideTransition(
      position: _slideAnimation,
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
        decoration: BoxDecoration(
          color: Colors.black87,
          borderRadius: BorderRadius.circular(8),
        ),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Expanded(
              child: Text(
                widget.message,
                style: const TextStyle(color: Colors.white),
              ),
            ),
            IconButton(
              icon: const Icon(Icons.close, color: Colors.white),
              onPressed: dismiss,
            ),
          ],
        ),
      ),
    );
  }
}

Step 3: Usage

Now, anywhere in your app—whether inside a regular screen, a bottom sheet, or a dialog—you just call:

GlobalSnackbar.show(context, 'Data saved successfully!');

It will always appear on top of everything else!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment