image

26 Jan 2026

9K

35K

Building a Movie List Widget with Poster Grid in Flutter

Creating an engaging user interface for displaying lists of items is a common requirement in many mobile applications. For movie apps, a visually rich poster grid is essential to captivate users. Flutter, with its declarative UI and powerful widget system, makes building such interfaces intuitive and performant. This article will guide you through the process of constructing a movie list widget featuring a poster grid in Flutter, from data modeling to UI implementation.

1. Project Setup

First, ensure you have Flutter installed. If not, refer to the official Flutter documentation. Create a new Flutter project:


flutter create movie_app
cd movie_app

You can then open the project in your preferred IDE (VS Code, Android Studio).

2. Defining the Movie Data Model

Before we build the UI, we need a way to represent our movie data. Let's create a simple Movie class with basic properties like id, title, and posterUrl. Create a new file, e.g., lib/models/movie.dart:


// lib/models/movie.dart
class Movie {
  final String id;
  final String title;
  final String posterUrl;

  Movie({
    required this.id,
    required this.title,
    required this.posterUrl,
  });
}

3. Preparing Mock Movie Data

For demonstration purposes, we'll use mock data. In a real application, you would fetch this data from an API (e.g., The Movie Database API). Let's create a list of dummy movies. Create lib/data/mock_movies.dart:


// lib/data/mock_movies.dart
import '../models/movie.dart';

final List<Movie> mockMovies = [
  Movie(
    id: '1',
    title: 'The Shawshank Redemption',
    posterUrl: 'https://m.media-amazon.com/images/M/MV5BNDE3ODcxYzMtY2YzZC00NmJkLWFkMDAtNTg3NDg2ZjRjMDhjXkEyXkFqcGdeQXVyMTA4NjE0NjEy._V1_QL75_UX190_CR0,0,190,281_.jpg',
  ),
  Movie(
    id: '2',
    title: 'The Godfather',
    posterUrl: 'https://m.media-amazon.com/images/M/MV5BM2MyNjYxNmUtYjQyOS00NGYxLWEzYzctNDc3NWQ5NDYwNTc2XkEyXkFqcGdeQXVyNzkwMjQ5NzM@._V1_QL75_UX190_CR0,0,190,281_.jpg',
  ),
  Movie(
    id: '3',
    title: 'The Dark Knight',
    posterUrl: 'https://m.media-amazon.com/images/M/MV5BMTMxNTMwODM0NF5BMl5BanBnXkFtZTcwODAyMTk2Mw@@._V1_QL75_UX190_CR0,0,190,281_.jpg',
  ),
  Movie(
    id: '4',
    title: 'Pulp Fiction',
    posterUrl: 'https://m.media-amazon.com/images/M/MV5BNGNhMDIzZTUtNTBlZi00MTRlLWFjM2ItYzViMjYxNjdlNTM3XkEyXkFqcGdeQXVyMjYwNDA2MDE@._V1_QL75_UX190_CR0,0,190,281_.jpg',
  ),
  Movie(
    id: '5',
    title: 'Forrest Gump',
    posterUrl: 'https://m.media-amazon.com/images/M/MV5BNWIwODc4MjEtNWMyZC00MmE3LTkxYmQtNDIxYmEzZGYwNTkzXkEyXkFqcGdeQXVyMjAzNDQyNjU@._V1_QL75_UX190_CR0,0,190,281_.jpg',
  ),
  Movie(
    id: '6',
    title: 'Inception',
    posterUrl: 'https://m.media-amazon.com/images/M/MV5BMjAxMzYwYTEtNzIwNi00YTMxLWE3NzItMDI2ODkyMGM3MWY4XkEyXkFqcGdeQXVyMTE0MzQwMjky._V1_QL75_UX190_CR0,0,190,281_.jpg',
  ),
  Movie(
    id: '7',
    title: 'The Matrix',
    posterUrl: 'https://m.media-amazon.com/images/M/MV5BNzQzOTU3ODktNDg0Zi00ZWYtOWRlYy0zZDZkZmNhMDJlYjcwXkEyXkFqcGdeQXVyMTE0MzQwMjky._V1_QL75_UX190_CR0,0,190,281_.jpg',
  ),
  Movie(
    id: '8',
    title: 'Fight Club',
    posterUrl: 'https://m.media-amazon.com/images/M/MV5BNWJmYjc0NzUtOWVkZC00YzUzLWI2OGEtZWI0YjUzNTYxMTU0XkEyXkFqcGdeQXVyNzkwMjQ5NzM@._V1_QL75_UX190_CR0,0,190,281_.jpg',
  ),
  Movie(
    id: '9',
    title: 'Interstellar',
    posterUrl: 'https://m.media-amazon.com/images/M/MV5BMjI0NTQ0NjU1N15BMl5BanBnXkFtZTgwNTU5Nzc5MTE@._V1_QL75_UX190_CR0,0,190,281_.jpg',
  ),
  Movie(
    id: '10',
    title: 'The Lord of the Rings',
    posterUrl: 'https://m.media-amazon.com/images/M/MV5BNzgxYzA4NDYtZjIyMy00MGUxLWIxNGEtM2EzODMyNTUzYzgwXkEyXkFqcGdeQXVyMDM2NDM2MQ@@._V1_QL75_UX190_CR0,0,190,281_.jpg',
  ),
];

4. Creating the Movie Poster Widget

Each item in our grid will be a movie poster. Let's create a reusable widget for this. It will display the movie's poster image and can be extended to show the title or other details. Create lib/widgets/movie_poster.dart:


// lib/widgets/movie_poster.dart
import 'package:flutter/material.dart';

class MoviePoster extends StatelessWidget {
  final String posterUrl;
  final String title;
  final VoidCallback? onTap;

  const MoviePoster({
    super.key,
    required this.posterUrl,
    required this.title,
    this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onTap,
      child: Container(
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(8.0),
          boxShadow: [
            BoxShadow(
              color: Colors.black.withOpacity(0.3),
              spreadRadius: 1,
              blurRadius: 5,
              offset: const Offset(0, 3),
            ),
          ],
        ),
        clipBehavior: Clip.antiAlias, // Ensures content respects border radius
        child: Image.network(
          posterUrl,
          fit: BoxFit.cover,
          errorBuilder: (context, error, stackTrace) {
            return Container(
              color: Colors.grey[300],
              child: const Center(
                child: Icon(Icons.broken_image, size: 48, color: Colors.grey),
              ),
            );
          },
          loadingBuilder: (context, child, loadingProgress) {
            if (loadingProgress == null) return child;
            return Container(
              color: Colors.grey[200],
              child: Center(
                child: CircularProgressIndicator(
                  value: loadingProgress.expectedTotalBytes != null
                      ? loadingProgress.cumulativeBytesLoaded /
                          loadingProgress.expectedTotalBytes!
                      : null,
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}

5. Building the Movie Grid Widget

Now, let's create the widget that arranges multiple MoviePoster widgets in a grid. We'll use GridView.builder for efficiency, especially with large lists, as it only builds items that are visible on screen. Create lib/widgets/movie_grid.dart:


// lib/widgets/movie_grid.dart
import 'package:flutter/material.dart';
import '../models/movie.dart';
import 'movie_poster.dart';

class MovieGrid extends StatelessWidget {
  final List<Movie> movies;

  const MovieGrid({super.key, required this.movies});

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      padding: const EdgeInsets.all(12.0),
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2, // Number of columns in the grid
        crossAxisSpacing: 12.0, // Spacing between columns
        mainAxisSpacing: 12.0, // Spacing between rows
        childAspectRatio: 0.7, // Aspect ratio of each item (width / height)
      ),
      itemCount: movies.length,
      itemBuilder: (context, index) {
        final movie = movies[index];
        return MoviePoster(
          posterUrl: movie.posterUrl,
          title: movie.title,
          onTap: () {
            // TODO: Implement navigation to movie detail page
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('Tapped on ${movie.title}')),
            );
          },
        );
      },
    );
  }
}

The SliverGridDelegateWithFixedCrossAxisCount is perfect for a fixed number of columns. You can adjust crossAxisCount, crossAxisSpacing, mainAxisSpacing, and childAspectRatio to control the grid's layout and appearance.

6. Integrating into the Main Application

Finally, let's integrate our MovieGrid into the main Scaffold. Open lib/main.dart and replace its content:


// lib/main.dart
import 'package:flutter/material.dart';
import 'data/mock_movies.dart'; // Import your mock data
import 'widgets/movie_grid.dart'; // Import your MovieGrid widget

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Movie Poster Grid',
      theme: ThemeData(
        primarySwatch: Colors.blueGrey,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const MovieListPage(),
    );
  }
}

class MovieListPage extends StatelessWidget {
  const MovieListPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Movies'),
        centerTitle: true,
      ),
      body: MovieGrid(
        movies: mockMovies, // Pass your mock movie data to the grid
      ),
    );
  }
}

Run your application:


flutter run

You should now see a scrollable grid of movie posters on your device or simulator.

7. Enhancements and Next Steps

This is a foundational setup. Here are some ideas for further enhancements:

  • Real API Integration: Replace mockMovies with actual data fetched from a movie API (e.g., TMDB, OMDb) using packages like http or dio.
  • Loading States: Implement loading indicators while fetching data and error messages if the fetch fails.
  • Detail Screen: When a poster is tapped, navigate to a new screen displaying detailed information about the movie.
  • Responsiveness: Adjust crossAxisCount in SliverGridDelegateWithFixedCrossAxisCount based on screen width using MediaQuery.of(context).size.width to display more columns on larger screens (e.g., tablets).
  • Search and Filtering: Add a search bar or filter options to narrow down the movie list.
  • Infinite Scrolling: For large datasets, implement pagination and load more movies as the user scrolls to the end of the list.
  • Better Styling: Add movie titles overlayed on posters, ratings, genre tags, and more sophisticated animations.

Conclusion

You've successfully built a dynamic movie list widget with a poster grid in Flutter. By combining a clear data model, reusable widgets like MoviePoster, and efficient list builders like GridView.builder, Flutter allows you to create stunning and performant user interfaces with remarkable ease. This foundation can now be extended with more advanced features to create a full-fledged movie application.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is