이번에는 영화진흥위원회의 오픈 API에서 2022년도 1월 1일이 있는 주간에 상영한 영화 한 편의 정보를 가져올 것입니다.
해당 API를 얻을 수 있는 링크는 아래와 같습니다.
제공서비스 영화관입장권통합전산망이 제공하는 오픈API서비스 모음입니다. 사용 가능한 서비스를 확인하고 서비스별 인터페이스 정보를 조회합니다.
0. 종속성 추가
Retrofit2
를 사용하기 위해서는 먼저 app 모듈의 build.gradle에 종속성을 추가해야 합니다.
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
1. Manifest에 권한 추가
RestAPI를 통해 데이터를 받아오기 위해서는 인터넷이 필요하므로 manifest 파일에 Internet 권한을 추가해야 합니다.
<uses-permission android:name="android.permission.INTERNET" />
제가 사용한 RestAPI는 요청 url이 ‘http’로 시작하는 반면, 안드로이드에서는 기본적으로 Http의 접근을 허용하지 않기 때문에 관련 속성도 추가합니다.
<application
...
android:usesCleartextTraffic="true"
android:theme="@style/Theme.AOS_RestAPI_with_Retrofit2">
2. RetrofitClient 생성
통신할 baseUrl을 설정하고 데이터 파싱 및 객체 정보를 반환할 수 있는 Retrofit 객체와 Json을 Kotlin Object로 변환시켜줄 Gson 객체를 생성하는 부분을 싱글톤(Object)으로 만들어줍니다.
먼저, baseUrl
은 고정된 주소로 지정합니다. 주간 박스오피스 정보를 얻어올 것이기 때문에 “http://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/”를 baseUrl로 지정했습니다. 중요한 점은 baseUrl은 꼭 ‘/’로 끝나야 오류가 생기지 않는다는 점입니다.
그리고 addConverterFactory(GsonConverterFactory.create(gson))
은 Json 데이터를 POJO Class 형식으로 자동변환하기 위해 필요합니다.
- RetrofitClient.kt
import com.google.gson.GsonBuilder
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object RetrofitClient {
private var instance: Retrofit? = null
private val gson = GsonBuilder().setLenient().create()
fun getInstance(): Retrofit {
if(instance == null) {
instance = Retrofit.Builder()
.baseUrl("http://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/")
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
}
return instance!!
}
}
3. POJO Class 생성
RestAPI의 응답 예시를 확인하고, 출력에 따라서 POJO Class
를 생성합니다.
제가 날린 요청에 돌아오는 결과는 아래와 같습니다. 이런 출력변수에 대응되는 Data Class
를 만들어주면 됩니다.
{
"boxOfficeResult": {
"boxofficeType": "주간 박스오피스",
"showRange": "20211227~20220102",
"yearWeekTime": "202152",
"weeklyBoxOfficeList": [
{
"rnum": "1",
"rank": "1",
"rankInten": "0",
"rankOldAndNew": "OLD",
"movieCd": "20210028",
"movieNm": "스파이더맨: 노 웨이 홈",
"openDt": "2021-12-15",
"salesAmt": "12284467410",
"salesShare": "64.5",
"salesInten": "-8420703150",
"salesChange": "-40.7",
"salesAcc": "60973798690",
"audiCnt": "1247766",
"audiInten": "-808903",
"audiChange": "-39.3",
"audiAcc": "6074288",
"scrnCnt": "2166",
"showCnt": "48970"
}
]
}
}
Data Class를 생성할 때, JSON 데이터의 속성명과 변수명 + 타입(ex String,Int,Boolean) 일치는 필수입니다.
출력변수에 맞는 POJO Class 생성하기
- RestAPI에 요청한 후에 받은 출력변수를 Copy 합니다.
Command
+,
를 통해 Preferences로 들어와 아래처럼 Plugins를 선택하고, Marketplace에서JSON To Kotlin Class
를 찾아 설치합니다.
- POJO Class를 생성하려는 디렉토리 혹은 패키지 우클릭 → New → Kotlin data class File from JSON
- Copy해두었던 출력변수들을 Paste하고, Class Name을 작성한 다음 Generate 버튼을 누릅니다.
- 완성!
4. API Interface 생성
RestAPI와 통신하기 위해 사용할 HTTP CRUD동작(메소드)들을 정의할 API Interface를 생성합니다.
해당 RestAPI에서 “요청 parameter : 3번항의 요청 인터페이스 정보를 참조하여 GET 방식으로 호출”과 같이 정해두었기에 GET 방식으로 데이터를 보내고, 가져오도록 하겠습니다.
@GET
요청 메서드에는 RetrofitClient에 작성한 baseUrl의 뒤에 오는 EndPoint를 지정하고, 동적으로 변경해야 하는 파라미터는 @Query
어노테이션을 이용해서 메서드를 호출할 때 값을 넘겨받아 주소에 포함시키도록 합니다. 마지막으로, 요청한 주소로부터 Json을 반환받는다면 위에서 만들어둔 POJO Class로 받을 수 있도록 Call를 return type으로 정해줍니다.
- RetrofitAPI.kt
import com.example.aos_restapi_with_retrofit2.model.Movie
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query
interface RetrofitAPI {
@GET("searchWeeklyBoxOfficeList.json?")
fun getMovie(
@Query("key") key: String,
@Query("weekGb") weekGb: Int,
@Query("targetDt") targetDt: Int,
@Query("itemPerPage") itemPerPage: Int
) : Call<Movie>
}
5. 요청 보내고 받기
처음에 만들었던 RetrofitClient
를 통해 생성한 Retrofit 객체를 받아오고, 해당 객체를 이용해 Interface를 구현합니다. 통신 방법에는 enqueue
를 이용하는 비동기 작업과 execute
를 이용하는 동기 작업이 있습니다.
비동기 작업으로 실행한다면 통신 종료 후 이벤트 처리를 위해 Callback을 등록해야 하고, 동기 작업으로 실행한다면 AsyncTask 등을 통해 받아온 데이터를 가지고 UI 작업을 할 수 있습니다.
저는 비동기 작업으로 실행할 것이므로 enqueue를 이용하였습니다. 요청을 보내고 난 다음 결과는 두 가지로 나뉩니다. 통신에 성공했을 경우에는 onResponse
, 통신에 실패했을 경우에는 onFailure
로 나뉘는데 실패했을 경우에는 그에 따른 처리를 해주어야 합니다.
- MainActivity.kt
private fun requestToRestAPI() {
val retrofit = RetrofitClient.getInstance()
val service = retrofit.create(RetrofitAPI::class.java)
service.getMovie(RestAPIKey, 0, 20220101, 1)
.enqueue(object: Callback<Movie> {
override fun onResponse(
call: Call<Movie>,
response: Response<Movie>
) {
Log.d("Success", response.body().toString())
response.body()?.let {
processMovie(response.body()!!)
}
}
override fun onFailure(
call: Call<Movie>,
t: Throwable
) {
Log.d("Failure", t.localizedMessage)
}
})
}
6. 받아온 데이터 처리
- res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
tools:context=".MainActivity">
<TextView
android:id="@+id/tvRange"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvMovieNm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvRange"
android:layout_marginTop="5dp"/>
<TextView
android:id="@+id/tvMovieRank"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="45sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/tvRange"
app:layout_constraintBottom_toBottomOf="@id/tvMovieNm"
app:layout_constraintHorizontal_bias="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvMovieNm" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="오늘 관객수 : "
android:textSize="15sp"/>
<TextView
android:id="@+id/tvTodayAudience"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="/"
android:layout_marginHorizontal="5dp"
android:textSize="15sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="누적 관객수 : "
android:textSize="15sp"/>
<TextView
android:id="@+id/tvAccumulatedAudience"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="15sp"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
- MainActivity.kt
private fun processMovie(movie: Movie) {
val range = movie.boxOfficeResult.showRange
var from = range.split("~")[0]
var to = range.split("~")[1]
val stringToDateFormat = SimpleDateFormat("yyyyMMdd")
val fromDate = stringToDateFormat.parse(from)
val toDate = stringToDateFormat.parse(to)
val dateToStringFormat = SimpleDateFormat("yyyy/MM/dd")
from = dateToStringFormat.format(fromDate)
to = dateToStringFormat.format(toDate)
tvRange.text = "[$from ~ $to]"
movie.boxOfficeResult.weeklyBoxOfficeList.forEach {
tvMovieNm.text = it.movieNm
tvMovieRank.text = it.rank
tvTodayAudience.text = it.audiCnt
tvAccumulatedAudience.text = it.audiAcc
}
}
7. 완성된 화면
8. 그 외
- 완성 코드
'Programando > Android' 카테고리의 다른 글
[Android/Kotlin] Coroutines (0) | 2022.02.06 |
---|---|
[Android/Kotlin] ViewBinding (0) | 2022.01.27 |
[Android/Kotlin] RecyclerView 사용하기 (0) | 2022.01.12 |
[Android/Kotlin] 안드로이드 숫자 국가 코드 (0) | 2021.11.12 |
[Android/Kotlin] Retrofit2 PHP MySQL 통신 (0) | 2021.09.13 |