Flutter - Using Dismissible Widget Examples

This tutorial is about how to use Dismissible widget in Flutter.

In Flutter, if you need to create a widget that can be dismissed, you can wrap the widget as the child of Dismissible. Dismissible is usually used to wrap each list item so that it can be dismissed, either horizontally or vertically. I'm going to give you some examples of how to use the widget, including how to show confirmation modal, set backgrounds that will be shown when the child is being dismissed, and set dismiss direction.

Creating Dismissible

Below is the constructor of Dismissible

  const Dismissible({
    @required Key key,
    @required this.child,
    this.background,
    this.secondaryBackground,
    this.confirmDismiss,
    this.onResize,
    this.onDismissed,
    this.direction = DismissDirection.horizontal,
    this.resizeDuration = const Duration(milliseconds: 300),
    this.dismissThresholds = const <DismissDirection, double>{},
    this.movementDuration = const Duration(milliseconds: 200),
    this.crossAxisEndOffset = 0.0,
    this.dragStartBehavior = DragStartBehavior.start,
  })

You are required to pass key (Key) and child (Widget). key becomes very important since the widget can be removed from the widget list. If there are multiple dismissible widgets, make sure each has a unique key. Be careful not to use index as key as dismissing a widget can change the index of other widgets. The second required property is child where you need to pass the widget that can be dismissed.

Another important property is onDismissed. It's a callback function accepting one parameter of type DismissDirection. Inside, you can define what to do after the widget has been dismissed. For example, you can remove the widget from the list.

For other properties, you can read the Properties section.

For example, we are going to create a simple ListView where the item can be dismissed. The ListView is created using the following values.

  List<String> _values = ['One', 'Two', 'Three', 'Four', 'Five'];

Here's the code for building the ListView. The itemBuilder, which is used to build the list items, returns a Dismissible. In addition of the required arguments (key and child), onDismissed callback is also passed. The below example shows you how to set different action for each direction.

  ListView.separated(
    itemCount: _values.length,
    padding: const EdgeInsets.all(5.0),
    separatorBuilder: (context, index) => Divider(
      color: Colors.black,
    ),
    itemBuilder: (context, index) {
      return Dismissible(
        key: Key('item ${_values[index]}'),
        onDismissed: (DismissDirection direction) {
          if (direction == DismissDirection.startToEnd) {
            print("Add to favorite");
          } else {
            print('Remove item');
          }

          setState(() {
            _values.removeAt(index);
          });
        },
        child: ListTile(
          leading: Icon(Icons.local_activity, size: 50),
          title: Text(_values[index]),
          subtitle: Text('Description here'),
        ),
      );
    }
  ),

Output:

Flutter - Dismissible - Basic

 

Showing Confirmation

Dismissible is often used for delete action. If you think the performed action is crucial and cannot be undone, it's better to show confirmation before the action defined inside onDismissed is performed. You can do it by passing confirmDismissCallback to the constructor. It's a callback that accepts one parameter of type DismissDirection and returns Future<bool>. The below example shows an AlertDialog where the user can confirm to delete the item or cancel the action.

  confirmDismiss: (DismissDirection direction) async {
    return await showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: const Text("Delete Confirmation"),
          content: const Text("Are you sure you want to delete this item?"),
          actions: <Widget>[
            FlatButton(
                onPressed: () => Navigator.of(context).pop(true),
                child: const Text("Delete")
            ),
            FlatButton(
              onPressed: () => Navigator.of(context).pop(false),
              child: const Text("Cancel"),
            ),
          ],
        );
      },
    );
  }

Output:

Flutter - Dismissible - Confirm

 

Setting Backgrounds

The default dismiss direction is horizontal. You can swipe to the right or left. Swiping to left or right may end up in a different action depending on what you define in onDismissed callback. Not only different actions, Flutter allows you to set different widgets that will be shown when the child is being dismissed. Use background to define the widget to be displayed when the child is swiped to the right and secondaryBackground for the widget when the child is swiped to the left. If you only set background, it will be used for both directions.

  background: Container(
    color: Colors.blue,
    child: Padding(
      padding: const EdgeInsets.all(15),
      child: Row(
        children: [
          Icon(Icons.favorite, color: Colors.white),
          Text('Move to favorites', style: TextStyle(color: Colors.white)),
        ],
      ),
    ),
  ),
  secondaryBackground: Container(
    color: Colors.red,
    child: Padding(
      padding: const EdgeInsets.all(15),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          Icon(Icons.delete, color: Colors.white),
          Text('Move to trash', style: TextStyle(color: Colors.white)),
        ],
      ),
    ),
  ),

Output:

Flutter - Dismissible - Backgrounds

 

Below is the full code that you can copy if you want to try the code in this tutorial. This includes the confirmation modal and different backgrounds for each direction.

  import 'package:flutter/material.dart';
  
  void main() => runApp(MyApp());
  
  class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'Welcome to Flutter',
        home: _DismissibleApp(),
      );
    }
  }
  
  class _DismissibleApp extends StatefulWidget {
    @override
    _DismissibleAppState createState() => new _DismissibleAppState();
  }
  
  class _DismissibleAppState extends State<_DismissibleApp> {
  
    List<String> _values = ['One', 'Two', 'Three', 'Four', 'Five'];
  
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text('Woolha.com Flutter Tutorial'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(15),
          child: ListView.separated(
            itemCount: _values.length,
            padding: const EdgeInsets.all(5.0),
            separatorBuilder: (context, index) => Divider(
              color: Colors.black,
            ),
            itemBuilder: (context, index) {
              return Dismissible(
                key: Key('item ${_values[index]}'),
                background: Container(
                  color: Colors.blue,
                  child: Padding(
                    padding: const EdgeInsets.all(15),
                    child: Row(
                      children: <Widget>[
                        Icon(Icons.favorite, color: Colors.white),
                        Text('Move to favorites', style: TextStyle(color: Colors.white)),
                      ],
                    ),
                  ),
                ),
                secondaryBackground: Container(
                  color: Colors.red,
                  child: Padding(
                    padding: const EdgeInsets.all(15),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.end,
                      children: <Widget>[
                        Icon(Icons.delete, color: Colors.white),
                        Text('Move to trash', style: TextStyle(color: Colors.white)),
                      ],
                    ),
                  ),
                ),
                confirmDismiss: (DismissDirection direction) async {
                  return await showDialog(
                    context: context,
                    builder: (BuildContext context) {
                      return AlertDialog(
                        title: const Text("Delete Confirmation"),
                        content: const Text("Are you sure you want to delete this item?"),
                        actions: <Widget>[
                          FlatButton(
                              onPressed: () => Navigator.of(context).pop(true),
                              child: const Text("Delete")
                          ),
                          FlatButton(
                            onPressed: () => Navigator.of(context).pop(false),
                            child: const Text("Cancel"),
                          ),
                        ],
                      );
                    },
                  );
                },
                onDismissed: (DismissDirection direction) {
                  if (direction == DismissDirection.startToEnd) {
                    print("Add to favorite");
                  } else {
                    print('Remove item');
                  }
  
                  setState(() {
                    _values.removeAt(index);
                  });
                },
                child: ListTile(
                  leading: Icon(Icons.local_activity, size: 50),
                  title: Text(_values[index]),
                  subtitle: Text('Description here'),
                ),
              );
            }
          ),
        ),
      );
    }
  }
  

Output:

Flutter - Dismissible - Full Code

 

Using Dismissible with Vertical Direction

If the scroll direction of the list is horizontal, the dismiss direction should be vertical (up or down). You need to set the value of direction to DismissDirection.vertical. Below is the full code with horizontal list and vertical dismiss direction.

  import 'package:flutter/material.dart';
  
  void main() => runApp(MyApp());
  
  class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        title: 'Woolha.com Flutter tutorial',
        home: _DismissibleApp(),
      );
    }
  }
  
  class _DismissibleApp extends StatefulWidget {
    @override
    _DismissibleAppState createState() => new _DismissibleAppState();
  }
  
  class _DismissibleAppState extends State {
  
    List _values = ['One', 'Two', 'Three', 'Four', 'Five'];
  
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text('Woolha.com Flutter Tutorial'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(15),
          child: Container(
            alignment: Alignment.center,
            height: 100,
            child: ListView.separated(
                scrollDirection: Axis.horizontal,
                itemCount: _values.length,
                padding: const EdgeInsets.all(5.0),
                separatorBuilder: (context, index) => VerticalDivider(
                  color: Colors.black,
                ),
                itemBuilder: (context, index) {
                  return Dismissible(
                    direction: DismissDirection.vertical,
                    key: Key('item ${_values[index]}'),
                    background: Container(
                      color: Colors.blue,
                      child: Padding(
                        padding: const EdgeInsets.all(15),
                        child: Icon(Icons.favorite, color: Colors.white),
                      ),
                    ),
                    secondaryBackground: Container(
                      color: Colors.red,
                      child: Padding(
                        padding: const EdgeInsets.all(15),
                        child: Icon(Icons.delete, color: Colors.white),
                      ),
                    ),
                    onDismissed: (DismissDirection direction) {
                      if (direction == DismissDirection.down) {
                        print("Add to favorite");
                      } else {
                        print('Remove item');
                      }
  
                      setState(() {
                        _values.removeAt(index);
                      });
                    },
                    child: Container(
                      width: 100,
                      child: Center(
                        child: Column(
                          children: [
                            Icon(Icons.local_activity, size: 50),
                            Text(_values[index]),
                          ],
                        ),
                      ),
                    ),
                  );
                }
            ),
          )
        ),
      );
    }
  }
  

Output:

Flutter - Dismissible - Vertical

 

Properties

Here's the list of properties of Dismissible you can pass to the constructor.

  • Key key *: The widget key, used to control if it should be replaced.
  • Widget child *: The widget below this widget in the tree.
  • Widget background: A widget stacked behind the child. If secondaryBackground is set, it's only shown when the child is being dragged down or to the right.
  • Widget secondaryBackground: A widget stacked behind the child. It's only shown when the child is being dragged up or to the left.
  • ConfirmDismissCallback confirmDismiss: Gives the app an opportunity to confirm or veto a pending dismissal.
  • VoidCallback onResize: A callback that will be called when the widget changes size.
  • DismissDirectionCallback onDismissed: A callback that will be called when the widget has been dismissed.
  • DismissDirection direction: The direction in which the widget can be dismissed. Defaults to DismissDirection.horizontal.
  • Duration resizeDuration: The amount of time the widget will spend contracting before onDismissed is called. Defaults to const Duration(milliseconds: 300).
  • Map<DismissDirection, double> dismissThresholds: The offset threshold the item has to be dragged in order to be considered dismissed. Defaults to const <DismissDirection, double>
  • Duration movementDuration: The duration to dismiss or back to original position if not dismissed. Defaults to const Duration(milliseconds: 200).
  • double crossAxisEndOffset: The end offset across the main axis after the card is dismissed. Defaults to 0.0.
  • DragStartBehavior dragStartBehavior: How the drag start behavior is handled. Defaults to DragStartBehavior.start.

*: required