본문 바로가기
Programando/Android

[Android/Kotlin] Retrofit2를 통해 RestAPI와 통신하기

이번에는 영화진흥위원회의 오픈 API에서 2022년도 1월 1일이 있는 주간에 상영한 영화 한 편의 정보를 가져올 것입니다.

해당 API를 얻을 수 있는 링크는 아래와 같습니다.

 

영화진흥위원회 오픈API

제공서비스 영화관입장권통합전산망이 제공하는 오픈API서비스 모음입니다. 사용 가능한 서비스를 확인하고 서비스별 인터페이스 정보를 조회합니다.

www.kobis.or.kr

제공서비스 영화관입장권통합전산망이 제공하는 오픈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 생성하기

  1. RestAPI에 요청한 후에 받은 출력변수를 Copy 합니다.
  2. Command+,를 통해 Preferences로 들어와 아래처럼 Plugins를 선택하고, Marketplace에서 JSON To Kotlin Class를 찾아 설치합니다.

  1. POJO Class를 생성하려는 디렉토리 혹은 패키지 우클릭 → New → Kotlin data class File from JSON

  1. Copy해두었던 출력변수들을 Paste하고, Class Name을 작성한 다음 Generate 버튼을 누릅니다.

  1. 완성!

 

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. 그 외

- 완성 코드

 

GitHub - na-ram/AOS_RestAPI_with_Retrofit2_Example

Contribute to na-ram/AOS_RestAPI_with_Retrofit2_Example development by creating an account on GitHub.

github.com

 

반응형