<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kamali20072002</title>
    <description>The latest articles on DEV Community by Kamali20072002 (@kamali20072002).</description>
    <link>https://dev.to/kamali20072002</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3814675%2F29ecd453-3482-4b33-bc57-69e25416c641.png</url>
      <title>DEV Community: Kamali20072002</title>
      <link>https://dev.to/kamali20072002</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kamali20072002"/>
    <language>en</language>
    <item>
      <title>Flutter custom notifications</title>
      <dc:creator>Kamali20072002</dc:creator>
      <pubDate>Mon, 09 Mar 2026 11:47:06 +0000</pubDate>
      <link>https://dev.to/kamali20072002/flutter-custom-notifications-35ln</link>
      <guid>https://dev.to/kamali20072002/flutter-custom-notifications-35ln</guid>
      <description>&lt;p&gt;Every Flutter app needs notifications. Whether it's a success alert after a payment, an error toast when upload fails, or a warning banner before a session expires — you need them everywhere.&lt;br&gt;
Most developers reach for ScaffoldMessenger.showSnackBar() and call it a day. But after building several production apps, I found this approach too limiting. So I built a proper reusable notification system from scratch. Here's everything I learned.&lt;/p&gt;

&lt;p&gt;The Problem with ScaffoldMessenger&lt;br&gt;
ScaffoldMessenger works for basic cases but has real limitations:&lt;/p&gt;

&lt;p&gt;Tied to Scaffold context — breaks in certain widget trees&lt;br&gt;
Very limited customization (position, style, animations)&lt;br&gt;
Can't easily show dialogs AND toasts simultaneously&lt;br&gt;
No built-in dark mode support&lt;br&gt;
Animations are not customizable&lt;/p&gt;

&lt;p&gt;The Solution — Flutter's Overlay API&lt;br&gt;
Overlay is Flutter's built-in system for showing widgets above everything else — it's literally how tooltips, dropdowns, and route transitions work internally.&lt;br&gt;
Here's the core pattern:&lt;br&gt;
dartvoid showCustomToast(BuildContext context) {&lt;br&gt;
  final overlay = Overlay.of(context);&lt;br&gt;
  late OverlayEntry entry;&lt;/p&gt;

&lt;p&gt;entry = OverlayEntry(&lt;br&gt;
    builder: (_) =&amp;gt; Positioned(&lt;br&gt;
      bottom: 40,&lt;br&gt;
      left: 16,&lt;br&gt;
      right: 16,&lt;br&gt;
      child: YourToastWidget(&lt;br&gt;
        onDismiss: () =&amp;gt; entry.remove(),&lt;br&gt;
      ),&lt;br&gt;
    ),&lt;br&gt;
  );&lt;/p&gt;

&lt;p&gt;overlay.insert(entry);&lt;/p&gt;

&lt;p&gt;// Auto dismiss after 3 seconds&lt;br&gt;
  Future.delayed(const Duration(seconds: 3), () {&lt;br&gt;
    entry.remove();&lt;br&gt;
  });&lt;br&gt;
}&lt;br&gt;
Key advantage: this works from anywhere in your widget tree — no Scaffold required.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Building the Toast
A good toast needs:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Smooth entrance/exit animation&lt;br&gt;
Auto dismiss with manual dismiss option&lt;br&gt;
Dark mode awareness&lt;/p&gt;

&lt;p&gt;dartclass ToastWidget extends StatefulWidget {&lt;br&gt;
  final String message;&lt;br&gt;
  final String? title;&lt;br&gt;
  final VoidCallback onDismiss;&lt;/p&gt;

&lt;p&gt;const ToastWidget({&lt;br&gt;
    super.key,&lt;br&gt;
    required this.message,&lt;br&gt;
    this.title,&lt;br&gt;
    required this.onDismiss,&lt;br&gt;
  });&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/override"&gt;@override&lt;/a&gt;&lt;br&gt;
  State createState() =&amp;gt; _ToastWidgetState();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;class _ToastWidgetState extends State&lt;br&gt;
    with SingleTickerProviderStateMixin {&lt;br&gt;
  late AnimationController _controller;&lt;br&gt;
  late Animation _slide;&lt;br&gt;
  late Animation _opacity;&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/override"&gt;@override&lt;/a&gt;&lt;br&gt;
  void initState() {&lt;br&gt;
    super.initState();&lt;br&gt;
    _controller = AnimationController(&lt;br&gt;
      vsync: this,&lt;br&gt;
      duration: const Duration(milliseconds: 380),&lt;br&gt;
    );&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Slide up from bottom
_slide = Tween&amp;lt;Offset&amp;gt;(
  begin: const Offset(0, 1),
  end: Offset.zero,
).animate(CurvedAnimation(
  parent: _controller,
  curve: Curves.elasticOut, // 👈 this creates the satisfying bounce
));

_opacity = Tween&amp;lt;double&amp;gt;(begin: 0, end: 1)
    .animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeOut,
    ));

_controller.forward();

// Auto dismiss
Future.delayed(const Duration(seconds: 3), _dismiss);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;void _dismiss() async {&lt;br&gt;
    if (!mounted) return;&lt;br&gt;
    await _controller.reverse();&lt;br&gt;
    widget.onDismiss();&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/override"&gt;@override&lt;/a&gt;&lt;br&gt;
  void dispose() {&lt;br&gt;
    _controller.dispose();&lt;br&gt;
    super.dispose();&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/override"&gt;@override&lt;/a&gt;&lt;br&gt;
  Widget build(BuildContext context) {&lt;br&gt;
    return SlideTransition(&lt;br&gt;
      position: _slide,&lt;br&gt;
      child: FadeTransition(&lt;br&gt;
        opacity: _opacity,&lt;br&gt;
        child: Material(&lt;br&gt;
          color: Colors.transparent,&lt;br&gt;
          child: Container(&lt;br&gt;
            padding: const EdgeInsets.all(14),&lt;br&gt;
            decoration: BoxDecoration(&lt;br&gt;
              color: Colors.white,&lt;br&gt;
              borderRadius: BorderRadius.circular(14),&lt;br&gt;
              boxShadow: [&lt;br&gt;
                BoxShadow(&lt;br&gt;
                  color: Colors.black.withOpacity(0.1),&lt;br&gt;
                  blurRadius: 20,&lt;br&gt;
                  offset: const Offset(0, 4),&lt;br&gt;
                ),&lt;br&gt;
              ],&lt;br&gt;
            ),&lt;br&gt;
            child: Text(widget.message),&lt;br&gt;
          ),&lt;br&gt;
        ),&lt;br&gt;
      ),&lt;br&gt;
    );&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Dark Mode — Auto Detected, Zero Extra Code
The cleanest approach reads directly from Flutter's theme:
dartbool isDark = Theme.of(context).brightness == Brightness.dark;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Color surface = isDark&lt;br&gt;
    ? const Color(0xFF1C1C1E)   // dark surface&lt;br&gt;
    : Colors.white;              // light surface&lt;/p&gt;

&lt;p&gt;Color textPrimary = isDark&lt;br&gt;
    ? const Color(0xFFF9FAFB)   // near white&lt;br&gt;
    : const Color(0xFF1A1A1A);  // near black&lt;/p&gt;

&lt;p&gt;Color textSecondary = isDark&lt;br&gt;
    ? const Color(0xFF9CA3AF)   // muted light&lt;br&gt;
    : const Color(0xFF6B7280);  // muted dark&lt;br&gt;
Now your notification automatically adapts when the user toggles dark mode — no params needed.&lt;br&gt;
For alert type colors, define light/dark pairs:&lt;br&gt;
dart// Success colors&lt;br&gt;
Color successLight = const Color(0xFF1DB954);&lt;br&gt;
Color successDark  = const Color(0xFF4ADE80);&lt;br&gt;
Color successBgLight = const Color(0xFFE8FAF0);&lt;br&gt;
Color successBgDark  = const Color(0xFF052E16);&lt;/p&gt;

&lt;p&gt;Color accent = isDark ? successDark : successLight;&lt;br&gt;
Color iconBg = isDark ? successBgDark : successBgLight;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Animation Styles&lt;br&gt;
One system, five different feels. Here's how to implement each:&lt;br&gt;
Bounce (Most satisfying)&lt;br&gt;
dartScaleTransition(&lt;br&gt;
scale: Tween(begin: 0.6, end: 1.0).animate(&lt;br&gt;
CurvedAnimation(&lt;br&gt;
  parent: controller,&lt;br&gt;
  curve: Curves.elasticOut, // 👈 magic curve&lt;br&gt;
),&lt;br&gt;
),&lt;br&gt;
child: FadeTransition(opacity: animation, child: child),&lt;br&gt;
)&lt;br&gt;
Flip (3D card effect)&lt;br&gt;
dartAnimatedBuilder(&lt;br&gt;
animation: controller,&lt;br&gt;
builder: (&lt;em&gt;, c) =&amp;gt; Transform(&lt;br&gt;
alignment: Alignment.center,&lt;br&gt;
transform: Matrix4.identity()&lt;br&gt;
  ..setEntry(3, 2, 0.001)  // perspective&lt;br&gt;
  ..rotateX((1 - controller.value) * 3.14159 / 2),&lt;br&gt;
child: FadeTransition(opacity: animation, child: c),&lt;br&gt;
),&lt;br&gt;
child: child,&lt;br&gt;
)&lt;br&gt;
Blur (Soft focus-in)&lt;br&gt;
dart// Use ImageFiltered for blur effect during transition&lt;br&gt;
AnimatedBuilder(&lt;br&gt;
animation: controller,&lt;br&gt;
builder: (&lt;/em&gt;, child) {&lt;br&gt;
double blurAmount = (1 - controller.value) * 10;&lt;br&gt;
return ImageFiltered(&lt;br&gt;
  imageFilter: ImageFilter.blur(&lt;br&gt;
    sigmaX: blurAmount,&lt;br&gt;
    sigmaY: blurAmount,&lt;br&gt;
  ),&lt;br&gt;
  child: Opacity(opacity: controller.value, child: child),&lt;br&gt;
);&lt;br&gt;
},&lt;br&gt;
child: child,&lt;br&gt;
)&lt;br&gt;
Slide&lt;br&gt;
dartSlideTransition(&lt;br&gt;
position: Tween(&lt;br&gt;
begin: const Offset(0, -0.3),&lt;br&gt;
end: Offset.zero,&lt;br&gt;
).animate(CurvedAnimation(&lt;br&gt;
parent: controller,&lt;br&gt;
curve: Curves.easeOutCubic,&lt;br&gt;
)),&lt;br&gt;
child: FadeTransition(opacity: animation, child: child),&lt;br&gt;
)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bottom Sheet Alerts&lt;br&gt;
The key insight for bottom sheets is safe area padding. Without it, content gets hidden behind the home bar on modern phones:&lt;br&gt;
dartWidget build(BuildContext context) {&lt;br&gt;
// 👇 ALWAYS get this&lt;br&gt;
final bottomPad = MediaQuery.of(context).padding.bottom;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;return Container(&lt;br&gt;
    decoration: BoxDecoration(&lt;br&gt;
      color: surfaceColor,&lt;br&gt;
      borderRadius: const BorderRadius.vertical(&lt;br&gt;
        top: Radius.circular(24),&lt;br&gt;
      ),&lt;br&gt;
    ),&lt;br&gt;
    child: Padding(&lt;br&gt;
      padding: EdgeInsets.only(&lt;br&gt;
        left: 24,&lt;br&gt;
        right: 24,&lt;br&gt;
        top: 20,&lt;br&gt;
        bottom: bottomPad + 16, // 👈 safe area aware&lt;br&gt;
      ),&lt;br&gt;
      child: Column(&lt;br&gt;
        mainAxisSize: MainAxisSize.min,&lt;br&gt;
        children: [&lt;br&gt;
          // drag handle&lt;br&gt;
          Container(&lt;br&gt;
            width: 36, height: 4,&lt;br&gt;
            decoration: BoxDecoration(&lt;br&gt;
              color: Colors.black.withOpacity(0.1),&lt;br&gt;
              borderRadius: BorderRadius.circular(2),&lt;br&gt;
            ),&lt;br&gt;
          ),&lt;br&gt;
          const SizedBox(height: 16),&lt;br&gt;
          // your content&lt;br&gt;
        ],&lt;br&gt;
      ),&lt;br&gt;
    ),&lt;br&gt;
  );&lt;br&gt;
}&lt;br&gt;
For destructive actions, use a subtle red background instead of a filled red button — it's less alarming but still communicates danger:&lt;br&gt;
dart// ✅ Better destructive button&lt;br&gt;
Container(&lt;br&gt;
  padding: const EdgeInsets.symmetric(vertical: 14),&lt;br&gt;
  decoration: BoxDecoration(&lt;br&gt;
    // subtle red bg instead of solid red&lt;br&gt;
    color: Colors.red.withOpacity(0.08),&lt;br&gt;
    borderRadius: BorderRadius.circular(12),&lt;br&gt;
  ),&lt;br&gt;
  child: Text(&lt;br&gt;
    'Delete Account',&lt;br&gt;
    style: TextStyle(&lt;br&gt;
      color: Colors.red,&lt;br&gt;
      fontWeight: FontWeight.w700,&lt;br&gt;
    ),&lt;br&gt;
  ),&lt;br&gt;
)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Full Architecture
Here's how I structured it into a reusable system:
lib/
└── notifications/
├── nk_type.dart      ← NotifType enum + color configs
├── nk_theme.dart     ← Global theme (borderRadius, font, duration)
├── nk_dialog.dart    ← Popup alerts
├── nk_toast.dart     ← Floating toasts
├── nk_banner.dart    ← Full-width banners
└── nk_sheet.dart     ← Bottom sheet alerts
Each component follows the same pattern:
dartclass MyToast {
static OverlayEntry? _current;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;static void show(BuildContext context, {required String message}) {&lt;br&gt;
    // Remove existing toast first&lt;br&gt;
    _current?.remove();&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;final overlay = Overlay.of(context);
late OverlayEntry entry;

entry = OverlayEntry(
  builder: (_) =&amp;gt; ToastWidget(
    message: message,
    onDismiss: () {
      entry.remove();
      if (_current == entry) _current = null;
    },
  ),
);

_current = entry;
overlay.insert(entry);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;static void dismiss() {&lt;br&gt;
    _current?.remove();&lt;br&gt;
    _current = null;&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
This static _current pattern ensures only one toast shows at a time — the new one always replaces the old one.&lt;/p&gt;

&lt;p&gt;Key Takeaways&lt;/p&gt;

&lt;p&gt;Use Overlay instead of ScaffoldMessenger for full customization&lt;br&gt;
Read brightness from Theme for automatic dark mode — no extra params&lt;br&gt;
Curves.elasticOut makes bounce animations feel natural and satisfying&lt;br&gt;
Always add MediaQuery bottom padding in bottom sheets for safe area&lt;br&gt;
Matrix4 perspective (setEntry(3, 2, 0.001)) is the key to 3D flip effects&lt;br&gt;
Static OverlayEntry lets you manage dismiss/replace behavior cleanly&lt;/p&gt;

&lt;p&gt;What I Packaged This Into&lt;br&gt;
I turned this entire system into NotiX Pro — a Flutter package with all 4 components, dark mode, 5 animation styles, and a commercial license.&lt;br&gt;
Early bird pricing at $2 👉 kamaliv.gumroad.com/l/siarxs&lt;br&gt;
A free version (notify_kit) is also on pub.dev with the basic components.&lt;/p&gt;

&lt;p&gt;Have questions about the implementation? Drop them in the comments — I'll reply to every one! 👇&lt;br&gt;
What notification patterns do you use in your Flutter apps?&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>mobileapp</category>
      <category>dart</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
