In the modern digital era, the demand for movie apps is soaring. These applications provide users with an immersive experience, allowing them to discover, explore, and enjoy a wide range of movies. Jetpack Compose, a declarative UI toolkit for Android, has gained immense popularity due to its simplicity, flexibility, and powerful features.
In this article, we will explore how to create a movie app using Jetpack Compose. A sample video is given at the end to get an idea about what we are going to do in this article.
Step by Step Implementation
Step 1: Create a New Project in Android Studio
To create a new project in the Android Studio, please refer to How to Create a new Project in Android Studio with Jetpack Compose.
Step 2: Add dependencies for navigation and image loading
Navigate to Gradle Scripts > build.gradle.kts (Module :app) and the following dependencies under dependencies{} tag.
dependencies {
...
implementation ("androidx.navigation:navigation-compose:2.7.7")
implementation("io.coil-kt:coil-compose:2.4.0")
}
Step 3: Setup Navigation
Create a Kotlin file with the name Navigation.kt to setup navigation using the NavHost composable. Create an enum class in kotlin with the name MovieScreens.kt to define screens.
package com.geeksforgeeks.demo
import androidx.compose.runtime.Composable
import androidx.navigation.NavType
import androidx.navigation.compose.*
import androidx.navigation.navArgument
@Composable
fun MovieNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = MovieScreens.HomeScreen.name) {
composable(MovieScreens.HomeScreen.name) {
MovieListScreen(navController = navController)
}
composable(
MovieScreens.DetailsScreen.name + "/{movie}",
arguments = listOf(navArgument("movie") { type = NavType.StringType })
) { backStackEntry ->
DetailsScreen(
navController = navController,
movieId = backStackEntry.arguments?.getString("movie")
)
}
}
}
package com.geeksforgeeks.demo
import androidx.compose.runtime.Composable
import androidx.navigation.NavType
import androidx.navigation.compose.*
import androidx.navigation.navArgument
@Composable
fun MovieNavigation() {
val navController = rememberNavController()
// Define navigation graph
NavHost(
navController = navController,
// Initial screen
startDestination = MovieScreens.HomeScreen.name
) {
// Home screen route
composable(MovieScreens.HomeScreen.name) {
MovieListScreen(navController = navController)
}
// Details screen route with movie ID as argument
composable(
route = MovieScreens.DetailsScreen.name + "/{movie}",
arguments = listOf(navArgument("movie") { type = NavType.StringType })
) { backStackEntry ->
DetailsScreen(
navController = navController,
// Get movie ID from route
movieId = backStackEntry.arguments?.getString("movie")
)
}
}
}
Step 4: Create a data class to store Movie data
Create a kotlin data class with the name Movie.kt and create a function inside the file with the name getMovies() where we are going to add some movie data. Add the following code inside the file.
Movie.kt:
package com.geeksforgeeks.demo
// Data model representing a Movie
data class Movie(
val id: String,
val title: String,
val year: String,
val genre: String,
val director: String,
val actors: String,
val plot: String,
// URL to the main poster image
val poster: String,
// List of additional images
val images: List<String>,
val rating: String
)
// Returns a hardcoded list of Movie objects
fun getMovies() : List<Movie> {
return listOf(
Movie(
id = "101",
title = "Avatar",
images = listOf(
"https://images-na.ssl-images-amazon.com/images/M/MV5BMjEyOTYyMzUxNl5BMl5BanBnXkFtZTcwNTg0MTUzNA@@._V1_SX1500_CR0,0,1500,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BNzM2MDk3MTcyMV5BMl5BanBnXkFtZTcwNjg0MTUzNA@@._V1_SX1777_CR0,0,1777,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTY2ODQ3NjMyMl5BMl5BanBnXkFtZTcwODg0MTUzNA@@._V1_SX1777_CR0,0,1777,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTMxOTEwNDcxN15BMl5BanBnXkFtZTcwOTg0MTUzNA@@._V1_SX1777_CR0,0,1777,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTYxMDg1Nzk1MV5BMl5BanBnXkFtZTcwMDk0MTUzNA@@._V1_SX1500_CR0,0,1500,999_AL_.jpg"
),
year = "2009",
genre = "Action",
director = "James Cameron",
actors = "Sam Worthington, Zoe Saldana, Sigourney Weaver, Stephen Lang",
plot = "A paraplegic marine dispatched to the moon Pandora on a unique mission becomes torn between following his orders and protecting the world he feels is his home.",
poster = "https://m.media-amazon.com/images/M/MV5BMDEzMmQwZjctZWU2My00MWNlLWE0NjItMDJlYTRlNGJiZjcyXkEyXkFqcGc@._V1_.jpg",
rating = "7.9"
) ,
Movie(
id = "102",
title = "I Am Legend",
images = listOf(
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTI0NTI4NjE3NV5BMl5BanBnXkFtZTYwMDA0Nzc4._V1_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTIwMDg2MDU4M15BMl5BanBnXkFtZTYwMTA0Nzc4._V1_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTc5MDM1OTU5OV5BMl5BanBnXkFtZTYwMjA0Nzc4._V1_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTA0MTI2NjMzMzFeQTJeQWpwZ15BbWU2MDMwNDc3OA@@._V1_.jpg"
),
year = "2007",
genre = "Drama, Horror, Sci-Fi",
director = "Francis Lawrence",
actors = "Will Smith, Alice Braga, Charlie Tahan, Salli Richardson-Whitfield",
plot = "Years after a plague kills most of humanity and transforms the rest into monsters, the sole survivor in New York City struggles valiantly to find a cure.",
poster = "https://m.media-amazon.com/images/M/MV5BMGE1OWZkZmItNmVhMC00YzAxLTgxOTctNjg3NWExM2RmOWJkXkEyXkFqcGc@._V1_.jpg",
rating = "9.8"
) ,
Movie (
id = "103",
title ="The Avengers",
year = "2012",
director = "Joss Whedon",
images = listOf(
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTA0NjY0NzE4OTReQTJeQWpwZ15BbWU3MDczODg2Nzc@._V1_SX1777_CR0,0,1777,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMjE1MzEzMjcyM15BMl5BanBnXkFtZTcwNDM4ODY3Nw@@._V1_SX1777_CR0,0,1777,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMjMwMzM2MTg1M15BMl5BanBnXkFtZTcwNjM4ODY3Nw@@._V1_SX1777_CR0,0,1777,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTQ4NzM2Mjc5MV5BMl5BanBnXkFtZTcwMTkwOTY3Nw@@._V1_SX1777_CR0,0,1777,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTc3MzQ3NjA5N15BMl5BanBnXkFtZTcwMzY5OTY3Nw@@._V1_SX1777_CR0,0,1777,999_AL_.jpg"
) ,
genre = "Action, Sci-Fi, Thriller",
actors = "Robert Downey Jr., Chris Evans, Mark Ruffalo, Chris Hemsworth",
plot = "Earth's mightiest heroes must come together and learn to fight as a team if they are to stop the mischievous Loki and his alien army from enslaving humanity.",
poster = "https://m.media-amazon.com/images/M/MV5BNGE0YTVjNzUtNzJjOS00NGNlLTgxMzctZTY4YTE1Y2Y1ZTU4XkEyXkFqcGc@._V1_FMjpg_UX1000_.jpg",
rating = "8.1",
),
Movie(
id = "104",
title ="The Wolf of Wall Street",
year = "2013",
director = "Martin Scorsese",
images = listOf(
"https://images-na.ssl-images-amazon.com/images/M/MV5BNDIwMDIxNzk3Ml5BMl5BanBnXkFtZTgwMTg0MzQ4MDE@._V1_SX1500_CR0,0,1500,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTc0NzAxODAyMl5BMl5BanBnXkFtZTgwMDg0MzQ4MDE@._V1_SX1500_CR0,0,1500,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTExMDk1MDE4NzVeQTJeQWpwZ15BbWU4MDM4NDM0ODAx._V1_SX1500_CR0,0,1500,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTg3MTY4NDk4Nl5BMl5BanBnXkFtZTgwNjc0MzQ4MDE@._V1_SX1500_CR0,0,1500,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTgzMTg4MDI0Ml5BMl5BanBnXkFtZTgwOTY0MzQ4MDE@._V1_SY1000_CR0,0,1553,1000_AL_.jpg"
) ,
genre = "Biography, Comedy, Crime",
actors = "Leonardo DiCaprio, Jonah Hill, Margot Robbie, Matthew McConaughey",
plot = "Based on the true story of Jordan Belfort, from his rise to a wealthy stock-broker living the high life to his fall involving crime, corruption and the federal government.",
poster = "https://m.media-amazon.com/images/M/MV5BMjIxMjgxNTk0MF5BMl5BanBnXkFtZTgwNjIyOTg2MDE@._V1_FMjpg_UX1000_.jpg",
rating = "8.2",
) ,
Movie(
id = "105",
title ="Interstellar",
year = "2014",
director = "Christopher Nolan",
images = listOf(
"https://images-na.ssl-images-amazon.com/images/M/MV5BMjA3NTEwOTMxMV5BMl5BanBnXkFtZTgwMjMyODgxMzE@._V1_SX1500_CR0,0,1500,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMzQ5ODE2MzEwM15BMl5BanBnXkFtZTgwMTMyODgxMzE@._V1_SX1500_CR0,0,1500,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMTg4Njk4MzY0Nl5BMl5BanBnXkFtZTgwMzIyODgxMzE@._V1_SX1500_CR0,0,1500,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BMzE3MTM0MTc3Ml5BMl5BanBnXkFtZTgwMDIyODgxMzE@._V1_SX1500_CR0,0,1500,999_AL_.jpg",
"https://images-na.ssl-images-amazon.com/images/M/MV5BNjYzNjE2NDk3N15BMl5BanBnXkFtZTgwNzEyODgxMzE@._V1_SX1500_CR0,0,1500,999_AL_.jpg"
) ,
genre = "Adventure, Drama, Sci-Fi",
actors = "Ellen Burstyn, Matthew McConaughey, Mackenzie Foy, John Lithgow",
plot = "A team of explorers travel through a wormhole in space in an attempt to ensure humanity's survival.",
poster = "https://m.media-amazon.com/images/M/MV5BYzdjMDAxZGItMjI2My00ODA1LTlkNzItOWFjMDU5ZDJlYWY3XkEyXkFqcGc@._V1_FMjpg_UX1000_.jpg",
rating = "8.6",
)
)
}
Step 5: Setup Home Screen
Navigate to app > kotlin+java > {package-name} > MainActivity.kt and create a composable to setup the home screen. In the home screen, setup a LazyColumn that navigates to the details screen on click. To design the items of the LazyColumn, create a kotlin file with the name MovieRow.kt and create a composable. This composable will contain an Image of the movie poster on the left and three texts on the right which mentions the Movie Title, Genre and Rating.
package com.geeksforgeeks.demo
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// Set the UI content using Compose and apply Material theme
MaterialTheme {
// Entry point to navigation
MovieNavigation()
}
}
}
}
@Composable
fun MovieListScreen(navController: NavController, movieList: List<Movie> = getMovies()) {
// Column to add padding around the movie list
Column(modifier = Modifier.padding(12.dp)) {
// LazyColumn to display a scrollable list of movies
LazyColumn {
items(movieList) { movie ->
// Reusable row component for each movie
MovieRow(movie = movie) { movieId ->
// Navigate to the DetailsScreen when a movie is clicked
navController.navigate(route = MovieScreens.DetailsScreen.name + "/$movieId")
}
}
}
}
}
package com.geeksforgeeks.demo
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
@Composable
fun MovieRow(movie: Movie, onItemClick: (String) -> Unit) {
// Card representing a single movie item
Card(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
.clickable { onItemClick(movie.id) }, // Navigate on click
colors = CardDefaults.cardColors(containerColor = Color.LightGray)
) {
Row(modifier = Modifier.padding(16.dp)) {
// Movie poster image
AsyncImage(
model = movie.poster,
contentDescription = movie.title,
modifier = Modifier.height(100.dp)
)
Spacer(modifier = Modifier.width(10.dp))
// Movie details: title, genre, rating
Column {
Text(text = movie.title, style = MaterialTheme.typography.titleMedium)
Text(text = "Genre: ${movie.genre}", style = MaterialTheme.typography.bodySmall)
Text(text = "Rating: ${movie.rating}", style = MaterialTheme.typography.bodySmall)
}
}
}
}
Step 6: Setup Details Screen
Create a new kotlin file to setup the Details Screen of movies. Set the name of the file and composable as DetailsScreen.kt. The composable will contain a Top Bar with a back stack navigation and the details of the movies. Use a LazyHorizontalGrid to display multiple images of a movie in Horizontal Grid with 2 maximum rows.
DetailsScreen.kt:
package com.geeksforgeeks.demo
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.grid.*
import androidx.compose.foundation.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import coil.compose.AsyncImage
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DetailsScreen(navController: NavController, movieId: String?) {
// Find the movie based on passed ID
val movie = getMovies().firstOrNull { it.id == movieId }
// Show fallback text if movie not found
if (movie == null) {
Text("Movie not found")
return
}
Scaffold(
topBar = {
TopAppBar(
title = { Text("Movies", modifier = Modifier.padding(start = 8.dp)) },
navigationIcon = {
// Back navigation icon
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Back",
modifier = Modifier
.clickable { navController.popBackStack() }
.padding(12.dp)
)
}
)
}
) { paddingValues ->
Column(
modifier = Modifier
.padding(paddingValues)
.verticalScroll(rememberScrollState())
.fillMaxSize()
) {
// Movie poster and title section
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
AsyncImage(
model = movie.poster,
contentDescription = movie.title,
modifier = Modifier.height(200.dp)
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = movie.title,
style = MaterialTheme.typography.headlineSmall,
fontWeight = androidx.compose.ui.text.font.FontWeight.Bold
)
}
// Divider between poster and movie details
HorizontalDivider(thickness = 1.dp, color = Color.LightGray, modifier = Modifier.padding(12.dp))
// Movie details (text info)
Column(modifier = Modifier.padding(12.dp, 0.dp)) {
Text(text = "Year: ${movie.year}", style = MaterialTheme.typography.labelLarge)
Text(text = "Genre: ${movie.genre}", style = MaterialTheme.typography.labelLarge)
Text(text = "Rating: ${movie.rating}", style = MaterialTheme.typography.labelLarge)
Text(text = "Actors: ${movie.actors}", style = MaterialTheme.typography.labelLarge)
Text(text = "Director: ${movie.director}", style = MaterialTheme.typography.labelLarge)
Text(text = "Plot: ${movie.plot}", style = MaterialTheme.typography.labelLarge)
Spacer(modifier = Modifier.height(8.dp))
}
// Section title for images
Text(
text = "Movie Images",
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier.padding(12.dp, 0.dp)
)
// Horizontal grid of additional movie images
HorizontalScrollableImageGrid(listOf(movie))
}
}
}
@Composable
fun HorizontalScrollableImageGrid(movies: List<Movie>) {
val images = movies[0].images
// Lazy horizontal grid displaying movie images
LazyHorizontalGrid(
rows = GridCells.Fixed(2),
modifier = Modifier.height(300.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(12.dp)
) {
items(images.size) { index ->
val image = images[index]
Card(
modifier = Modifier.size(240.dp, 140.dp),
colors = CardDefaults.cardColors(containerColor = Color.White)
) {
AsyncImage(
model = image,
contentDescription = "Movie Poster",
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
}
}
}
}
Output:
Refer to the following github repo to get the entire code: Movies-App-Jetpack-Compose