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
, aRouteSettings
object tosuper
in the constructor, you pass it to the basePopupRoute
class. This ties the custom route to a page in case thesettings
is a subclass ofPage
. A page-based route, as opposed to a pageless route, is created fromPage.createRoute
duringNavigator.pages
updates. - When defining a route, you implement barrier-specific overrides.
barrierColor
is the color of the barrier between the route and the previous one. In this case, you made it slightly transparent.barrierDismissible
is a Boolean responsible if the route can be dismissed, by clicking outside of its defined bounded box.barrierLabel
is the semantic label used for a dismissible barrier. - The
transitionDuration
is the animation duration of opening the custom route. - Override the
buildPage
to return the passed builder with the newly createdcontext
. This builder contains the widget that you want to add as an overlay. - Specify
ScaleTransition
andFadeTransition
as 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
CustomPopupRoute
instead ofMaterialPageRoute
. Now you’ll be using yourCustomPopupRoute
that you created before. - Returned
SaveNotePage
for thebuilder
property of the route and passedonNoteSaved
as the callback with typeNoteCallBack
from theRouteSettings
arguments. - CustomPopupRoute takes a
RouteSetting
parameter as settings. Provide thesettings
value as theRouteSettings
property 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 PopupRoute
s, 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 OverlayEntry
s 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
mixin
named OverlaysStateMixin that you’ll use with theState
class ofStatefulWidget
s. - 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
removeOverlay
that removes the overlayEntry by calling_overlayEntry?.remove()
function assigned in theOverlayEntry
before setting it to null. -
_dismissibleOverlay
is a private function that allows for callingremoveOverlay()
when clicking outside the child boundaries. - A private function you use to display a
Widget
as an overlay. - You assign
OverlayEntry
with the dismissablechild
. -
Overlay.of(context)
yieldsOverlayState
object which you use to insert your newly assignedOverlayEntry
object.
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
OverlayEntry
is visible by checking if it was not null. - Expose a public function to toggle viewing the single
_overlayEntry
.
Since you’ll use OverlayStateMixin
with StatefulWidget
s, 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.