avatarDevTechie

Summary

The provided content is a comprehensive guide on creating and customizing popup menus (context menus) in Flutter applications using the PopupMenuButton and PopupMenuItem widgets.

Abstract

The article "Popup-Menu (Context Menu) in Flutter" introduces the concept of popup menus within the Flutter framework, explaining their utility in providing additional options to users, typically accessed via a right-click or long-press. It delves into the implementation details, demonstrating how to use PopupMenuButton to create the menu and PopupMenuItem to define individual menu items. The guide covers various properties (parameters) of PopupMenuButton, such as itemBuilder, initialValue, onSelected, onCanceled, onOpened, iconSize, and customization options including tooltip, elevation, position, offset, color, and shape. It also illustrates how to add icons to menu items using the Row widget and adjust spacing with SizedBox. The article concludes with practical examples, showing how to place the popup menu in the app bar and emphasizing the importance of actionable and visually appealing menus in real-world applications.

Opinions

  • The author emphasizes the importance of popup menus in enhancing user interaction by providing context-sensitive options.
  • The use of PopupMenuButton and PopupMenuItem is recommended as the standard approach for creating popup menus in Flutter.
  • Customization of the popup menu is encouraged to improve user experience, with the article providing examples of changing the menu's appearance and behavior.
  • The article suggests that the placement of the popup menu within the app bar is a common and effective design pattern in Flutter applications.
  • The author values the use of snack bars as a means to provide feedback to the user after interacting with the popup menu.
  • There is an opinion that using widgets like SizedBox for spacing and Row for combining icons and text enhances the visual appeal of menu items.
  • The article implies that understanding and utilizing the various properties of PopupMenuButton is key to creating fully functional and user-friendly popup menus.

Popup-Menu (Context Menu) in Flutter

Popup-Menu (Context Menu) in Flutter

Introduction

Popup menu is a popular graphical user interface control element for displaying additional options to the user. The menu that displays when you click the right-button of the mouse to show extra options such as rename, delete, copy, and paste to the user is the most typical example of a popup menu. (This is also known as the context-menu.)

Ok, popup menus have a great utility in apps, but how to create them in Flutter. We can use PopupMenuButton and PopupMenuItem widgets to create them.

PopupMenuButton

It is the widget which is responsible for creating a popup-menu. A menu has many items, each item is created by a different widget — the PopupMenuItem widget.

PupupMenuButton has various properties (parameters of the constructor) which are described in the below sections.

itemBuilder

It is a function prop, hence takes a callback function which creates popup-menu. itemBuilder will supply widgets to display inside PopupMenuButton items using the PopupMenuItem widget. A popup-menu can have many items (options), hence itemBuilder must return a list of PopupMenuItem.

import 'package:flutter/material.dart';
void main() {
  runApp(const MyApp());
}
class MyApp extends StatelessWidget {
  const MyApp({super.key});
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        primarySwatch: Colors.blue,
      ),
      home: const AppHome(),
    );
  }
}
class AppHome extends StatelessWidget {
  const AppHome({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Popup Menu",)),
      body: PopupMenuButton(
               // build the popup menu items
        itemBuilder: (BuildContext context){
          return const [
            PopupMenuItem(
              value: 1,
              child: Text("Create"),
            ),
            PopupMenuItem(
              value: 2,
              child: Text("Remove"),
            ),
            PopupMenuItem(
              value: 3,
              child: Text("Update"),
            ),
            PopupMenuItem(
              value: 4,
              child: Text("Delete"),
            ),
          ];
        },
      ),
    );
  }
}

The above code creates a popup menu which is represented by the three vertical dots button. When it is tapped/clicked, the popup menu will be displayed containing the options (menu items) defined by the itemBuilder function prop.

itemBuilder returns a list of popup menu items, each popup menu item is defined by a PopupMenuItem widget. Hence, itemBuilder returns a list of PopupMenuItem widgets.

The PopupMenuItem widget has various props to define each menu item. Here, I have used two props — child and value.

The child property inside a PopupMenuItem can be any widget. Here, I have set it to a Text widget, but it can be any Flutter widget. For example to have an icon together with text, Text and Icon widgets can be used as I will explain in the later section.

PopupMenuItem also contains a value prop (parameter in constructor) that is used to assign a value to each popup menu item. This value identifies a popup menu item within the popup-menu. This value is received within the function prop.

The value can be of different types, here I have used integer values, string values can also be used.

The below preview video shows the popup-menu.

popup menu using PopupMenuButton and PopupMenuItem widgets

initialValue

When the popup menu is launched, the value you assigned to the initialValue prop, and the corresponding menu item is highlighted. To use it, you must use value prop inside the PopupMenuItem.

The initialValue will highlight the item that has same value; with a grey background when showing the popup-menu.

Add the prop initialValue:3, within the PopupMenuButton to highlight the menu-item “update” since it corresponds to the value 3.

This is shown in the below preview image .

intialvalue highlights the corresponding menu-option when popup menu is opened

onSelected

This is a function prop, hence takes a callback function. This callback function receives the value of the selected (tapped/pressed) menu-item from the popup menu as its parameter.

Add the below code to use this function prop to the PopupMenuButton

onSelected: (value){
  SnackBar snackBar = SnackBar(
    content: Text("You have selected the menu item: $value", textScaleFactor: 2,),
    duration: const Duration(seconds: 5),
  );
  // show he snack bar (message)
  ScaffoldMessenger.of(context).showSnackBar(snackBar);
},

When a menu option is selected by the user, the popup-menu will disappear and this function is called which receives the value of the selected menu item which identifies which menu-item is selected by the user.

We can initiate any kind of processing (a sequence of actions) upon the menu item selection, however in above code I have just shown a message that displays which menu-item is selected by the user. In real production apps, there could be more complex processing like fetching, updating database etc.

The message is displayed using the SnackBar and ScaffoldMessenger widgets.

The preview after the addition of the above code is as follows:

onSelected to respond to popup menu selection

onCanceled

Like the onSelected, this is also a function prop.

The specified callback function is invoked when the user taps/clicks the area outside the popup-menu i.e. the user dismisses the popup menu without selecting an item.

Tapping/Clicking outside the popup menu will let the popup menu goes away.

Add the below lines of code to the PopupMenuButton to use this function prop.

onCanceled: (){
  SnackBar snackBar = const SnackBar(
    content: Text("You have cancelled (not selected) the menu item:",
    style: TextStyle(fontSize: 20),),
    duration: Duration(seconds: 5),
    backgroundColor: Colors.teal,
    showCloseIcon: true,
    closeIconColor: Colors.red,
  );
  // show he snack bar (message)
  ScaffoldMessenger.of(context).showSnackBar(snackBar);
},

Like the onSelected, here also any action can be initiated. For demonstration, I have shown a message. However, unlike the SnackBar of onSelected, this time background-color is specified and a close button is also added to the snack-bar message.

The preview video below shows the effect of this prop.

using onCanceled prop of PopupMenuButton

onOpened

This is also a function prop which is called when the popup-menu is opened displayed upon tapping/pressing the three-vertical-dots (popup-menu-button)

I will stick to displaying a message but any kind of processing can be dobe within this function.

Add the below lines of code to learn this prop of PopupMenuButton widget.

onOpened: (){
  SnackBar snackBar = const SnackBar(
    content: Text("Popup menu is opened- select an option.",
      style: TextStyle(fontSize: 20),),
    duration: Duration(seconds: 5),
    backgroundColor: Colors.indigoAccent,
  );
  ScaffoldMessenger.of(context).showSnackBar(snackBar);
},

I have also wrapped the entire PopupMenuButton inside the Center widget to show the three-vertical-dots at the Scaffold center.

The effect of the above changes is shown in the below preview video.

Speciffying the onOpened function prop.

iconSize

This property determines the size of the three-vertical-dots icon.

Add the line iconSize: 100, to the PopupMenuButton for this.

The preview is as follows.

specifying the icon-size of the popup-menu-button

Adding icons to the menu item

Since PopupMenuItem widget has the child prop which can take only a single Flutter widget, we have to use multi-children widget such as Row widget to have more than text in menu items

The below modified code of the itemBuilder prop creates menu-items having both — icon and label using a Row widget.

Please noe that the const keyword before the list is now removed due to use of Row widget constructor. It is now added to the list within the children prop of Row widget. However, the use of the const keyword is optional and will affect the output. It is used for performance purposes only.

itemBuilder: (BuildContext context) {
  return [
    PopupMenuItem(
      value: 1,
      child: Row( children: const [Icon(Icons.create), Text("Create")],),
    ),
    PopupMenuItem(
      value: 2,
      child:Row( children: const [Icon(Icons.remove_circle_outline), Text("Remove")],),
    ),
    PopupMenuItem(
      value: 3,
      child:Row( children: const [Icon(Icons.update ), Text("Update")],),
    ),
    PopupMenuItem(
      value: 4,
      child:Row(children: const [Icon(Icons.delete), Text("Delete")],),
    ),
  ];
},

The preview of the above code is as follows:

both icon and label in the popup menu-items

Oh! You are not satisfied by the spacing between the icon and the label in menu-items.

It can be adjusted using Row props

mainAxisAlignment: MainAxisAlignment.spaceBetween

but a better way to do so is to use widgets like SizedBox.

Below is the code for one menu-item which uses SizedBox widget to add some space between icon and the label to enhance look.

PopupMenuItem(
  value: 4,
  child:Row( children: const [Icon(Icons.delete), SizedBox(width: 10,), Text("Delete")],),
),

The preview is as follows after adding a gap.

Using SizedBox to add space between icon and the label of the popup-menu items.

You can also use style prop of Text widget to enhance the label.

Customizing Popup-Menu

You can customize the popup-menu using props such as tooltip, elevation, padding, offset etc. Let’s explore each of them in turn below.

tooltip

A brief description of the popup-menu which will be displayed when the user long-presses the popup-menu-button (the three-vertical-dots). It takes a String.

Add a line tooltip: “The operations on data-base”, for this.

The preview is as shown in the below video.

tooltip prop for a brief description of popup-menu

elevation:

This prop decides the size of the shadow behind the popup-menu. Add a line elevation: 200, within the PopupMenuButton for this.

controling popop-menu shadow using elevation prop

position:

This prop decides whether the popup menu sits above or below the popup menu button.

If this parameter is not set, PopupMenuThemeData.position is used instead. If PopupMenuThemeData.position is likewise null, the position will be PopupMenuPosition.over, which will cause the popup menu to display immediately over the button (three vertical-dots) that was used to generate it .

Thir prop takes two values — PopupMenuPosition.over and PopupMenuPosition.under.

1 — PopupMenuPosition.over

Add the line position: PopupMenuPosition.over to the popup-menu-button.

The all above preview images and videos were produced with this position: PopupMenuPosition.over.

2 — PopupMenuPosition.under

This value produces the output like below.

popup-menu is displayed under the button

offset:

Useful property when you want to display a menu at a particular position. This prop is used to move the popup menu relative to the position specified by the position prop.

Specifying this prop like offset: Offset(0,100), will produce the below output.

positioing the popup menu using offset

offset prop comes handy when popup-menu-button is placed inside another widget like app-bar, to position it appropriately.

color

Provides the background color for the Popup Menu.

Adding the line color: Colors.redAccent, to the PopupMenuButton will produce the following output.

deciding the background — color of the popup-menu-button

shape

Used to provide a specific shape to the popup menu.

The below code for the shape prop produces the following output.

shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(50),
),
changing shpe of popup-menu

Each corner can be given a different degree of circularity as shown in the below code for the shape prop.

shape: const RoundedRectangleBorder(
    borderRadius: BorderRadius.only(
  topLeft: Radius.circular(50),
  topRight: Radius.circular(30),
  bottomRight: Radius.circular(15),
  bottomLeft: Radius.circular(20),
)),

The preview of the popup-menu is shown below after the above code for shape prop.

different degree of circularity for border-corners

Changing the popup-menu-button

The child prop of PopupMenuButton can be used to change the button (three-vertical-dots in above preview images/videos) which is used to generate the Popup-Menu. The child prop takes a Flutter widget.

Specify the child prop as follows:

child: const Text( "I am hero", style: TextStyle(fontSize: 20),),

The preview video after this addition is as follows:

child prop changes the popup-button

change the child widget as follows:

child: const Icon(Icons.settings_applications_outlined, size: 100,),

Then, the output will be like below.

child prop is set to Icon widget

The complete code until this point is shown in the code-block.

import 'package:flutter/material.dart';
void main() {
  runApp(const MyApp());
}
class MyApp extends StatelessWidget {
  const MyApp({super.key});
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        primarySwatch: Colors.blue,
      ),
      home: const AppHome(),
    );
  }
}
class AppHome extends StatelessWidget {
  const AppHome({Key? key}) : super (key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
          title: const Text(
        "Popup Menu",
      )),
      body: Center(
        child: PopupMenuButton(
          initialValue: 3,
          iconSize: 100,
          tooltip: "The operations on data-base",
          elevation: 200,
          position: PopupMenuPosition.under,
          offset: Offset(0, 100),
          color: Colors.redAccent,
          shape: const RoundedRectangleBorder(
              borderRadius: BorderRadius.only(
            topLeft: Radius.circular(50),
            topRight: Radius.circular(30),
            bottomRight: Radius.circular(15),
            bottomLeft: Radius.circular(20),
          )),
          // this prop takes a widget which replace
          // the default three-vertical dots button of popup-menu-button
          
          // child:const Text("I am hero", style: TextStyle(fontSize: 20),),
          child: const Icon(Icons.settings_applications_outlined, size: 100,),
          onOpened: () {
            SnackBar snackBar = const SnackBar(
              content: Text(
                "Popup menu is opened- select an option.",
                style: TextStyle(fontSize: 20),
              ),
              duration: Duration (seconds: 5),
              backgroundColor: Colors.indigoAccent,
            );
            ScaffoldMessenger.of(context).showSnackBar(snackBar);
          },
          onSelected: (value) {
            SnackBar snackBar = SnackBar(
              content: Text(
                "You have selected the menu item: $value",
                textScaleFactor: 2,
              ),
              duration: const Duration(seconds: 5),
            );
            // show he snack bar (message)
            ScaffoldMessenger.of(context).showSnackBar(snackBar);
          },
          onCanceled: () {
            SnackBar snackBar = const SnackBar(
              content: Text(
                "You have cancelled (not selected) the menu item:",
                style: TextStyle(fontSize: 20),
              ),
              duration: Duration(seconds: 5),
              backgroundColor: Colors.teal,
              showCloseIcon: true,
              closeIconColor: Colors.red,
            );
            // show he snack bar (message)
            ScaffoldMessenger.of(context).showSnackBar(snackBar);
          },
          // build the popup menu items
          itemBuilder: (BuildContext context) {
            return [
              PopupMenuItem(
                value: 1,
                child: Row(
                  children: const [
                    Icon(Icons.create),
                    SizedBox(
                      width: 10,
                    ),
                    Text("Create")
                  ],
                ),
              ),
              PopupMenuItem(
                value: 2,
                child: Row(
                  children: const [
                    Icon(Icons.remove_circle_outline),
                    SizedBox(
                      width: 10,
                    ),
                    Text("Remove")
                  ],
                ),
              ),
              PopupMenuItem(
                value: 3,
                child: Row(
                  children: const [
                    Icon(Icons.update),
                    SizedBox(
                      width: 10,
                    ),
                    Text("Update")
                  ],
                ),
              ),
              PopupMenuItem(
                value: 4,
                child: Row(
                  children: const [
                    Icon(Icons.delete),
                    SizedBox(
                      width: 10,
                    ),
                    Text("Delete")
                  ],
                ),
              ),
            ];
          },
        ),
      ),
    );
  }
}

A realistic placement of Popup-menu

In a real (production) app, the popup-menu is not placed inside some panel, bar etc. The below code places the above designed popup-menu in the app-bar (action bar). For this, I will use the actions prop of AppBar widget.

AppBar.actions

This property of AppBar takes a list of widgets since a widget represents an action. The widgets list denotes actions which are placed in an action bar or tool-bar. The toolbar (of actions) is displayed just after the title in the app bar.

The action bar is aligned to the right of AppBar.

Although it is a list of widgets, here we will use only PopupMenuButton widget (i.e. list has a sinlge widget — PopupMenuButton ) to create the popup-menu.

Updated code snippet is as follows

Please note that, offset property is set like offset: const Offset(0, 110), to position the popup-menu just below the app-bar. Also the icon-size within child property is set to fit the icon within the app-bar.

A Text widget is now placed as the Sacffold body content since PopupMenuButton is moved to the app-bar.

The preview is shown in the below video.

Popup-menu is placed inside the app-bar

Recapitulate

This article talks about how to create a popup menu and customize the appearance of it.

With that we have reached the end of this article. Thank you once again for reading. If you liked this, don’t forget to 👏 and follow 😍. Also subscribe to our weekly newsletter at https://www.devtechie.com

Flutter
iOS
Android
Dart
Cross Platform
Recommended from ReadMedium