How to make a Dismissible List View in Flutter

How to make a Dismissible List View in Flutter
COMMENTS (0)
Tweet

In this blog, we’ll look at how we can build a dismissable list view in Flutter. You can think of dismissible widget as a draggable piece UI of that can be dragged in any direction and the action causes the UI to slide out from the view. Here is an example of how it looks and what we are going to build:

The complete code for this project is available on the GitHub repo here. Feel free to fork & use it.

Now, Let’s start building that together.

Create a Flutter Project

First things first, create a Flutter project:

$ flutter create dismissible_listview

Build Movie Model

In the lib directory of your project, create a new folder with the name modelsadd a new file movie.dart with the following code:

class Movie {
  final String title;
  final String genre;
  final String year;
  final String imageUrl;

  Movie({this.genre, this.title, this.year, this.imageUrl});
}

Generate Movie Data

Now we’ll create some static movie data to create our ListView. Obviously, this kind of data might come from an API most of the time. Here just for simplicity, I created an array of movies using hard-coded data (which is real and taken from IMBD).

Create new folder, data in the lib directory of your project. Now add a file with the name movie-list.dart and write the following code:

import 'package:dismissable_listview/models/movie.dart';

class MovieList {
  static List<Movie> getMovies() {
    return [
      Movie(
          title: 'Avengers: Endgame',
          genre: 'Action, Adventure, Fantasy',
          year: '2019',
          imageUrl:
              'https://m.media-amazon.com/images/M/MV5BMTc5MDE2ODcwNV5BMl5BanBnXkFtZTgwMzI2NzQ2NzM@._V1_SY1000_CR0,0,674,1000_AL_.jpg'),
      Movie(
          title: 'Dora and the Lost City of Gold',
          genre: '',
          year: '2019',
          imageUrl:
              'https://m.media-amazon.com/images/M/MV5BNDlmNGYxYzAtODczMy00MzgxLTk2MWQtZTk3MDdjY2IwYzdiXkEyXkFqcGdeQXVyOTc3MDA0ODE@._V1_SX300.jpg'),
      Movie(
          title: 'UglyDolls',
          genre: 'Animation, Adventure, Comedy',
          year: '2019',
          imageUrl:
              'https://m.media-amazon.com/images/M/MV5BMTc0NjE2ODM2OV5BMl5BanBnXkFtZTgwMjQyNDUzNzM@._V1_SX300.jpg'),
      Movie(
          title: 'Captain Marvel',
          genre: 'Action, Adventure, Sci-Fi',
          year: '2019',
          imageUrl:
              'https://m.media-amazon.com/images/M/MV5BMTE0YWFmOTMtYTU2ZS00ZTIxLWE3OTEtYTNiYzBkZjViZThiXkEyXkFqcGdeQXVyODMzMzQ4OTI@._V1_SX300.jpg'),
      Movie(
          title: 'John Wick: Chapter 3 - Parabellum',
          genre: 'Action, Thriller',
          year: '2019',
          imageUrl:
              'https://m.media-amazon.com/images/M/MV5BNDU3YzJlY2EtODA3NS00ZWM3LWJhYjUtZWE3MmE2YmEzNWYwXkEyXkFqcGdeQXVyNDMzMzI5MjM@._V1_SX300.jpg'),
      Movie(
          title: 'Us',
          genre: 'Horror, Thriller',
          year: '2019',
          imageUrl:
              'https://m.media-amazon.com/images/M/MV5BZTliNWJhM2YtNDc1MC00YTk1LWE2MGYtZmE4M2Y5ODdlNzQzXkEyXkFqcGdeQXVyMzY0MTE3NzU@._V1_SX300.jpg'),
      Movie(
          title: 'Triple Threat',
          genre: 'Action, Thriller',
          year: '2019',
          imageUrl:
              'https://m.media-amazon.com/images/M/MV5BYTY1MjRhYmYtZDg4Yy00ZWRiLWIwYzktZThkY2E0YjZlNjgxXkEyXkFqcGdeQXVyMTc3MjY3NTY@._V1_SX300.jpg'),
      Movie(
          title: 'Hellboy',
          genre: 'Action, Adventure, Fantasy, Sci-Fi',
          year: '2019',
          imageUrl:
              'https://m.media-amazon.com/images/M/MV5BYTMyYjg0MTItYTcyZS00MmRiLWIxNWQtYTRiZjRkYWMxZGNkXkEyXkFqcGdeQXVyNjg2NjQwMDQ@._V1_SX300.jpg'),
      Movie(
          title: 'Stranger Things',
          genre: 'Drama, Fantasy, Horror, Mystery, Sci-Fi, Thriller',
          year: '2016',
          imageUrl:
              'https://m.media-amazon.com/images/M/MV5BMTk3NTc2NTI0N15BMl5BanBnXkFtZTgwMDA4MjcwNzM@._V1_SX300.jpg'),
      Movie(
          title: 'The Lion King',
          genre: 'Animation, Adventure, Drama, Family, Musical',
          year: '2019',
          imageUrl:
              'https://m.media-amazon.com/images/M/MV5BMjIwMjE1Nzc4NV5BMl5BanBnXkFtZTgwNDg4OTA1NzM@._V1_SX300.jpg'),
    ];
  }
}

Important: Your package name can differ from mine based on the name of the project you decided.

We now have a source of data. All we need to do is to build our list. For this, we’ll start with the movie card first.

Build MovieCard UI

Create widgets folder in the lib directory and add file movie-card.dart with the following code:

import 'package:dismissable_listview/models/movie.dart';
import 'package:flutter/material.dart';

class MovieCard extends StatelessWidget {
  final Movie movie;

  MovieCard({this.movie});

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Column(
        children: <Widget>[
          ListTile(
            leading: CircleAvatar(
              backgroundImage: NetworkImage(movie.imageUrl),
            ),
            title: Text(movie.title),
            subtitle: Text(movie.genre),
            trailing: Text(movie.year),
          )
        ],
      ),
    );
  }
}

Note: Although we are not fetching data from anywhere outside the app, however, the images are being fetched through URL for which we are using NetworkImage API of Flutter which loads the image asynchronously.

Build MovieList Page

Now create one more folder pages in the lib directory. Create new file movies.dart and paste the following code:

import 'package:dismissable_listview/data/movie-list.dart';
import 'package:dismissable_listview/models/movie.dart';
import 'package:dismissable_listview/widgets/movie-card.dart';
import 'package:flutter/material.dart';

class MoviesPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _MoviesPageState();
  }
}

class _MoviesPageState extends State<MoviesPage> {
  final List<Movie> movies = MovieList.getMovies();

  Widget _buildMoviesList() {
    return Container(
      child: movies.length > 0
          ? ListView.builder(
              itemCount: movies.length,
              itemBuilder: (BuildContext context, int index) {
                return Dismissible(
                  onDismissed: (DismissDirection direction) {
                    setState(() {
                      movies.removeAt(index);
                    });
                  },
                  secondaryBackground: Container(
                    child: Center(
                      child: Text(
                        'Delete',
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                    color: Colors.red,
                  ),
                  background: Container(),
                  child: MovieCard(movie: movies[index]),
                  key: UniqueKey(),
                  direction: DismissDirection.endToStart,
                );
              },
            )
          : Center(child: Text('No Items')),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Movies'),
      ),
      body: _buildMoviesList(),
    );
  }
}

Take a closer look at the ListView.builder code we used above:

ListView.builder(
itemCount: movies.length,
itemBuilder: (BuildContext context, int index) {...})

First, we are providing the itemCount with the length of the movies array. Next, the itemBuilder builds and returns the dismissible list item by receiving the context and the index of the item to be built.

The Dismissible Widget

In the itemBuilder method, we are using Flutter’s Dismissible widget. It receives a method under onDismissed along with the DismissDirectionSupported DismissDirections are:

  • endToStart( from right to left)
  • startToEnd (from left to right)
  • horizontal (both from right to left and left to right)
  • up
  • down
  • vertical (both up and down)

In our case, we are only using one direction which is from right to left (endToStart in the code). We are removing that item from the list and performing this action inside the setState() method. Removing the item from the movies list changes the state, so we have to perform this action inside the setState() method. This method under the hood calls the build method to reflect the changes on the UI.

The child receives the actual MovieCard with the movie item to build. Also, a unique key is assigned to the dismissible to avoid duplicates.

Dismissible(
    onDismissed: (DismissDirection direction) {
        setState(() {
            movies.removeAt(index);
        });
    },
    ...
    child: MovieCard(movie: movies[index]),
    key: UniqueKey(),
)

We have done almost everything. All we need to do is to call this movies page as soon as the application starts. For this, edit your main.dart file to look like this:

import 'package:dismissable_listview/pages/movies.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Dismissible ListView',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MoviesPage(),
    );
  }
}

We are all set to launch the app and see how it looks. Run this app on your emulator or any real device to see how it looks.

Conclusion

Using a dismissible widget is very simple and straightforward. You can dismiss it in any direction(horizontal or vertical) and attach events to perform any operations against the event. You can wrap any widget inside Dismissible widget that you can logically think off. Here’s the link to the Flutter’s documentation for Dismissible Widget.

I’ll come with some more exciting stuff on Flutter soon. Till then, Enjoy development with Flutter 🙂

CALL

USA408 365 4638

VISIT

1301 Shoreway Road, Suite 160,

Belmont, CA 94002

Contact us

Whether you are a large enterprise looking to augment your teams with experts resources or an SME looking to scale your business or a startup looking to build something.
We are your digital growth partner.

Tel: +1 408 365 4638
Support: +1 (408) 512 1812