본문 바로가기

프로그래밍/영화 TMDB API

영화 정보 앱 만들기 - TMDB API 사용, TV 정보 popular 추가

반응형

TV 정보 popular 추가 

 

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

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

 

영화정보 - Google Play 앱

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

play.google.com

 

TMDB는 영화 정보 외에 TV 정보도 제공합니다.

영화 정보 페이지와 동일하게 구성하면 됩니다.

복습하는 차원에서 TV 정보 화면을 구현해 보겠습니다.

 

이미 만들어져 있는 HomeFragment 를 사용하겠습니다.

 

1. TV.kt 생성

TV 정보를 담을 class를 만듭니다.

common 폴더에서 마우스 오른쪽 클릭을 하여 TV class 를 생성합니다. 

 

소스코드는 아래와 같이 작성했습니다.

data class TV (
    @SerializedName("id") val id : Long,
    @SerializedName("name") val name : String,
    @SerializedName("overview") val overview : String,
    @SerializedName("poster_path") val poster_path: String,
    @SerializedName("backdrop_path") val backdrop_path: String,
    @SerializedName("vote_average") val rating: Float,
    @SerializedName("vote_count") val vcount: Long,
    @SerializedName("first_air_date") val first_air_date: String,
    @SerializedName("popularity") val prating: Float
) {}

 

Movie와 차이점은 title 을 name으로, release_date 를 first_air_date 로 변경했습니다.

https://developers.themoviedb.org/3/tv/get-popular-tv-shows

 

get-popular-tv-shows 의 TV 정보 결과 값입니다.

@SerializedName 는 응답받은 JSON KEY 값과 다른 이름으로 변수를 사용하고 싶을 때 사용합니다.

 

2. 응답을 받는 Class 생성. GetTVResponse.kt

개별 TV 정보 Class는 만들었고, 서버 응답 전체를 저장하는 Class를 만들겠습니다.

common 폴더에서 마우스 오른쪽 클릭하여 GetTVResponse.kt 파일을 생성합니다.

 

소스코드는 아래와 같이 작성했습니다.

data class GetTVResponse (
    @SerializedName("page") val page: Int,
    @SerializedName("results") val tvlist: List<TV>,
    @SerializedName("totla_pages") val pages: Int,
    @SerializedName("totla_results") val results: Int
) {}

get-popular-tv-shows 의 결과인 Responses 내용 전체를 묶었습니다. 

results 값들을 List로 받습니다.

 

 

3. Api interface 에 TV관련 함수 추가

tv popular 함수를 아래와 같이 추가합니다.

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

 

아래와 같이 필요한 파라메터 값을 @Query 로 전달합니다.

region값이 없지만 우리가 만든 API는 movie와 동일합니다.

 

4. 실행 함수 생성 - TVRepository .kt 

Api에 정의된 함수를 직접 실행하는 Object 를 만들겠습니다.

common 폴더에서 마우스 오른쪽 클릭을 하여 New > Kotlin File/Class 를 선택합니다.

아래와 같이 Object 를 선택하고 TVRepository 를 입력하여 TVRepository .kt 파일을 생성합니다.

 

아래와 같이 초기화를 합니다.

    private val api: Api //인터페이스 구현

    init {
        val retrofit = Retrofit.Builder()
            .baseUrl("https://api.themoviedb.org/3/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()

        api = retrofit.create(Api::class.java)
    }

 

Retrofit 을 Builder객체로 만듭니다.

baseUrl 은 "https://api.themoviedb.org/3/" 로 설정합니다. 

JSON 데이터 객체를 사용하기 위해 GsonConverterFactory 를 사용합니다.

 

아래와 같이 실행 함수를 구현합니다.

    fun getPopularTV(page: Int = 1,
                         onSuccess: (tvlist: List<TV>) -> Unit,
                         onError: () -> Unit ) {
        TVRepository.api.getPopularTV(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()
                }
            })
    }

Movie popular 함수를 복사하여 TV 용으로 수정하였습니다.

 

 

5. TV 관련 String을 추가합니다. 

영어로만 사용한 다른 String은 그대로 사용하겠습니다.

    <string name="most_popular_tv">가장 인기있는 TV</string>
    <string name="most_toprated_tv">최고 평점 TV</string>

 

6. fragment_home.xml 에 layout 추가

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">


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

                <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/popular"
                    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_popular_tv" />

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

 

 

7. 개별 TV 정보 layout 추가

Movie와 동일한 layout 입니다.

item_tv.xml 파일을 추가합니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="128dp"
    android:layout_height="192dp"
    android:layout_marginEnd="8dp"
    app:cardCornerRadius="4dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/item_tv_poster"
            android:layout_width="match_parent"
            android:layout_height="172dp" />

        <TextView
            android:id="@+id/item_tv_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Title" />
    </LinearLayout>

</androidx.cardview.widget.CardView>

 

 

8. TV Adapter 를 추가합니다.

home 폴더에서 마우스 오른쪽 클릭을 하여 TVAdapter를 추가합니다.

 

소스 코드는 아래와 같이 구성하였습니다.

Movie 파일에서 TV 용으로 수정하였습니다.

class TVAdapter (var tvlist: MutableList<TV>, var onTVClick: (tv: TV) -> Unit
) : RecyclerView.Adapter<TVAdapter.TvViewHolder>(){

    inner class TvViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val poster: ImageView = itemView.findViewById(R.id.item_tv_poster)
        fun bind(tv: TV) {
            Glide.with(itemView)
                .load("https://image.tmdb.org/t/p/w342${tv.poster_path}")
                .transform(CenterCrop())
                .into(poster)
            itemView.item_tv_title.text = tv.name
            itemView.setOnClickListener { onTVClick.invoke(tv) }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TvViewHolder {
        val view = LayoutInflater
            .from(parent.context)
            .inflate(R.layout.item_tv, parent, false)
        return TvViewHolder(view)
    }

    override fun getItemCount(): Int = tvlist.size

    override fun onBindViewHolder(holder: TvViewHolder, position: Int) {
        holder.bind(tvlist[position])
    }

    fun appendTV(tvlist: List<TV>) {
        this.tvlist.addAll(tvlist)
        notifyItemRangeInserted(
            this.tvlist.size,
            tvlist.size - 1
        )
    }
}

 

 

9. HomeFragment.kt 파일에 호출 함수를 주가합니다.

TV 정보를 기존 HomeFragment 에 사용하겠습니다.

 

소스 코드는 아래와 같이 구성하였습니다.

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

    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()

        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 onError() {
        Toast.makeText(activity, "error Movies", Toast.LENGTH_SHORT).show()
    }

    private fun showTVDetails(tv: TV) {
    }
}

 

 

10. 상세 화면 이동

보여주는 정보가 Movie와 동일하므로 MovieDetailsActivity.kt를 호출하는 것을 그대로 사용해 보겠습니다.

 

showTVDetails() 함수를 아래와 같이 구성합니다.

    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)
    }

TV 상세 화면을 다르게 구성하려면 Extra Name 과 TVDetailsActivity.kt 를 새로 만들어 사용하면 되겠습니다.

 

실행해 보면 아래와 같이 잘 나타납니다.

 

 

상세화면

 

 

다음은 Top rated, TV On the Air, TV Airing Today API를 사용하여 카테고리를 구성해 보겠습니다.

 

TMDB API 를 활용한 Android 앱 만들기

https://stockant.tistory.com/530

 

 

반응형