본문 바로가기

프로그래밍/영화 TMDB API

영화 정보 앱 만들기 - TMDB API 사용, 영화 카테고리 추가

반응형

영화 정보 카테고리를 추가해 보겠습니다.

 

완성하면 아래 앱이 됩니다.

https://play.google.com/store/apps/details?id=com.enigmah2k.movieinfo

 

영화정보 - Google Play 앱

영화 또는 TV 시리즈 정보를 검색할 수 있습니다. TMDB API를 사용하여 만들었습니다. 한국에 소개되지 않은 컨텐츠를 찾을 수 있습니다.

play.google.com

 

popular 정보를 가져온 형식을 그대로 활용하여 Top rated, Upcomming, Now playing, Discover 카테고리를 추가하겠습니다.

 

1. Api.kt 에 사용 API 를 추가합니다.

API 레퍼런스 사이트에서 Movies 메뉴를 참고합니다.

결과적으로 GET 하는 API name 만 다를 뿐 나머지 파라메터는 동일합니다.

    @GET("movie/top_rated")
    fun getTopRatedMovies(
        @Query("api_key") apiKey: String = "<< API KEY >>",
        @Query("page") page : Int,
        @Query("language") language : String = "ko,en-US"
    ): Call<GetMoviesResponse>

    @GET("movie/upcoming")
    fun getUpcomingMovies(
        @Query("api_key") apiKey: String = "<< API KEY >>",
        @Query("page") page : Int,
        @Query("language") language : String = "ko,en-US"
    ): Call<GetMoviesResponse>

    @GET("movie/now_playing")
    fun getNowPlayingMovies(
        @Query("api_key") apiKey: String = "<< API KEY >>",
        @Query("page") page : Int,
        @Query("language") language : String = "ko,en-US"
    ): Call<GetMoviesResponse>

    @GET("discover/movie")
    fun getDiscoverMovies(
        @Query("api_key") apiKey: String = "<< API KEY >>",
        @Query("page") page : Int,
        @Query("language") language : String = "ko,en-US"
    ): Call<GetMoviesResponse>

 

2. MoviesRepository 클래스에 API 호출 함수들을 추가합니다.

함수명만 다르고 구조는 동일하여 중목 코드 같지만 어쩔수 없습니다.

    fun getTopRatedMovies(page: Int = 1,
                          onSuccess: (movies: List<Movie>) -> Unit,
                          onError: () -> Unit ) {
        api.getTopRatedMovies(page = page)
            .enqueue(object : Callback<GetMoviesResponse> {
                override fun onResponse(
                    call: Call<GetMoviesResponse>,
                    response: Response<GetMoviesResponse>
                ) {
                    if (response.isSuccessful) {
                        val responseBody = response.body()

                        if (responseBody != null) {
                            onSuccess.invoke(responseBody.movies)
                        } else {
                            onError.invoke()
                        }
                    } else {
                        onError.invoke()
                    }
                }

                override fun onFailure(call: Call<GetMoviesResponse>, t: Throwable) {
                    onError.invoke()
                }
            })
    }

    fun getUpcomingMovies( page: Int = 1,
                           onSuccess: (movies: List<Movie>) -> Unit,
                           onError: () -> Unit ) {
        api.getUpcomingMovies(page = page)
            .enqueue(object : Callback<GetMoviesResponse> {
                override fun onResponse(
                    call: Call<GetMoviesResponse>,
                    response: Response<GetMoviesResponse>
                ) {
                    if (response.isSuccessful) {
                        val responseBody = response.body()

                        if (responseBody != null) {
                            onSuccess.invoke(responseBody.movies)
                        } else {
                            onError.invoke()
                        }
                    } else {
                        onError.invoke()
                    }
                }

                override fun onFailure(call: Call<GetMoviesResponse>, t: Throwable) {
                    onError.invoke()
                }
            })
    }

    fun getNowPlayingMovies( page: Int = 1,
                             onSuccess: (movies: List<Movie>) -> Unit,
                             onError: () -> Unit ) {
        api.getNowPlayingMovies(page = page)
            .enqueue(object : Callback<GetMoviesResponse> {
                override fun onResponse(
                    call: Call<GetMoviesResponse>,
                    response: Response<GetMoviesResponse>
                ) {
                    if (response.isSuccessful) {
                        val responseBody = response.body()

                        if (responseBody != null) {
                            onSuccess.invoke(responseBody.movies)
                        } else {
                            onError.invoke()
                        }
                    } else {
                        onError.invoke()
                    }
                }

                override fun onFailure(call: Call<GetMoviesResponse>, t: Throwable) {
                    onError.invoke()
                }
            })
    }

    fun getDiscoverMovies( page: Int = 1,
                           onSuccess: (movies: List<Movie>) -> Unit,
                           onError: () -> Unit ) {
        api.getDiscoverMovies(page = page)
            .enqueue(object : Callback<GetMoviesResponse> {
                override fun onResponse(
                    call: Call<GetMoviesResponse>,
                    response: Response<GetMoviesResponse>
                ) {
                    if (response.isSuccessful) {
                        val responseBody = response.body()

                        if (responseBody != null) {
                            onSuccess.invoke(responseBody.movies)
                        } else {
                            onError.invoke()
                        }
                    } else {
                        onError.invoke()
                    }
                }

                override fun onFailure(call: Call<GetMoviesResponse>, t: Throwable) {
                    onError.invoke()
                }
            })
    }

 

3. Strings 추가

 

<string name="toprated">Top Rated 평점</string>
<string name="most_toprated">최고 평점 영화</string>
<string name="upcoming">Upcoming</string>
<string name="now_playing">Now Playing</string>
<string name="discover">Discover</string>

 

한글로 해야 하는데 편의상 같은 값으로 사용해야 겠습니다.

 

 

4. 카테고리 레이아웃 추가

fragment_movie.xml 에 카테고리 레이아웃을 추가합니다.

popular layout 과 동일하게 하여 id 값만 구분하였습니다.

            <LinearLayout
                android:id="@+id/linearLayout2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintTop_toBottomOf="@+id/linearLayout">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="16dp"
                    android:layout_marginTop="16dp"
                    android:layout_marginEnd="16dp"
                    android:text="@string/toprated"
                    android:textSize="18sp"
                    android:textStyle="bold" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="16dp"
                    android:layout_marginEnd="16dp"
                    android:text="@string/most_toprated" />

                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/top_rated_movies"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"
                    android:clipToPadding="false"
                    android:paddingStart="16dp"
                    android:paddingEnd="16dp" />

            </LinearLayout>

            <LinearLayout
                android:id="@+id/linearLayout3"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintTop_toBottomOf="@+id/linearLayout2">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="16dp"
                    android:layout_marginTop="16dp"
                    android:layout_marginEnd="16dp"
                    android:text="@string/upcoming"
                    android:textSize="18sp"
                    android:textStyle="bold" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="16dp"
                    android:layout_marginEnd="16dp"
                    android:text="@string/upcoming" />

                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/upcoming_movies"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"
                    android:clipToPadding="false"
                    android:paddingStart="16dp"
                    android:paddingEnd="16dp" />

            </LinearLayout>

            <LinearLayout
                android:id="@+id/linearLayout4"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintTop_toBottomOf="@+id/linearLayout3">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="16dp"
                    android:layout_marginTop="16dp"
                    android:layout_marginEnd="16dp"
                    android:text="@string/now_playing"
                    android:textSize="18sp"
                    android:textStyle="bold" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="16dp"
                    android:layout_marginEnd="16dp"
                    android:text="@string/now_playing" />

                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/nowplaying_movies"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"
                    android:clipToPadding="false"
                    android:paddingStart="16dp"
                    android:paddingEnd="16dp" />

            </LinearLayout>

            <LinearLayout
                android:id="@+id/linearLayout5"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                app:layout_constraintTop_toBottomOf="@+id/linearLayout4">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="16dp"
                    android:layout_marginTop="16dp"
                    android:layout_marginEnd="16dp"
                    android:text="@string/discover"
                    android:textSize="18sp"
                    android:textStyle="bold" />

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="16dp"
                    android:layout_marginEnd="16dp"
                    android:text="@string/discover" />

                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/discover_movies"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"
                    android:clipToPadding="false"
                    android:paddingStart="16dp"
                    android:paddingEnd="16dp" />

            </LinearLayout>

            <LinearLayout
                android:id="@+id/linearLayout6"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                app:layout_constraintTop_toBottomOf="@+id/linearLayout5">

                <TextView
                    android:id="@+id/textView"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="\n\n\n\n"
                    tools:layout_editor_absoluteX="9dp" />

            </LinearLayout>

 

 

5. MovieFragment.kt 에 카테고리별 실행 코드 구현

카테고리별 변수를 선언하고 MoviesAdapter 실행 코드를 구현합니다.

동일한 구조의 코드가 다섯개가 되었네요.

class MovieFragment : Fragment() {
    lateinit var root: View

    private lateinit var popularMovies: RecyclerView
    private lateinit var popularMoviesAdapter: MoviesAdapter
    private lateinit var popularMoviesLayoutMgr: LinearLayoutManager
    private var popularMoviesPage = 1

    private lateinit var topRatedMovies: RecyclerView
    private lateinit var topRatedMoviesAdapter: MoviesAdapter
    private lateinit var topRatedMoviesLayoutMgr: LinearLayoutManager
    private var topRatedMoviesPage = 1

    private lateinit var upcomingMovies: RecyclerView
    private lateinit var upcomingMoviesAdapter: MoviesAdapter
    private lateinit var upcomingMoviesLayoutMgr: LinearLayoutManager
    private var upcomingMoviesPage = 1

    private lateinit var nowplayingMovies: RecyclerView
    private lateinit var nowplayingMoviesAdapter: MoviesAdapter
    private lateinit var nowplayingMoviesLayoutMgr: LinearLayoutManager
    private var nowplayingMoviesPage = 1

    private lateinit var discoverMovies: RecyclerView
    private lateinit var discoverMoviesAdapter: MoviesAdapter
    private lateinit var discoverMoviesLayoutMgr: LinearLayoutManager
    private var discoverMoviesPage = 1

    override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle? ): View? {
        root = inflater.inflate(R.layout.fragment_movie, container, false)

        popularMovies = root.findViewById(R.id.popular_movies)
        popularMoviesLayoutMgr = LinearLayoutManager(
            context,
            LinearLayoutManager.HORIZONTAL,
            false
        )
        popularMovies.layoutManager = popularMoviesLayoutMgr
        popularMoviesAdapter = MoviesAdapter(mutableListOf())
        popularMovies.adapter = popularMoviesAdapter

        getPopularMovies()


        topRatedMovies = root.findViewById(R.id.top_rated_movies)
        topRatedMoviesLayoutMgr = LinearLayoutManager(
            context,
            LinearLayoutManager.HORIZONTAL,
            false
        )
        topRatedMovies.layoutManager = topRatedMoviesLayoutMgr
        topRatedMoviesAdapter = MoviesAdapter(mutableListOf())
        topRatedMovies.adapter = topRatedMoviesAdapter

        getTopRatedMovies()


        upcomingMovies = root.findViewById(R.id.upcoming_movies)
        upcomingMoviesLayoutMgr = LinearLayoutManager(
            context,
            LinearLayoutManager.HORIZONTAL,
            false
        )
        upcomingMovies.layoutManager = upcomingMoviesLayoutMgr
        upcomingMoviesAdapter = MoviesAdapter(mutableListOf())
        upcomingMovies.adapter = upcomingMoviesAdapter

        getUpcomingMovies()


        nowplayingMovies = root.findViewById(R.id.nowplaying_movies)
        nowplayingMoviesLayoutMgr = LinearLayoutManager(
            context,
            LinearLayoutManager.HORIZONTAL,
            false
        )
        nowplayingMovies.layoutManager = nowplayingMoviesLayoutMgr
        nowplayingMoviesAdapter = MoviesAdapter(mutableListOf())
        nowplayingMovies.adapter = nowplayingMoviesAdapter

        getNowplayingMovies()


        discoverMovies = root.findViewById(R.id.discover_movies)
        discoverMoviesLayoutMgr = LinearLayoutManager(
            context,
            LinearLayoutManager.HORIZONTAL,
            false
        )
        discoverMovies.layoutManager = discoverMoviesLayoutMgr
        discoverMoviesAdapter = MoviesAdapter(mutableListOf())
        discoverMovies.adapter = discoverMoviesAdapter

        getDiscoverMovies()


        return root
    }

    private fun getPopularMovies() {
        MoviesRepository.getPopularMovies(
            popularMoviesPage,
            ::onPopularMoviesFetched,
            ::onError
        )
    }

    private fun onPopularMoviesFetched(movies: List<Movie>) {
        popularMoviesAdapter.appendMovies(movies)
        attachPopularMoviesOnScrollListener()
    }

    private fun attachPopularMoviesOnScrollListener() {
        popularMovies.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                val totalItemCount = popularMoviesLayoutMgr.itemCount
                val visibleItemCount = popularMoviesLayoutMgr.childCount
                val firstVisibleItem = popularMoviesLayoutMgr.findFirstVisibleItemPosition()

                if (firstVisibleItem + visibleItemCount >= totalItemCount / 2) {
                    popularMovies.removeOnScrollListener(this)
                    popularMoviesPage++
                    getPopularMovies()
                }
            }
        })
    }


    private fun getTopRatedMovies() {
        MoviesRepository.getTopRatedMovies(
            topRatedMoviesPage,
            ::onTopRatedMoviesFetched,
            ::onError
        )
    }

    private fun attachTopRatedMoviesOnScrollListener() {
        topRatedMovies.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                val totalItemCount = topRatedMoviesLayoutMgr.itemCount
                val visibleItemCount = topRatedMoviesLayoutMgr.childCount
                val firstVisibleItem = topRatedMoviesLayoutMgr.findFirstVisibleItemPosition()

                if (firstVisibleItem + visibleItemCount >= totalItemCount / 2) {
                    topRatedMovies.removeOnScrollListener(this)
                    topRatedMoviesPage++
                    getTopRatedMovies()
                }
            }
        })
    }

    private fun onTopRatedMoviesFetched(movies: List<Movie>) {
        topRatedMoviesAdapter.appendMovies(movies)
        attachTopRatedMoviesOnScrollListener()
    }


    private fun getUpcomingMovies() {
        MoviesRepository.getUpcomingMovies(
            upcomingMoviesPage,
            ::onUpcomingMoviesFetched,
            ::onError
        )
    }

    private fun attachUpcomingMoviesOnScrollListener() {
        upcomingMovies.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                val totalItemCount = upcomingMoviesLayoutMgr.itemCount
                val visibleItemCount = upcomingMoviesLayoutMgr.childCount
                val firstVisibleItem = upcomingMoviesLayoutMgr.findFirstVisibleItemPosition()

                if (firstVisibleItem + visibleItemCount >= totalItemCount / 2) {
                    upcomingMovies.removeOnScrollListener(this)
                    upcomingMoviesPage++
                    getUpcomingMovies()
                }
            }
        })
    }

    private fun onUpcomingMoviesFetched(movies: List<Movie>) {
        upcomingMoviesAdapter.appendMovies(movies)
        attachUpcomingMoviesOnScrollListener()
    }


    private fun getNowplayingMovies() {
        MoviesRepository.getNowPlayingMovies(
            nowplayingMoviesPage,
            ::onNowplayingMoviesFetched,
            ::onError
        )
    }

    private fun attachNowplayingMoviesOnScrollListener() {
        nowplayingMovies.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                val totalItemCount = nowplayingMoviesLayoutMgr.itemCount
                val visibleItemCount = nowplayingMoviesLayoutMgr.childCount
                val firstVisibleItem = nowplayingMoviesLayoutMgr.findFirstVisibleItemPosition()

                if (firstVisibleItem + visibleItemCount >= totalItemCount / 2) {
                    nowplayingMovies.removeOnScrollListener(this)
                    nowplayingMoviesPage++
                    getNowplayingMovies()
                }
            }
        })
    }

    private fun onNowplayingMoviesFetched(movies: List<Movie>) {
        nowplayingMoviesAdapter.appendMovies(movies)
        attachNowplayingMoviesOnScrollListener()
    }


    private fun getDiscoverMovies() {
        MoviesRepository.getDiscoverMovies(
            discoverMoviesPage,
            ::onDiscoverMoviesFetched,
            ::onError
        )
    }

    private fun attachDiscoverMoviesOnScrollListener() {
        discoverMovies.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                val totalItemCount = discoverMoviesLayoutMgr.itemCount
                val visibleItemCount = discoverMoviesLayoutMgr.childCount
                val firstVisibleItem = discoverMoviesLayoutMgr.findFirstVisibleItemPosition()

                if (firstVisibleItem + visibleItemCount >= totalItemCount / 2) {
                    discoverMovies.removeOnScrollListener(this)
                    discoverMoviesPage++
                    getDiscoverMovies()
                }
            }
        })
    }

    private fun onDiscoverMoviesFetched(movies: List<Movie>) {
        discoverMoviesAdapter.appendMovies(movies)
        attachDiscoverMoviesOnScrollListener()
    }
    

    private fun onError() {
        Toast.makeText(activity, "error Movies", Toast.LENGTH_SHORT).show()
    }

}

 

실행해 보면 아래와 같이 카테고리가 추가되어 나타납니다.

다음에는 상세 정보 페이지를 만들어 보겠습니다.

 

 

TMDB API 를 활용한 Android 앱 만들기

https://stockant.tistory.com/530

 

 

반응형