Overlays in Flutter: Getting Started
Learn Flutter’s own way to display overlays like popUps, modals and dialog boxes with the help of popUpRoutes. By Michael Malak.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Overlays in Flutter: Getting Started
25 mins
- Getting Started
- Setting up the Starter Project
- Understanding Overlays
- Viewing the Pop-up Menu to Sort Notes
- Showing Delete Confirmation Dialog
- Adding a Note as an Overlay
- Creating a Custom PopupRoute
- Using Custom PopupRoute when Adding a Note
- Understanding Overlay Widgets
- Opening Note Details Overlay
- Creating Overlay mixin
- Using Overlay Mixin to Edit a Note
- Removing the Overlay on Native Back Press
- Confirming Before Editing Notes
- Where to Go From Here?
Adding a Note as an Overlay
When you select add icon in the app bar, you want to push SaveNotePage on top of the displayed list of notes as an overlay. Since you want to control the overlay and add transition animations, you’ll create a custom route instead of relying on Flutter’s MaterialPageRoute.
Creating a Custom PopupRoute
In lib/service/router_service/router_service.dart, append the following class at the end of the file:
// 1
class CustomPopupRoute extends PopupRoute {
// 2
CustomPopupRoute({
required this.builder,
RouteSettings? settings,
}) : super(settings: settings);
final WidgetBuilder builder;
// 3
@override
Color get barrierColor => Colors.black54.withAlpha(100);
@override
bool get barrierDismissible => true;
@override
String get barrierLabel => 'customPopupRoute';
// 4
@override
Duration get transitionDuration => const Duration(milliseconds: 300);
// 5
@override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) => builder(context);
// 6
@override
Widget buildTransitions(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return ScaleTransition(
scale: animation,
child: FadeTransition(
opacity: animation,
child: child,
),
);
}
}
Here’s a detailed breakdown of the code above:
- You created your custom route that extends
PopupRoute. - By passing
settings, aRouteSettingsobject tosuperin the constructor, you pass it to the basePopupRouteclass. This ties the custom route to a page in case thesettingsis a subclass ofPage. A page-based route, as opposed to a pageless route, is created fromPage.createRouteduringNavigator.pagesupdates. - When defining a route, you implement barrier-specific overrides.
barrierColoris the color of the barrier between the route and the previous one. In this case, you made it slightly transparent.barrierDismissibleis a Boolean responsible if the route can be dismissed, by clicking outside of its defined bounded box.barrierLabelis the semantic label used for a dismissible barrier. - The
transitionDurationis the animation duration of opening the custom route. - Override the
buildPageto return the passed builder with the newly createdcontext. This builder contains the widget that you want to add as an overlay. - Specify
ScaleTransitionandFadeTransitionas the animation transitions when opening and closing the custom route.
Now that you created a CustomPopupRoute, you’re ready to use it with SaveNotePage screen.
Using Custom PopupRoute when Adding a Note
While you’re at lib/service/router_service/router_service.dart, replace the implementation at # TODO 3: Create then use CustomPopupRoute with:
case SaveNotePage.route:
// 1
return CustomPopupRoute(
// 2
builder: (_) => SaveNotePage(
onNoteSaved: settings.arguments as NoteCallback,
),
// 3
settings: settings,
);
Here’s what you did:
- Switched the return to
CustomPopupRouteinstead ofMaterialPageRoute. Now you’ll be using yourCustomPopupRoutethat you created before. - Returned
SaveNotePagefor thebuilderproperty of the route and passedonNoteSavedas the callback with typeNoteCallBackfrom theRouteSettingsarguments. - CustomPopupRoute takes a
RouteSettingparameter as settings. Provide thesettingsvalue as theRouteSettingsproperty to theCustomPopupRoute.
Build and run; it functions as you’d expect when you click the add icon.
Understanding Overlay Widgets
As previously mentioned, it’s most common to use overlays created by the Navigator. The Navigator will manage the visual appearance of its routes, including PopupRoutes, if they’re an overlay. However, Flutter allows you to create an Overlay directly.
Overlay widget is a StatefulWidget that stacks independent child widgets and allows them to float on top of your widget tree. It manages each of its OverlayEntry children very similarly to how the Stack widget works. You use OverlayState to insert OverlayEntrys into the Overlay widget using the insert and insertAll functions.
Overlay‘s main use case is related to being able to insert widgets on top of the pages in an app, as opposed to Stack, that simply displays a stack of widgets.Opening Note Details Overlay
You want to edit a note as an overlay by tapping on it without pushing the SaveNotePage to the navigation stack. Instead, you’ll use the Overlay widget. First, you’ll create a generic implementation to manage Overlay widget. Then, you’ll use this implementation to display note details as an overlay.
Creating Overlay mixin
You’ll create a shared mixin that will add and remove overlays in Flutter for a cleaner approach.
Mixins are a way to reuse methods or variables among otherwise unrelated classes. To learn more you can check out the Dart Apprentice – Chapter 9: Advanced Classes.Create a new dart file in lib/ui/_shared/mixin/ called overlay_mixin.dart, and add the following code to it:
import 'package:flutter/material.dart';
import '../utils/app_colors.dart';
// 1
mixin OverlayStateMixin<T extends StatefulWidget> on State<T> {
// 2
OverlayEntry? _overlayEntry;
// 3
void removeOverlay() {
_overlayEntry?.remove();
_overlayEntry = null;
}
// 4
Widget _dismissibleOverlay(Widget child) => Stack(
children: [
Positioned.fill(
child: ColoredBox(
color: AppColors.barrierColor,
child: GestureDetector(
onTap: removeOverlay,
),
),
),
child,
],
);
// 5
void _insertOverlay(Widget child) {
// 6
_overlayEntry = OverlayEntry(
builder: (_) => _dismissibleOverlay(child),
);
// 7
Overlay.of(context)?.insert(_overlayEntry!);
}
}
This is what the code does:
- Creates a new
mixinnamed OverlaysStateMixin that you’ll use with theStateclass ofStatefulWidgets. - Adds a single private nullable variable
OverlayEntry. You’ll use this variable to manage the overlay entries. In this mixin, you’ll only manage one overlay. - A void function named
removeOverlaythat removes the overlayEntry by calling_overlayEntry?.remove()function assigned in theOverlayEntrybefore setting it to null. -
_dismissibleOverlayis a private function that allows for callingremoveOverlay()when clicking outside the child boundaries. - A private function you use to display a
Widgetas an overlay. - You assign
OverlayEntrywith the dismissablechild. -
Overlay.of(context)yieldsOverlayStateobject which you use to insert your newly assignedOverlayEntryobject.
Now that you can insert and remove an overlay using the mixin, you want to expose a public function to toggle it. Add the following to OverlayStateMixin:
// 1
bool get isOverlayShown => _overlayEntry != null;
// 2
void toggleOverlay(Widget child) =>
isOverlayShown ? removeOverlay() : _insertOverlay(child);
Here you:
- Implement a getter method to check if the
OverlayEntryis visible by checking if it was not null. - Expose a public function to toggle viewing the single
_overlayEntry.
Since you’ll use OverlayStateMixin with StatefulWidgets, you can override some methods to remove the overlay in certain senarios. You add the following to OverlayStateMixin:
@override
void dispose() {
removeOverlay();
super.dispose();
}
@override
void didChangeDependencies() {
removeOverlay();
super.didChangeDependencies();
}
You override state functions dispose and didChangeDependencies to remove the displayed overlay when they trigger.
Now that you created a shared mixin to manage a single OverlayEntry, you’ll use it to display an overlay that allows editing a note item.
