본문 바로가기

프로그래밍/영화 TMDB API

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

반응형

TV 카테고리 추가

 

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

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

 

영화정보 - Google Play 앱

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

play.google.com

 

TV Top rated, TV On the Air, TV Airing Today, Discover API 를 사용하여 TV 카테고리도 추가해 보겠습니다.

 

 

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

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

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

    @GET("tv/top_rated")
    fun getTopRatedTV(
        @Query("api_key") apiKey: String = "348eefabae0631d8003f24551c45a05c",
        @Query("page") page : Int,
        @Query("language") language : String = "ko,en-US"
    ): Call<GetTVResponse>

    @GET("tv/on_the_air")
    fun getOnTheAirTV(
        @Query("api_key") apiKey: String = "348eefabae0631d8003f24551c45a05c",
        @Query("page") page : Int,
        @Query("language") language : String = "ko,en-US"
    ): Call<GetTVResponse>

    @GET("tv/airing_today")
    fun getAiringTodayTV(
        @Query("api_key") apiKey: String = "348eefabae0631d8003f24551c45a05c",
        @Query("page") page : Int,
        @Query("language") language : String = "ko,en-US"
    ): Call<GetTVResponse>

    @GET("discover/tv")
    fun getDiscoverTV(
        @Query("api_key") apiKey: String = "348eefabae0631d8003f24551c45a05c",
        @Query("page") page : Int,
        @Query("language") language : String = "ko,en-US"
    ): Call<GetTVResponse>

 

 

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

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

    fun getTopRatedTV(page: Int = 1,
                      onSuccess: (tvlist: List<TV>) -> Unit,
                      onError: () -> Unit){
        api.getTopRatedTV(page = page)
            .enqueue(object : Callback<GetTVResponse> {
                override fun onResponse(
                    call: Call<GetTVResponse>,
                    response: Response<GetTVResponse>
                ) {
                    if(response.isSuccessful) {
                        val responseBody = response.body()
                        if(responseBody != null) {
                            onSuccess.invoke(responseBody.tvlist)
                        } else {
                            onError.invoke()
                        }
                    } else {
                        onError.invoke()
                    }
                }

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

    fun getOnTheAirTV(page: Int = 1,
                      onSuccess: (tvlist: List<TV>) -> Unit,
                      onError: () -> Unit){
        api.getOnTheAirTV(page = page)
            .enqueue(object : Callback<GetTVResponse> {
                override fun onResponse(
                    call: Call<GetTVResponse>,
                    response: Response<GetTVResponse>
                ) {
                    if(response.isSuccessful) {
                        val responseBody = response.body()
                        if(responseBody != null) {
                            onSuccess.invoke(responseBody.tvlist)
                        } else {
                            onError.invoke()
                        }
                    } else {
                        onError.invoke()
                    }
                }

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

    fun getAiringTodayTV(page: Int = 1,
                         onSuccess: (tvlist: List<TV>) -> Unit,
                         onError: () -> Unit){
        api.getAiringTodayTV(page = page)
            .enqueue(object : Callback<GetTVResponse> {
                override fun onResponse(
                    call: Call<GetTVResponse>,
                    response: Response<GetTVResponse>
                ) {
                    if(response.isSuccessful) {
                        val responseBody = response.body()
                        if(responseBody != null) {
                            onSuccess.invoke(responseBody.tvlist)
                        } else {
                            onError.invoke()
                        }
                    } else {
                        onError.invoke()
                    }
                }

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

    fun getDiscoverTV(page: Int = 1,
                      onSuccess: (tvlist: List<TV>) -> Unit,
                      onError: () -> Unit){
        api.getDiscoverTV(page = page)
            .enqueue(object : Callback<GetTVResponse> {
                override fun onResponse(
                    call: Call<GetTVResponse>,
                    response: Response<GetTVResponse>
                ) {
                    if(response.isSuccessful) {
                        val responseBody = response.body()
                        if(responseBody != null) {
                            onSuccess.invoke(responseBody.tvlist)
                        } else {
                            onError.invoke()
                        }
                    } else {
                        onError.invoke()
                    }
                }

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

 

 

3. Strings 추가

    <string name="on_the_air">On The Air</string>
    <string name="airing_today">Airing Today</string>

 

Movie와 다른 카테고리만 추가했습니다.

 

 

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

fragment_home.xml 에 layout 을 추가해 줍니다.

            <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_tv" />

                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/top_rated_tv"
                    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/on_the_air"
                    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/on_the_air" />

                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/on_the_air_tv"
                    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/airing_today"
                    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/airing_today" />

                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/airing_today_tv"
                    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_tv"
                    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. HomeFragment.kt 에 카테고리별 실행 코드 구현

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

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

 

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

    private lateinit var popularTV: RecyclerView
    private lateinit var popularTVAdapter: TVAdapter
    private lateinit var popularTVLayoutMgr: LinearLayoutManager
    private var popularTVPage = 1

    private lateinit var topRatedTV: RecyclerView
    private lateinit var topRatedTVAdapter: TVAdapter
    private lateinit var topRatedTVLayoutMgr: LinearLayoutManager
    private var topRatedTVPage = 1

    private lateinit var onTheAirTV: RecyclerView
    private lateinit var onTheAirTVAdapter: TVAdapter
    private lateinit var onTheAirTVLayoutMgr: LinearLayoutManager
    private var onTheAirTVPage = 1

    private lateinit var airingTodayTV: RecyclerView
    private lateinit var airingTodayTVAdapter: TVAdapter
    private lateinit var airingTodayTVLayoutMgr: LinearLayoutManager
    private var airingTodayTVPage = 1

    private lateinit var discoverTV: RecyclerView
    private lateinit var discoverTVAdapter: TVAdapter
    private lateinit var discoverTVLayoutMgr: LinearLayoutManager
    private var discoverTVPage = 1


    override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle? ): View? {

        root = inflater.inflate(R.layout.fragment_home, container, false)

        popularTV = root.findViewById(R.id.popular_tv)
        popularTVLayoutMgr = LinearLayoutManager(
            context,
            LinearLayoutManager.HORIZONTAL,
            false
        )
        popularTV.layoutManager = popularTVLayoutMgr
        popularTVAdapter = TVAdapter(mutableListOf()) { tv -> showTVDetails(tv) }
        popularTV.adapter = popularTVAdapter

        getPopularTV()


        topRatedTV = root.findViewById(R.id.top_rated_tv)
        topRatedTVLayoutMgr = LinearLayoutManager(
            context,
            LinearLayoutManager.HORIZONTAL,
            false
        )
        topRatedTV.layoutManager = topRatedTVLayoutMgr
        topRatedTVAdapter = TVAdapter(mutableListOf()) { tv -> showTVDetails(tv) }
        topRatedTV.adapter = topRatedTVAdapter

        getTopRatedTV()


        onTheAirTV = root.findViewById(R.id.on_the_air_tv)
        onTheAirTVLayoutMgr = LinearLayoutManager(
            context,
            LinearLayoutManager.HORIZONTAL,
            false
        )
        onTheAirTV.layoutManager = onTheAirTVLayoutMgr
        onTheAirTVAdapter = TVAdapter(mutableListOf()) { tv -> showTVDetails(tv) }
        onTheAirTV.adapter = onTheAirTVAdapter

        getOnTheAirTV()


        airingTodayTV = root.findViewById(R.id.airing_today_tv)
        airingTodayTVLayoutMgr = LinearLayoutManager(
            context,
            LinearLayoutManager.HORIZONTAL,
            false
        )
        airingTodayTV.layoutManager = airingTodayTVLayoutMgr
        airingTodayTVAdapter = TVAdapter(mutableListOf()) { tv -> showTVDetails(tv) }
        airingTodayTV.adapter = airingTodayTVAdapter

        getAiringTodayTV()


        discoverTV = root.findViewById(R.id.discover_tv)
        discoverTVLayoutMgr = LinearLayoutManager(
            context,
            LinearLayoutManager.HORIZONTAL,
            false
        )
        discoverTV.layoutManager = discoverTVLayoutMgr
        discoverTVAdapter = TVAdapter(mutableListOf()) { tv -> showTVDetails(tv) }
        discoverTV.adapter = discoverTVAdapter

        getDiscoverTV()


        return root
    }


    private fun getPopularTV() {
        TVRepository.getPopularTV(
            popularTVPage,
            ::onPopularTVFetched,
            ::onError
        )
    }

    private fun onPopularTVFetched(tvlist: List<TV>) {
        popularTVAdapter.appendTV(tvlist)
        attachPopularTVOnScrollListener()
    }

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

                if (firstVisibleItem + visibleItemCount >= totalItemCount / 2) {
                    popularTV.removeOnScrollListener(this)
                    popularTVPage++
                    getPopularTV()
                }
            }
        })
    }

    private fun getTopRatedTV() {
        TVRepository.getTopRatedTV(
            topRatedTVPage,
            ::onTopRatedTVFetched,
            ::onError
        )
    }

    private fun onTopRatedTVFetched(tvlist: List<TV>) {
        topRatedTVAdapter.appendTV(tvlist)
        attachTopRatedTVOnScrollListener()
    }

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

                if (firstVisibleItem + visibleItemCount >= totalItemCount / 2) {
                    topRatedTV.removeOnScrollListener(this)
                    topRatedTVPage++
                    getTopRatedTV()
                }
            }
        })
    }

    private fun getOnTheAirTV() {
        TVRepository.getOnTheAirTV(
            onTheAirTVPage,
            ::onOnTheAirTVFetched,
            ::onError
        )
    }

    private fun onOnTheAirTVFetched(tvlist: List<TV>) {
        onTheAirTVAdapter.appendTV(tvlist)
        attachOnTheAirTVOnScrollListener()
    }

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

                if (firstVisibleItem + visibleItemCount >= totalItemCount / 2) {
                    onTheAirTV.removeOnScrollListener(this)
                    onTheAirTVPage++
                    getOnTheAirTV()
                }
            }
        })
    }


    private fun getAiringTodayTV() {
        TVRepository.getAiringTodayTV(
            airingTodayTVPage,
            ::onAiringTodayTVFetched,
            ::onError
        )
    }

    private fun onAiringTodayTVFetched(tvlist: List<TV>) {
        airingTodayTVAdapter.appendTV(tvlist)
        attachAiringTodayTVOnScrollListener()
    }

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

                if (firstVisibleItem + visibleItemCount >= totalItemCount / 2) {
                    airingTodayTV.removeOnScrollListener(this)
                    airingTodayTVPage++
                    getAiringTodayTV()
                }
            }
        })
    }

    private fun getDiscoverTV() {
        TVRepository.getDiscoverTV(
            discoverTVPage,
            ::onDiscoverTVFetched,
            ::onError
        )
    }

    private fun onDiscoverTVFetched(tvlist: List<TV>) {
        discoverTVAdapter.appendTV(tvlist)
        attachDiscoverTVOnScrollListener()
    }

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

                if (firstVisibleItem + visibleItemCount >= totalItemCount / 2) {
                    discoverTV.removeOnScrollListener(this)
                    discoverTVPage++
                    getDiscoverTV()
                }
            }
        })
    }


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

    private fun showTVDetails(tv: TV) {
        val intent = Intent(activity, MovieDetailsActivity::class.java)
        intent.putExtra(MainActivity.MOVIE_BACKDROP, tv.backdrop_path)
        intent.putExtra(MainActivity.MOVIE_POSTER, tv.poster_path)
        intent.putExtra(MainActivity.MOVIE_TITLE, tv.name)
        intent.putExtra(MainActivity.MOVIE_RATING, tv.rating)
        intent.putExtra(MainActivity.MOVIE_RELEASE_DATE, tv.first_air_date)
        intent.putExtra(MainActivity.MOVIE_OVERVIEW, tv.overview)
        startActivity(intent)
    }
}

 

 

6. HomeFragment를 재활용 했지만 그래도 TV로 표시되도록 일부 수정해 줍니다.

 

String을 아래와 같이 수정합니다.

<string name="title_home">TV</string>

 

Vector Asset 도 live tv 아이콘을 선택하여, ic_tv_black_24dp 이름으로 생성합니다.

bottom_nav_menu.xml 파일에서 

ic_home_black_24dp 을 ic_tv_black_24dp 로 수정합니다.

 

실행해 보면 영화와 마찬가지로 잘 나옵니다.

 

MovieDetailsActivity를 그대로 사용하였기 때문에 상세화면으로도 잘 이동합니다. 

 

 

다음은 검색 기능 API 를 사용하여 구성해 보겠습니다.

 

TMDB API 를 활용한 Android 앱 만들기

https://stockant.tistory.com/530

 

 

반응형