Implementing overlays in Flutter
Tags: flutter, webdev
Published: 2022-06-03 18:17:11

**Written by [Murtaza Sulaihi](https://blog.logrocket.com/author/murtazasulaihi/)✏️**

Let's assume that you have a widget in your application's UI, and you want to place another floating widget on top of it. Perhaps that widget needs to be rotated or has additional transformations. How do you display the widget and transform its information to the equivalent UI in the application? 

The Overlay in Flutter makes it easy to create visual elements on top of other widgets by adding them to the Overlay's stack. OverlayEntry is used to insert a widget into the Overlay, then Positioned or AnimatedPositioned is used to determine where it will enter within the Overlay. This is useful when you require an item to appear on top of another widget (similar to the Stack widget) without modifying your entire codebase. 

Using an Overlay widget in Flutter might seem a little intuitive but can be challenging at times. First, the overlay entry is inserted using a callback method. And it would be best if you also remembered to remove the `Entry` using the reference and not the context of the Overlay. 

We will be looking at three different examples where we can use an overlay widget to make the application's UI more user friendly.

*   [Example 1: Displaying an error message on a sign-up screen](#example-1-displaying-an-error-message-on-a-sign-up-screen)
*   [Example 2: A FloatingActionButton showing three other buttons](#example-2-a-floatingactionbutton-showing-three-other-buttons)
*   [Example 3: The Overlay widget follows while scrolling](#example-3-the-overlay-widget-follows-while-scrolling)

## Example 1: Displaying an error message on a sign-up screen <a name="example-1-displaying-an-error-message-on-a-sign-up-screen"></a>

![Error message left](https://blog.logrocket.com/wp-content/uploads/2022/05/displaying-error-message-left.png)

![Error message right](https://blog.logrocket.com/wp-content/uploads/2022/05/displaying-error-message-right.png)

This is a simple sign-up screen that you usually see on any modern-day application. It contains four TextFormFields for a full name, email address, password, and confirmation password. 

Since it is a TextFormField, we have validators for each of them. The full name must be more than two characters, it must be a proper email address, the password should be more than six characters, and the confirmed password should match the password field. When the user clicks on the **Submit** button, it verifies all the above fields, and if there is an error, the suffix icon's color changes to red, alerting the user that TextFormField does not match its relevant requirement. 

When the user clicks on the red icon, an Overlay widget displays for about three seconds, and it disappears. 

Visually, an Overlay can be mistaken for an AlertDialog or a Snackbar. An Overlay widget displays similarly, but it gives more power for customization to the developer to program it according to the application’s UI requirement. 

Let’s dive into the code and see how an Overlay widget will be displayed when an error occurs and the user clicks on it. 

We need to create a [stateful widget](https://blog.logrocket.com/difference-between-stateless-stateful-widgets-flutter/) since we will be running some animations along with the Overlay widget. 

We have added four `TextEditingController`s for name, email, password, and confirm password:

```dart
TextEditingController nameController = TextEditingController();

TextEditingController emailController = TextEditingController();

TextEditingController passwordController = TextEditingController();

TextEditingController confirmPassController = TextEditingController();
```

Again four `Color` variables for name, email, password, and confirm password to switch between red and gray colors:

```dart
Color? nameColor, emailColor, passColor, confirmPassColor;
```

Next, we have an `AnimationController` and an `Animation` object:

```dart
AnimationController? animationController;
Animation<double>? animation;
```

We are overriding `initState` and initializing `AnimationController` and the `Animation` object we just created:

```dart
@override
 void initState() {
   super.initState();
   animationController =
       AnimationController(vsync: this, duration: const Duration(seconds: 1));
   animation =
       CurveTween(curve: Curves.fastOutSlowIn).animate(animationController!);
 }
```

The primary function that we need to create will show the Overlay widget. Therefore, we are creating the `_showOverlay` function, which we will call when the user clicks on the suffix icon inside the text field when an error occurs. In the `_showOverlay` function, we have declared and initialized `OverlayState` and `OverlayEntry` objects.

### What is an `OverlayState`?

`OverlayState` is the current state of the overlay, which uses `OverlayEntry`, `Entries` using an insert, or `insertAll` functions to insert.

#### `OverlayState` constructor

1.  `debugIsVisible`: Returns a Boolean value to check whether the given `OverlayEntry` is visible or not
2.  `insert`: Inserts the given `OverlayEntry` inside the overlay
3.  `insertAll`: Takes a list of `OverlayEntries` and inserts all of them inside the overlay
4.  `rearrange`: Removes and re-inserts all the entries according to the given order in the list of `OverlayEntries`

### What is an `OverlayEntry`?

An overlay entry is a place inside an overlay that contains a widget. With the help of a Positioned or AnimatedPositioned widget, `Entry` positions itself within the overlay using a `Stack` layout.

#### `OverlayEntry` constructor

1.  `builder`: Takes in a builder widget
2.  `opaque`: Returns a Boolean value to decide whether the `Entry` occludes the entire overlay or not
3.  `maintainState`: Takes a Boolean value. It forcefully builds an occluded `Entry` below an opaque `Entry` if it is `true`

Below is the code for the `_showOverlay` function:

```dart
void _showOverlay(BuildContext context, {required String text}) async {
   OverlayState? overlayState = Overlay.of(context);
   OverlayEntry overlayEntry;
   overlayEntry = OverlayEntry(builder: (context) {
     return Positioned(
       left: MediaQuery.of(context).size.width * 0.1,
       top: MediaQuery.of(context).size.height * 0.80,
       child: ClipRRect(
         borderRadius: BorderRadius.circular(10),
         child: Material(
           child: FadeTransition(
             opacity: animation!,
             child: Container(
               alignment: Alignment.center,
               color: Colors.grey.shade200,
               padding:
                   EdgeInsets.all(MediaQuery.of(context).size.height * 0.02),
               width: MediaQuery.of(context).size.width * 0.8,
               height: MediaQuery.of(context).size.height * 0.06,
               child: Text(
                 text,
                 style: const TextStyle(color: Colors.black),
               ),
             ),
           ),
         ),
       ),
     );
   });
   animationController!.addListener(() {
     overlayState!.setState(() {});
   });
   // inserting overlay entry
   overlayState!.insert(overlayEntry);
   animationController!.forward();
   await Future.delayed(const Duration(seconds: 3))
       .whenComplete(() => animationController!.reverse())
       // removing overlay entry after stipulated time.
       .whenComplete(() => overlayEntry.remove());
 }
```

After initializing the `overlayEntry`, we return a `Positioned` widget inside the `builder` method to position the `Overlay` widget on the screen. Depending on the application's design, it can be placed and shown anywhere on the mobile screen. 

Next, we added a suffix icon button inside the `TextFormField` and called the `_showOverlay` function inside it. 

The `validator` property of `TextFormField` has conditions based on how the suffix icon changes from gray to red and vice versa if there is an error:

```dart
TextFormField(
                 controller: nameController,
                 keyboardType: TextInputType.name,
                 textInputAction: TextInputAction.next,
                 textCapitalization: TextCapitalization.words,
                 validator: (String? value) {
                   if (value == null || value.trim().isEmpty) {
                     nameColor = Colors.red;
                   }
                   if (value.toString().length <= 2) {
                     nameColor = Colors.red;
                   } else {
                     nameColor = Colors.grey;
                   }
                   return null;
                 },
                 onSaved: (String? value) {
                   _name = value;
                 },
                 decoration: kTextInputDecoration.copyWith(
                     labelText: 'Full Name',
                     prefixIcon: const Icon(Icons.person),
                     suffixIcon: IconButton(
                         padding: EdgeInsets.zero,
                         onPressed: () {
                           _showOverlay(context,
                               text:
                                   'Name should have more than 2 characters');
                         },
                         icon: Icon(Icons.info, color: nameColor
                             //change icon color according to form validation
                             ))),
               ),
```

Lastly, we created a `submitForm` method that validates the `TextFormField`s and saves the form, and we will call it inside the `onPressed` function of the `Submit` button:

```dart
void _submitForm() {
   setState(() {
     _autoValidateMode = AutovalidateMode.always;
   });
   final form = _formKey.currentState;
   if (form == null || !form.validate()) return;

   form.save();

   Fluttertoast.showToast(msg: _name.toString() + _email.toString());
 }
```

Calling the `_submitForm` method inside `onPressed` of `ElevatedButton`, we get this:

```dart
ElevatedButton(
                   onPressed: () {
                     _submitForm();
                   },
                   style: ElevatedButton.styleFrom(
                       padding: const EdgeInsets.all(10)),
                   child: const Text(
                     'Submit',
                     style: TextStyle(fontSize: 20),
                   )),
```

![Overlay entry left](https://blog.logrocket.com/wp-content/uploads/2022/05/overlayentry-left.gif)

![Overlay entry right](https://blog.logrocket.com/wp-content/uploads/2022/05/overlayentry-right.gif)

The full code is available on the [GitHub repository](https://github.com/timelessfusionapps/flutter_Overlay_SignupScreen).

## Example 2: A FloatingActionButton showing three other buttons <a name="example-2-a-floatingactionbutton-showing-three-other-buttons"></a>

![Floating action button left](https://blog.logrocket.com/wp-content/uploads/2022/05/floatingactionbutton-left.png)

![Floating action button right](https://blog.logrocket.com/wp-content/uploads/2022/05/floatingactionbutton-right.png)

First, create all the necessary objects and variables that we will use in this example. To start, we have an `AnimationController` and an empty `List` for animation. Then, we have a list of icons containing three icons and a list of colors that include three different colors associated with the icons:

```dart
AnimationController? animationController;
 List animation = [];
 List icons = [Icons.home, Icons.settings, Icons.location_city];
 List colors = [Colors.green, Colors.blueGrey, Colors.purple];
 OverlayEntry? overlayEntry;
 GlobalKey globalKey = GlobalKey();
```

Next, we have an `OverlayEntry` object, and we have also initialized the `GlobalKey` variable that will be attached to the `FloatingActionButton`'s key property. 

The main difference between the above and the `_showOverlay` function here is the `RenderBox` and `Offset` widget. The `RenderBox` and `Offset` widget and the `Positioned` widget position the three small icons above the main [`FloatingActionButton`](https://blog.logrocket.com/flutter-floatingactionbutton-a-complete-tutorial-with-examples/). When the user clicks the main `FloatingActionButton`, the three small icons open upward and close in a downward trend after the stipulated time mentioned in the animation:

```dart
_showOverLay() async {
   RenderBox? renderBox =
       globalKey.currentContext!.findRenderObject() as RenderBox?;
   Offset offset = renderBox!.localToGlobal(Offset.zero);

   OverlayState? overlayState = Overlay.of(context);
   overlayEntry = OverlayEntry(
     builder: (context) => Positioned(
       left: offset.dx,
       bottom: renderBox.size.height + 16,
       child: Column(
         mainAxisSize: MainAxisSize.min,
         mainAxisAlignment: MainAxisAlignment.center,
         children: [
           for (int i = 0; i < animation.length; i++)
             ScaleTransition(
               scale: animation[i],
               child: FloatingActionButton(
                 onPressed: () {
                   Fluttertoast.showToast(msg: 'Icon Button Pressed');
                 },
                 child: Icon(
                   icons[i],
                 ),
                 backgroundColor: colors[i],
                 mini: true,
               ),
             )
         ],
       ),
     ),
   );
   animationController!.addListener(() {
     overlayState!.setState(() {});
   });
   animationController!.forward();
   overlayState!.insert(overlayEntry!);

   await Future.delayed(const Duration(seconds: 5))
       .whenComplete(() => animationController!.reverse())
       .whenComplete(() => overlayEntry!.remove());
 }
```

In Example 1, we did not attach the Overlay widget with the suffix icon and displayed the widget at our convenient position. But in Example 2, we had to connect to the Overlay widget with FloatingActionButton to serve its primary purpose, giving more options when the user clicks the FAB button:

```dart
@override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: const Text('Animated Overlay'),
     ),
     floatingActionButton: FloatingActionButton(
       key: globalKey,
       onPressed: _showOverLay,
       child: const Icon(Icons.add),
     ),
   );
 }
}
```

![Animated Overlay](https://blog.logrocket.com/wp-content/uploads/2022/05/animated-overlay.png) The full code is available on the [GitHub repository](https://github.com/timelessfusionapps/flutter_Overlay_FAB).

## Example 3: The Overlay widget follows while scrolling <a name="example-3-the-overlay-widget-follows-while-scrolling"></a>

![Overlay widget left](https://blog.logrocket.com/wp-content/uploads/2022/05/overlay-widget-left.png)

![Overlay widget right](https://blog.logrocket.com/wp-content/uploads/2022/05/overlay-widget-right.png)

Similar to the previous example, we are attaching the Overlay widget with the TextFormField. It appears when a user clicks on it to enter some text and disappears when one clicks away from it. To be more precise, when TextFormField has focus, the Overlay widget appears with suggestions. When it loses focus or if the user scrolls and the TextFormField is not visible on the screen, the Overlay disappears. 

Here we have created a separate stateful widget for the country TextFormField and then called it inside our **Profile Page**, which is a stateless widget.

## A few points to remember:

1.  We have assigned `FocusNode` to the `TextFormField` and added a listener to it inside `initState` to detect when the `TextFormField` gains or loses focus
2.  Based on `FocusNode`, we are creating and inserting the `OverlayEntry` widget
3.  Again, we use `RenderBox` to know the exact position, size, and other rendering information needed for our widget
4.  We also use `RenderBox` to get the widget's size and the widget's coordinates on the screen
5.  We set those coordinates to the `Positioned` widget
6.  Inside the `Positioned` widget, we display a list of countries inside a `Column` widget using a `ListTile`. (I have hardcoded a few entries for this example):

```dart
final FocusNode _focusNode = FocusNode();
     OverlayEntry? _overlayEntry;
     GlobalKey globalKey = GlobalKey();
     final LayerLink _layerLink = LayerLink();

     @override
     void initState() {
       super.initState();
       OverlayState? overlayState = Overlay.of(context);
       WidgetsBinding.instance!.addPostFrameCallback((_) {
         globalKey;
       });
        _focusNode.addListener(() {
         if (_focusNode.hasFocus) {
                 _overlayEntry = _createOverlay();

           overlayState!.insert(_overlayEntry!);
         } else {
          _overlayEntry!.remove();
         }
       });
     }
```

Now we want our Overlay to follow our TextFormWidget while the user scrolls. 

Flutter provides two unique widgets:

1.  CompositedTransformFollower
2.  CompositedTransformTarget

Basically, we link the follower to the target, and then the follower will follow the target widget. To do that, we have to provide both widgets with the same `LayerLink`.

### Important points to remember

1.  Wrap the `Material` widget with the `CompositedTransformFollower`
2.  Wrap the `TextFormField` with `CompositedTransformTarget`
3.  Provide offset to `CompositedTransfromFollower` so that it does not cover the `TextFormField`
4.  Set the `showWhenUnlinked` property to `false` to hide the `Overlay` when `TextFormField` is not visible when the user scrolls

And now, the `OverlayEntry` will follow the `TextFormField`:

```dart
OverlayEntry _createOverlay() {
   RenderBox renderBox = context.findRenderObject() as RenderBox;

   var size = renderBox.size;
   return OverlayEntry(
       builder: (context) => Positioned(
             width: size.width,
             child: CompositedTransformFollower(
               link: _layerLink,
               showWhenUnlinked: false,
               offset: Offset(0.0, size.height + 5.0),
               child: Material(
                 elevation: 5.0,
                 child: Column(
                   children: const [
                     ListTile(
                       title: Text('India'),
                     ),
                     ListTile(
                       title: Text('Australia'),
                     ),
                     ListTile(
                       title: Text('USA'),
                     ),
                     ListTile(
                       title: Text('Canada'),
                     ),
                   ],
                 ),
               ),
             ),
           ));
 }

 @override
 Widget build(BuildContext context) {
   return CompositedTransformTarget(
     link: _layerLink,
     child: TextFormField(
       focusNode: _focusNode,
       keyboardType: TextInputType.text,
       textCapitalization: TextCapitalization.words,
       textInputAction: TextInputAction.next,
       decoration: kTextInputDecoration.copyWith(labelText: 'Country Name'),
     ),
   );
 }
}
```

![Profile Page](https://blog.logrocket.com/wp-content/uploads/2022/05/profile-page.gif) The **Profile Page** is a stateless widget, and you can [find the full code for it here](https://github.com/timelessfusionapps/flutter_overlayWidget_profilePage).

## Conclusion

That is all for this tutorial. I hope I was able to impart new knowledge to your existing experience in Flutter development. I know I have learned some great new features about Flutter and its widgets, and I hope to use them in future projects. I usually end with this quote that I love: "Discovery requires experimentation." 

Thank you! Take care and stay safe.

---

## [LogRocket](https://logrocket.com/signup/): Full visibility into your web apps

[![LogRocket signup](https://blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png)](https://www2.logrocket.com/react-performance-monitoring)

[LogRocket](https://logrocket.com/signup/) is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

[Try it for free](https://logrocket.com/signup/).