💡 TMDB를 사용하여 영화 목록을 가져오도록 하겠습니다.
api_key를 발급받아 사용해야하니, 따라해보고 싶은 분들은 검색을 통해 발급 받고 사용해보시면 좋을 것 같습니다.
tmdb는 영화의 정보를 가져올 수 있는 api를 제공하는 사이트입니다.
제일 처음 우리는 Retrofit을 사용해주기 위해서 gradle에 라이브러리를 추가해 줄 필요가 있습니다.
gradle의 하단의 dependencies 를 확인하면 초기 임에도 불구하고 많은 항목들을 implementation하고 있는 모습을 확인할 수 있습니다.
여기서 우리는 retrofit과 Okhttp3를 추가해줄 것입니다.
Okhttp3의 경우 api를 사용할 때, 어떤 데이터를 주고 받는지, 어떤 에러가 발생하는지 확인할 수 있도록 interceptor를 사용해주기 위해서 입니다.
(간단한 작업이라 필요없다고 생각하실 경우 생략해도 무관합니다.)
//Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
implementation 'com.google.code.gson:gson:2.9.0'
//Okhttp3
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0'
implementation 'com.squareup.okhttp3:okhttp-urlconnection:4.10.0'
버전의 경우
https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
https://mvnrepository.com/artifact/com.squareup.retrofit2/retrofit
여기서 확인하실 수 있습니다.
그리고 parcelize를 사용해주기 위해서 plugins에 코드를 추가해줍시다.
id 'kotlin-parcelize'
http와 https의 차이점은 보안을 담당하는 계층이 있냐 하나의 차이지만, 우리가 사용할 땐 말이 달라집니다.
사이드프로젝트를 진행하면서, 백엔드를 담당하시는 분이 http로 시작하는 base_url을 제공해줄 경우, Manifest에 코드를 추가해 줄 필요가 있습니다. 안드로이드는 기본적으로 http를 통한 접근을 제한하기 때문입니다.
(tmdb의 base_url은 https를 사용합니다.)
AndroidManifest.xml의 application의 바로 아래에 코드를 추가해 줄 필요가 있습니다.
android: useCleartextTraffic="true"
그리고 위의 경우와 상관없이, 인터넷을 통해서 데이터를 주고받는 작업을 행하기에, permission을 추가해줄 필요가 있습니다.
AndroidManifext.xml의 manifext 단에 코드를 추가해줍니다.
<uses-permission android:name="android.permission.INTERNET" />
retrofit client의 경우 싱글톤으로 만들어서 중복해서 생성하는 일을 방지할 수 있도록 합니다.
object RetrofitClient {
// private 로 instance를 만들어 줍니다. 없으면 생성해서 반환하고, 있으면 이 객체를 반환해줄 것입니다.
private var instance: Retrofit? = null
private val gson = GsonBuilder().setLenient().create()
private const val BASE_URL = "https://api.themoviedb.org/3/"
// 타임아웃 시간을 설정하는 것입니다. (ms 기준)
private const val CONNECT_TIMEOUT_SEC = 20000L
fun getInstance() : Retrofit {
// 없으면 생성
if(instance == null) {
// interceptor를 설정해줍니다.
val interceptor = HttpLoggingInterceptor()
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
val client = OkHttpClient.Builder()
.addInterceptor(interceptor)
.connectTimeout(CONNECT_TIMEOUT_SEC, TimeUnit.SECONDS)
.build()
instance = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build()
}
// 반환
return instance!!
}
}
api를 사용할 땐, Query, Request와 내가 받을 Response가 필요합니다.
그리고 이들을 어떤 HTTP method를 사용할지 정해줄 interface가 필요합니다.
이 들은 api 명세서를 통해 확인할 수 있습니다.
Request
POST나 PUT, PATCH의 경우 Request Body에 담아서 보내 줄 필요가 있습니다.
다음에 기회가 된다면 쓰도록 하겠습니다.
GET의 경우 Query String에 데이터를 담아서 보내주기 때문에 Query 정보가 필요합니다.
Query의 경우 api_key, language, page, region이 필요하다고 나옵니다.
이는 아래의 interface에서 어떻게 구현하는지 적혀있습니다.
Response
Response의 경우 담아줄 공간이 정말 많았습니다.
@Parcelize
data class NowPlayingResponse(
var page: Int,
@SerializedName("results")
var movies: ArrayList<Movie>,
var dates: Date,
var total_pages: Int,
var total_result: Int
) : Parcelable
@Parcelize
data class Movie(
var poster_path: String,
var adult: Boolean,
var overview: String,
var release_data: String,
var genre_ids: List<Int>,
var id: Int,
var original_title: String,
var original_language: String,
var title: String,
var backdrop_path: String,
var popularity: Number,
var vote_count: Int,
var video: Boolean,
var vote_average: Number
): Parcelable
@Parcelize
data class Date (
var maximum: String,
var minimum: String
) : Parcelable
여기서 @SerializedName와 @Parcelize라는 annotation이 보이실 겁니다.
SerializedName은 간단히 말해서 받은 객체의 이름은 A지만, 나는 B로 사용하고 싶다 할 때 사용합니다.
Parcelize는 간단히 말해서 데이터를 주고받기 편하도록 할 때 사용합니다.
interface
interface에는 POST, GET, DELETE, PUT, PATCH 등의 HTTP Method를 사용할 지 구현해 둘 필요가 있습니다.
interface MovieService {
@Headers("content-type: application/json")
@GET("movie/now_playing")
fun getNowPlaying(
@Query("api_key") api_key: String,
@Query("language") language: String,
@Query("page") page: Int
): Call<NowPlayingResponse>
}
일단 retrofit의 구현이 필요합니다.
private fun initRetrofit() {
retrofit = RetrofitClient.getInstance()
movieService = retrofit.create(MovieService::class.java)
}
그 후, api를 사용해주면 완료됩니다.
private fun getNowPlayingMovie() {
var nowPlayingService = movieService.getNowPlaying("${BuildConfig.TMDB_API_KEY}", "ko, en-US", 1)
nowPlayingService.enqueue(object: Callback<NowPlayingResponse> {
override fun onResponse(call: Call<NowPlayingResponse>, response: Response<NowPlayingResponse> ) {
if(response.isSuccessful) {
val movies = response.body()!!.movies
Log.e("Movies: ", "$movies")
}
}
override fun onFailure(call: Call<NowPlayingResponse>, t: Throwable) {
Log.e("MoviesError", "$t")
}
})
}
onResponse 함수와 onFailure 함수가 보일 것입니다.
이 둘은 각각 성공을 했을 때 어떤 명령을 처리할 지를 나눠놓은 것입니다.
저는 성공을 했을 경우 Movies를 출력하고, 실패했을 경우 Error Log를 출력하도록 해놨습니다.
로그를 확인하면, 정상적으로 데이터를 받는 모습을 볼 수 있습니다.
이 데이터를 어떻게 가공하여 보여줄 지는 개발자의 몫이라고 생각합니다!