overlay_layers 3.0.0
overlay_layers: ^3.0.0 copied to clipboard
A flexible overlay system for Flutter with native Overlay integration. Supports popups, modals, toasts, and dialogs with type-safe data passing.
overlay_layers #
A flexible overlay system for Flutter with support for popups, modals, toasts, and dialogs.
Built on Flutter's native Overlay system with type-safe data passing and lifecycle management.
Features #
- Native Flutter integration: Uses Flutter's Overlay API under the hood
- No wrapper required: Works immediately without wrapping your app
- Type-safe: Full type safety with generics
- Future-extensible: Designed to support popups, modals, toasts, and dialogs
- Simple API: Easy-to-use controllers and data contexts
- Lifecycle callbacks:
onDataChangeandonClosehooks - Animated widgets: Built-in animation support
- Positioned popups: Popup positioning relative to target widgets
Installation #
Add this to your package's pubspec.yaml file:
dependencies:
overlay_layers: ^3.0.0
Quick Start #
No setup required! Just import and use:
import 'package:overlay_layers/overlay_layers.dart';
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
PopupController.of(context).open(
builder: (context) => MyPopupWidget(),
);
},
child: Text('Open Popup'),
);
}
}
That's it! The package uses a global singleton manager automatically.
Usage #
1. Open a popup #
import 'package:overlay_layers/overlay_layers.dart';
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final popupController = PopupController.of(context);
return ElevatedButton(
onPressed: () {
popupController.open(
builder: (context) => MyPopupWidget(),
options: OverlayCreateOptions(
initialData: {'message': 'Hello!'},
onClose: (data) => print('Closed with: $data'),
),
);
},
child: Text('Open Popup'),
);
}
}
3. Access popup data from within a popup #
class MyPopupWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final popup = PopupDataContext.of<Map<String, dynamic>>(context);
return OverlayScaffold(
onBackdropTap: () => popup.close(),
child: AnimatedOverlay(
child: Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(popup.data['message']),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {
popup.updatePopupData({'message': 'Updated!'});
},
child: Text('Update'),
),
ElevatedButton(
onPressed: () => popup.close(),
child: Text('Close'),
),
],
),
),
),
);
}
}
2. Work with modals #
Modals are similar to popups but with one key difference: only one modal can be displayed at a time. Opening a new modal automatically closes the previous one (interchangeable behavior).
import 'package:overlay_layers/overlay_layers.dart';
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final modalController = ModalController.of(context);
return ElevatedButton(
onPressed: () {
// Opening this modal closes any existing modal
modalController.open(
builder: (context) => MyModalWidget(),
options: OverlayCreateOptions(
initialData: {'title': 'Welcome'},
onClose: (data) => print('Modal closed'),
),
);
},
child: Text('Open Modal'),
);
}
}
3. Access modal data from within #
class MyModalWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final modal = ModalDataContext.of<Map<String, dynamic>>(context);
return OverlayScaffold(
onBackdropTap: () => modal.close(),
child: AnimatedOverlay(
child: Container(
padding: EdgeInsets.all(32),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(modal.data['title']),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {
modal.updateModalData({'title': 'Updated!'});
},
child: Text('Update'),
),
ElevatedButton(
onPressed: () => modal.close(),
child: Text('Close'),
),
],
),
),
),
);
}
}
API Reference #
PopupController #
Controller for managing popups.
final controller = PopupController.of(context);
// Open a popup
final id = controller.open(
builder: (context) => MyPopupWidget(),
options: OverlayCreateOptions(
initialData: {},
onDataChange: (data) => print(data),
onClose: (data) => print('Closed'),
),
);
// Close specific popup
controller.close(id);
// Close topmost popup
controller.close();
// Close all popups
controller.closeAll();
// Update popup data from outside
controller.updatePopupData(id, {'key': 'value'});
// Get all active popups
final popups = controller.popups;
PopupDataContext #
Access and update popup data from within a popup widget.
final popup = PopupDataContext.of<MyDataType>(context);
// Access data
final data = popup.data;
// Update data (merges with existing)
popup.updatePopupData({'key': 'value'});
// Close popup
popup.close();
// Close with final data
popup.close({'finalKey': 'finalValue'});
ModalController #
Controller for managing modals. Interchangeable behavior: Only one modal can be displayed at a time.
final controller = ModalController.of(context);
// Open a modal (closes any existing modal automatically)
controller.open(
builder: (context) => MyModalWidget(),
options: OverlayCreateOptions(
initialData: {},
onDataChange: (data) => print(data),
onClose: (data) => print('Closed'),
),
);
// Close current modal
controller.close();
// Update current modal data
controller.updateModalData({'key': 'value'});
// Get current modal (if any)
final currentModal = controller.modal;
ModalDataContext #
Access and update modal data from within a modal widget.
final modal = ModalDataContext.of<MyDataType>(context);
// Access data
final data = modal.data;
// Update data (merges with existing)
modal.updateModalData({'key': 'value'});
// Close modal
modal.close();
// Close with final data
modal.close({'finalKey': 'finalValue'});
OverlayManager #
Access the global singleton manager for advanced use cases:
// Access all active overlays
final overlays = OverlayManager.instance.overlays;
// Get overlays by type
final popups = OverlayManager.instance.getOverlaysByType(OverlayType.popup);
// Or use a custom manager instance (advanced)
final customManager = OverlayManager.custom();
PopupController.withManager(context, customManager).open(...);
Helper Widgets #
OverlayScaffold
Generic overlay scaffold with backdrop and positioning. Works with all overlay types.
OverlayScaffold(
backdropColor: Colors.black.withOpacity(0.5),
onBackdropTap: () => context.close(),
alignment: Alignment.center,
child: MyContent(),
)
AnimatedOverlay
Flexible animated wrapper with configurable fade, scale, and slide animations.
// Default: fade + scale
AnimatedOverlay(
duration: Duration(milliseconds: 250),
curve: Curves.easeOut,
child: MyContent(),
)
// Slide from bottom (like bottom sheet)
AnimatedOverlay(
slideFrom: Offset(0, 1),
child: MyContent(),
)
// Slide from left with fade, no scale
AnimatedOverlay(
slideFrom: Offset(-1, 0),
scale: false,
child: MyContent(),
)
// Custom scale range
AnimatedOverlay(
scaleBegin: 0.8,
scaleEnd: 1.0,
child: MyContent(),
)
PositionedOverlay
Position overlay relative to a target widget. Perfect for tooltips, dropdowns, and context menus.
PositionedOverlay(
targetRect: targetRect,
position: OverlayPosition.bottom,
spacing: 8.0,
child: MyContent(),
)
Overlay Types #
The package currently supports:
- Popups: Multiple popups can be displayed simultaneously
- Modals: Interchangeable - only one modal at a time (opening a new modal closes the previous)
Future Features #
Additional overlay types planned:
- Tooltip: Positioned tooltips using
PositionedOverlay - Toast: Temporary notifications
- Dialog: Alert-style overlays
Architecture #
The package is built on Flutter's native Overlay system with a centralized OverlayManager for state management. Each overlay type (popup, toast, modal, dialog) has its own controller but shares the same underlying infrastructure.
How it works:
- Controllers access Flutter's native
Overlay.of(context) OverlayManagercreates and managesOverlayEntryinstances- Each entry wraps your widget with type-safe data context
- Updates trigger
markNeedsBuild()on specific entries
Benefits:
- No wrapper widget required (uses existing Overlay from MaterialApp/CupertinoApp)
- Native Flutter integration and performance
- Consistent API across overlay types
- Proper z-index management
- Type-safe data passing with generics
- Unified lifecycle management
- Tree-shakable widgets (v3.0.0+): unused widgets don't bloat your bundle
License #
MIT