Open In App

Building a Movie Database App in Flutter

Last Updated : 04 Apr, 2025
Comments
Improve
Suggest changes
Like Article
Like
Report

In this tutorial, we'll create a Flutter app that fetches movie data from an API, displays details such as ratings and reviews, and allows users to save their favorite movies locally using SQLite. This Application will demonstrate API integration, state management, and local data persistence.

Application Features

  1. Search for movies by name or genre.
  2. View detailed movie information (cast, plot, rating, etc.).
  3. Save and retrieve favorite movies locally.
Building-a-movie-database-app-in-flutter


What API is used in the Application

For this app, we’ll use the OMDb API to fetch movie data. OMDb is a free API that provides movie information like titles, posters, IMDb ratings, etc. You can get an API key from OMDb after signing up.

Setting Up the Project

Creating the Flutter Project

Create a new Flutter application using the command Prompt. To create a new app, write the below command and run it.

flutter create app_name

To know more about it refer this article: Creating a Simple Application in Flutter

Installing Dependencies

For this project, you’ll need these dependencies:

  1. http: To make network requests to the OMDb API.
  2. sqflite and path_provider : To save and manage favorite movies in a local SQLite database.

Add these dependencies in your pubspec.yaml file:

Dart
dependencies:
    flutter:
    sdk: flutter
    
    http: 
    sqflite: 
    path_provider: 


Run the following command to install the dependencies:

flutter pub get

Folder Structure

Create the following folder structure for better organization of your files:

This structure keeps database logic, models, services, and UI separated, making the code modular and easy to maintain.

Screenshot-2024-10-13-203439

Build the Application

The Application is divided into the following packages:

  • Models: Contains Objects that encapsulate the data and behavior of the application domain.
  • UI elements: These Contain Code that defines the UI of the Application.
  • API Services: It contains the code able to maintain the API Services
  • Database Helper: This part helps us to manage data in our local storage(SQLite).

Step 1 : Models

Let's start by creating a model for our movie data in movie.dart.

movie.dart

movie.dart
class Movie {
  // Movie properties
  final String imdbID;
  final String title;
  final String year;
  final String poster;
  final String type;

  // Constructor to initialize the Movie object.
  Movie({
    required this.imdbID,
    required this.title,
    required this.year,
    required this.poster,
    required this.type,
  });

  // Creates a Movie object from a JSON map.
  factory Movie.fromJson(Map<String, dynamic> json) {
    return Movie(
      imdbID: json['imdbID'],
      title: json['Title'],
      year: json['Year'],
      poster: json['Poster'],
      type: json['Type'],
    );
  }

  // Converts the Movie object into a map.
  Map<String, dynamic> toMap() {
    return {
      'imdbID': imdbID,
      'title': title,
      'year': year,
      'poster': poster,
      'type': type,
    };
  }
}


Now create another Model movie_details.dart; .this will store the movie details.

movie_details.dart

movie_details.dart
class MovieDetails {
  // MovieDetails properties
  final String title;
  final String year;
  final String rated;
  final String released;
  final String genre;
  final String director;
  final String actors;
  final String plot;
  final String poster;
  final String imdbRating;

  // Constructor to initialize the MovieDetails object.
  MovieDetails({
    required this.title,
    required this.year,
    required this.rated,
    required this.released,
    required this.genre,
    required this.director,
    required this.actors,
    required this.plot,
    required this.poster,
    required this.imdbRating,
  });

  // Creates a MovieDetails object from a JSON map.
  factory MovieDetails.fromJson(Map<String, dynamic> json) {
    return MovieDetails(
      title: json['Title'],
      year: json['Year'],
      rated: json['Rated'],
      released: json['Released'],
      genre: json['Genre'],
      director: json['Director'],
      actors: json['Actors'],
      plot: json['Plot'],
      poster: json['Poster'],
      imdbRating: json['imdbRating'],
    );
  }
}


Step 2 : API Service

Create the movie_service.dart file to handle the API calls using the http package.

movie_service.dart

movie_service.dart
import 'dart:convert';
import 'dart:developer'; 
import 'package:http/http.dart' as http; 
import 'package:movies/models/movie.dart'; 
import 'package:movies/models/movie_details.dart'; 

class MovieService {
  // Replace with your OMDb API key.
  final String apiKey = "API_KEY"; 

  // Fetches a list of movies based on the search query.
  Future<List<Movie>> fetchMovies(String searchQuery) async {
    final response = await http.get(
      Uri.parse('http://www.omdbapi.com/?s=$searchQuery&apikey=$apiKey'),
    );

    if (response.statusCode == 200) {
      
      // Decodes the JSON response and extracts
      // the 'Search' list.
      List<dynamic> moviesJson = jsonDecode(response.body)['Search'];
      
      // Logs the list of movies.
      log(moviesJson.toString()); 
      
      // Maps each JSON object to a Movie
      // instance and returns the list.
      return moviesJson.map((json) => Movie.fromJson(json)).toList();
    } 
    else {
      // Throws an exception if the request fails.
      throw Exception('Failed to load movies');
    }
  }

  // Fetches detailed information for a specific
  // movie by its IMDb ID.
  Future<MovieDetails> fetchMovieDetails(String imdbID) async {
    final response = await http.get(
      Uri.parse('http://www.omdbapi.com/?i=$imdbID&apikey=$apiKey'),
    );

    if (response.statusCode == 200) {
      // Decodes the JSON response into a MovieDetails object.
      return MovieDetails.fromJson(jsonDecode(response.body));
    } 
    else {
      // Throws an exception if the request fails.
      throw Exception('Failed to load movie details');
    }
  }
}


Step 3. Database Helper

Next, we’ll create the db_helper.dart file to manage saving favorites using SQLite to our local storage.

db_helper.dart

db_helper.dart
import 'package:geeks_for_geeks/models/movie.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

class DBHelper {
  // Singleton pattern to ensure only one
  // instance of DBHelper is created.
  static final DBHelper _instance = DBHelper._internal();
  factory DBHelper() => _instance;

  // Private constructor for singleton implementation.
  DBHelper._internal();

  // Holds the database instance.
  Database? _database;

  // Returns the database instance, initializes
  // it if not already created.
  Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDB();
    return _database!;
  }

  // Initializes the database and returns
  // the database object.
  Future<Database> _initDB() async {
    // Constructs the path to the 'movies.db' database file.
    String path = join(await getDatabasesPath(), 'movies.db');
    // Opens the database, creating it if it doesn't exist.
    return await openDatabase(
      path,
      version: 1, // Database version.
      onCreate: _onCreate, // Executes _onCreate if the database is new.
    );
  }

  // Called when the database is created; creates the 'favorites' table.
  Future<void> _onCreate(Database db, int version) async {
    await db.execute('''
      CREATE TABLE favorites (
        id INTEGER PRIMARY KEY AUTOINCREMENT, 
        imdbID TEXT, 
        title TEXT, 
        year TEXT, 
        poster TEXT, 
        type TEXT
      )
    '''); // Creates the 'favorites' table with relevant columns.
  }

  // Inserts a movie into the 'favorites' table.
  Future<void> insertFavorite(Movie movie) async {
    final db = await database; // Gets the database instance.
    await db.insert(
      'favorites', // Target table.
      movie.toMap(), // Converts the movie to a map.
      conflictAlgorithm: ConflictAlgorithm.replace, // Replaces if conflict.
    );
  }

  // Retrieves all movies from the 'favorites' table.
  Future<List<Movie>> getFavorites() async {
    final db = await database; // Gets the database instance.
    final List<Map<String, dynamic>> maps = await db.query('favorites');
    // Converts the list of maps into a list of Movie objects.
    return List.generate(maps.length, (i) {
      return Movie(
        imdbID: maps[i]['imdbID'],
        title: maps[i]['title'],
        year: maps[i]['year'],
        poster: maps[i]['poster'],
        type: maps[i]['type'],
      );
    });
  }

  // Deletes a movie from the 'favorites' table based on its IMDb ID.
  Future<bool> deleteFavorite(String imdbID) async {
    // Gets the database instance.
    final db = await database;
    final int count = await db.delete(
      'favorites',
      where: 'imdbID = ?',
      whereArgs: [imdbID], // Filters by the provided IMDb ID.
    );

    // Returns true if a row was deleted.
    return count > 0;
  }

  Future<bool> isFavorite(String imdbID) async {
    final db = await database; // Gets the database instance.
    final List<Map<String, dynamic>> maps = await db.query(
      'favorites',
      where: 'imdbID = ?', // Filters by the provided IMDb ID.
      whereArgs: [imdbID],
    );

    // Returns true if a movie with the given IMDb ID exists in favorites.
    return maps.isNotEmpty;
  }
}


Step 4. UI Components

Our Application contains three main UI Components Screens, as mentioned below:

  • Home Screen
  • Movie Details Screen
  • Favorites Screen

Now let us Observe all them.

- Home Screen (Search Movies)

The home_screen.dart is where users can search for movies:

home_screen.dart

home_screen.dart
import 'package:flutter/material.dart';
import 'package:geeks_for_geeks/models/movie.dart';
import 'package:geeks_for_geeks/screens/favorites_screen.dart';
import 'package:geeks_for_geeks/services/movie_services.dart';

import 'movie_details_screen.dart';

class HomeScreen extends StatefulWidget {
  // Constructor with optional key for widget.
  const HomeScreen({super.key});

  // Creates the state object.
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  // Controller for the search input.
  final TextEditingController _controller = TextEditingController();

  // Instance of MovieService to fetch movies.
  final MovieService _movieService = MovieService();

  // List to store fetched movies.
  List<Movie> _movies = [];

  // Fetches movies based on the search query
  // and updates the UI.
  void _searchMovies() async {
    // Fetches movies.
    final movies = await _movieService.fetchMovies(_controller.text);
    setState(() {
      // Updates the movie list.
      _movies = movies;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('GFG Movies App'), // App bar title.
        backgroundColor: Colors.green, // App bar background color.
        foregroundColor: Colors.white, // App bar text color.
        actions: [
          // Favorite button navigates to the FavoritesScreen.
          IconButton(
            icon: const Icon(Icons.favorite),
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => FavoritesScreen()),
              );
            },
          ),
        ],
      ),
      body: Column(
        children: [
          // Input field for entering the search query.
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextField(
              controller:
                  _controller, // Binds the controller to the input field.
              decoration: const InputDecoration(
                labelText: 'Search for movies', // Input field hint text.
              ),
            ),
          ),
          // Button to trigger the search function.
          ElevatedButton(
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.green, // Button background color.
                foregroundColor: Colors.white, // Button text color.
              ),
              onPressed: _searchMovies, // Calls _searchMovies on press.
              child: const Text(
                "Search", // Button text.
              )),
          // Displays search results or a message if no movies are found.
          Expanded(
            child: _movies.isEmpty
                ? const Center(
                    child: Text('No movies found')) // No results message.
                : ListView.builder(
                    itemCount: _movies.length, // Number of movies to display.
                    itemBuilder: (context, index) {
                      final movie = _movies[
                          index]; // Fetches the movie at the current index.
                      return Card(
                        margin: const EdgeInsets.all(8.0), // Card margin.
                        elevation: 5, // Card elevation effect.
                        child: ListTile(
                          leading: Image.network(
                              movie.poster), // Displays the movie poster.
                          title: Text(movie.title), // Displays the movie title.
                          subtitle:
                              Text(movie.year), // Displays the movie year.
                          trailing: const Icon(Icons
                              .arrow_forward), // Arrow icon for navigation.
                          onTap: () {
                            // Navigates to MovieDetailsScreen with the selected movie's IMDb ID.
                            Navigator.push(
                              context,
                              MaterialPageRoute(
                                builder: (context) =>
                                    MovieDetailsScreen(imdbID: movie.imdbID),
                              ),
                            );
                          },
                        ),
                      );
                    },
                  ),
          ),
        ],
      ),
    );
  }
}

Screenshot of Home Page:

HomePage

- Movie Details Screen

The movie_details_screen.dart is where users can see the movie details :

movie_details_screen.dart

movie_details_screen.dart
import 'package:flutter/material.dart';
import 'package:geeks_for_geeks/helpers/db_helper.dart';
import 'package:geeks_for_geeks/models/movie.dart';
import 'package:geeks_for_geeks/models/movie_details.dart';
import 'package:geeks_for_geeks/services/movie_services.dart';

class MovieDetailsScreen extends StatefulWidget {
  // IMDb ID of the selected movie.
  final String imdbID;

  // Constructor with required IMDb ID.
  const MovieDetailsScreen({super.key, required this.imdbID});

  // Creates the state for this widget.
  @override
  _MovieDetailsScreenState createState() => _MovieDetailsScreenState();
}

class _MovieDetailsScreenState extends State<MovieDetailsScreen> {
  // Service to fetch movie data from API.
  final MovieService _movieService = MovieService();

  // Database helper instance to manage favorites.
  final DBHelper _dbHelper = DBHelper();

  // Holds the detailed movie information.
  MovieDetails? _movieDetails;

  // Tracks whether the movie is marked as favorite.
  bool isFavorite = false;

  @override
  void initState() {
    super.initState();

    // Fetch movie details when the screen initializes.
    _loadMovieDetails();

    // Check if the movie is already marked as favorite.
    _checkFavoriteStatus();
  }

  // Fetches movie details from the API using the IMDb ID.
  void _loadMovieDetails() async {
    // API call.
    final movieDetails = await _movieService.fetchMovieDetails(widget.imdbID);
    setState(() {
      // Updates the state with the fetched movie details.
      _movieDetails = movieDetails;
    });
  }

  // Checks if the movie is already marked as a favorite.
  void _checkFavoriteStatus() async {
    // Checks in the database.
    final status = await _dbHelper.isFavorite(widget.imdbID);
    setState(() {
      // Updates the favorite status.
      isFavorite = status;
    });
  }

  // Toggles the favorite status of the movie.
  void _toggleFavorite() async {
    if (isFavorite) {
      // Removes the movie from favorites.
      await _dbHelper.deleteFavorite(widget.imdbID);
    } else {
      // Adds the movie to favorites.
      await _dbHelper.insertFavorite(Movie(
        imdbID: widget.imdbID,
        title: _movieDetails!.title,
        year: _movieDetails!.year,
        type: 'movie', // Assuming it is a movie.
        poster: _movieDetails!.poster,
      ));
    }

    // Re-checks and updates the favorite status.
    _checkFavoriteStatus();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Movie Details'), // App bar title.
        backgroundColor: Colors.green, // App bar background color.
        foregroundColor: Colors.white, // App bar text color.
        actions: [
          // Favorite button to toggle the favorite status.
          IconButton(
            icon: Icon(isFavorite
                ? Icons.favorite
                : Icons
                    .favorite_border), // Icon changes based on favorite status.
            onPressed:
                _toggleFavorite, // Toggles the favorite status when pressed.
          ),
        ],
      ),
      // Displays a loading indicator if movie details are not loaded yet.
      body: _movieDetails == null
          ? const Center(
              child: CircularProgressIndicator()) // Shows a loading spinner.
          : Padding(
              padding:
                  const EdgeInsets.all(8.0), // Adds padding around the content.
              child: Column(
                children: [
                  Image.network(
                      _movieDetails!.poster), // Displays the movie poster.
                  const SizedBox(height: 10), // Adds spacing.
                  // Displays the movie title with bold styling.
                  Text(
                    _movieDetails!.title,
                    style: const TextStyle(
                        fontSize: 24, fontWeight: FontWeight.bold),
                  ),
                  const SizedBox(height: 10), // Adds spacing.
                  // Displays the IMDb rating of the movie.
                  Text('Rating: ${_movieDetails!.imdbRating}'),
                  const SizedBox(height: 10), // Adds spacing.
                  // Displays the plot/description of the movie.
                  Text(_movieDetails!.plot),
                ],
              ),
            ),
    );
  }
}

Screenshot of the Movie Details Screen:

movie_details

- Favorites Screen

The favorites_screen.dart displays the movies added to favorites and stored in the local database :

favorites_screen.dart

favorites_screen.dart
import 'package:flutter/material.dart';
import 'package:geeks_for_geeks/helpers/db_helper.dart';
import 'package:geeks_for_geeks/models/movie.dart';
import 'movie_details_screen.dart';

class FavoritesScreen extends StatelessWidget {
  // DBHelper instance to manage favorites.
  final DBHelper _dbHelper = DBHelper();

  // Constructor with optional key for widget.
  FavoritesScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Favorite Movies'), // App bar title.
        backgroundColor: Colors.green, // App bar background color.
        foregroundColor: Colors.white, // App bar text color.
      ),

      // Builds the body of the screen using FutureBuilder
      // to load favorite movies asynchronously.
      body: FutureBuilder<List<Movie>>(
        future: _dbHelper
            .getFavorites(), // Fetches favorite movies from the database.
        builder: (context, snapshot) {
          // If data is not yet available, show a loading spinner.
          if (!snapshot.hasData) {
            return const Center(child: CircularProgressIndicator());
          }

          // If there are no favorite movies, display a message.
          if (snapshot.data!.isEmpty) {
            return const Center(child: Text('No favorite movies'));
          }

          // Displays the list of favorite movies.
          return ListView.builder(
            itemCount: snapshot.data!.length, // Number of favorite movies.
            itemBuilder: (context, index) {
              final movie = snapshot
                  .data![index]; // Fetches the movie at the current index.

              // Displays each movie as a ListTile with an image, title, and year.
              return ListTile(
                leading:
                    Image.network(movie.poster), // Displays the movie poster.
                title: Text(movie.title), // Displays the movie title.
                subtitle: Text(movie.year), // Displays the movie release year.

                // Navigates to the MovieDetailsScreen when tapped.
                onTap: () {
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => MovieDetailsScreen(
                          imdbID: movie.imdbID), // Passes the IMDb ID.
                    ),
                  );
                },
              );
            },
          );
        },
      ),
    );
  }
}

Screenshot of the Favorites Screen:

favorites

Main Application File Code

After successfully creating Models, Service, and Helper files, we can finally utilize them to create a fully functional Application.

Finally, let's Call Our HomeScreen from the main.dart file.

main.dart

main.dart
import 'package:flutter/material.dart';
import 'package:movies/screens/home_screen.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        debugShowCheckedModeBanner: false,
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
          useMaterial3: true,
        ),
        home: const HomeScreen());
  }
}

Complete code for the Application Here : Movie_Database_Application

Output :



Next Article

Similar Reads