그냥 가끔의 기록장

[kotlin] Okhttp+gson으로 오픈 API 사용하기 본문

Android

[kotlin] Okhttp+gson으로 오픈 API 사용하기

the phoenix 2021. 6. 2. 15:15

Okhttp란?

Square에서 제공하는 오픈소스 라이브러리로 HTTP 통신을 쉽게 할 수 있게 해준다.  안드로이드에서 Okhttp 없이 Http 통신을 하게 되면 예외처리, Buffer입출력, HttpURLConnection연결 등 할게 엄청 많아진다고 한다. ( 참고: https://heepie.me/282)

Retrofit이 Okhttp위에서 돈다하니, 나중엔 이것도 사용해봐야겠다. (Retrofit이 뭔지 잘 모르나, 서버와 클라이언트간 http 통신을 위한 인터페이스라고 한다. 근데 Okhttp랑은 뭐가 다른거지...얘도 또 찾아보고 정리해야겠다.)

GSON이란?

json 구조인 데이터를 Java 객체로 바꿔주는 자바 라이브러리이다. 즉, JSON Object <-> Java Object 로 서로 변환해주는 라이브러리이다. 

네이버 파파고 API  사용 예제

1. build.gradle

이번 프로젝트에서는 Okhttp, GSON을 사용할 것이므로 build.gradle(module)의 dependencies에 아래와 같이 추가한다.

dependencies {
    implementation "com.squareup.okhttp3:okhttp:4.9.0"
    implementation 'com.google.code.gson:gson:2.8.7'
}

2. manifest - permission

<application 태그 위에 user-permission을 위한 권한 태그를 추가한다. 

<uses-permission android:name="android.permission.INTERNET"/>
    

3. 네이버 개발자 센터 > Application 등록 

앱 명, 패키지 이름 등을 작성하여 등록하고 <파파고 NMT API> 에서 발급한 ID, KEY를 받는다. 


4. Okhttp로 json 갖고오기 [POST]

1) JSON 생성

okhttp 공식 홈페이지 > post to a Server 에 있는 코드를 복붙한다.

(자바로 작성되어있어서 코틀린으로 바꾼건 아래와 같다.)

val JSON = "application/json; charset=utf-8".toMediaTypeOrNull()

2) url 지정 및  client 객체 생성

네이버 개발자 센터의 url을 복붙해서 url 변수에 넣어준다. 또한 OkHttpClient()로 client 객체도 만든다.

<네이버 개발자 센터>

curl에 나와있는게 API url 이다.

var url = "https://openapi.naver.com/v1/papago/n2mt"
val client = OkHttpClient()

3) post의 body 작성

<네이버 개발자 센터> API 가이드 -d 를 보면, post의 경우 body에 넣어야 하는 값들이 적혀있다.

source, target, text 세 가지를 넘겨줘야 하니 json 객체에 넣어서 body에 넘겨주자.

//body로 넘길 json에 필요한 것들 넣기 (네이버 API 참고)

val json = JSONObject()
json.put("source", "ko")
json.put("target", "en")
json.put("text", "만나서 반가워")

val body = RequestBody.create(JSON, json.toString())

4) request 생성

request의 경우 Okhttp 공식 문서에 나와있는 코드를 코틀린으로 바꾼것이다. 여기서 추가할 점은 네이버 API를 사용하기 위해 토큰을 넘겨줘야한다. 따라서 header에 client id, secret 값을 같이 넘겨주자.

val request = Request.Builder()
            .header("X-Naver-Client-Id", "Su1zPHvcoLrA49Cd2uMF")
            .addHeader("X-Naver-Client-Secret", "------본인 키 작성------")
            .url(url)
            .post(body)
            .build()

5) response 생성

마지막으로 post 결과로 받아올 response를 작성하자. Okhttp 공식문서에서는 execute로 동기 처리를 하게 되어있는데, 비동기 처리를 위해서 enqueue를 사용하였다. enqueue로 Callback 객체를 넘겨주면 onFailure와 onResponse를 override해야 한다.

 

onResponse의 경우, 맨 처음에 그냥 실행하니 메인 thread에서 수행하는게 너무 많다는 경고와 함께 response가 출력이 안되는 버그가 났다. (경고명: Skipped 32 frames! The application may be doing too much work on its main thread.)

검색해보니 별도의 thread에서 네트워크 작업을 해야한다 해서, Runnable 인터페이스로 thread를 구현했다.

즉, thread를 별도로 두고 thread 내에서 response.body를 출력하게 하였다.

val response = client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                TODO("Not yet implemented")
            }
            // main thread말고 별도의 thread에서 실행해야 함.
            override fun onResponse(call: Call, response: Response) {
                Thread{
                    var str = response.body?.string()
                    println(str)
                }.start()
            }
        })

* 전체 코드:

MainActivity.kt 전체 코드는 다음과 같다. 

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import org.json.JSONObject
import java.io.IOException

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //post
        val JSON = "application/json; charset=utf-8".toMediaTypeOrNull()

        var url = "https://openapi.naver.com/v1/papago/n2mt"
        val client = OkHttpClient()

        //body로 넘길 json에 필요한 것들 넣기 (네이버 API 참고)
        val json = JSONObject()
        json.put("source", "ko")
        json.put("target", "en")
        json.put("text", "만나서 반가워")

        val body = RequestBody.create(JSON, json.toString())
        val request = Request.Builder()
            .header("X-Naver-Client-Id", "Su1zPHvcoLrA49Cd2uMF")
            .addHeader("X-Naver-Client-Secret", "H17GVymjiN")
            .url(url)
            .post(body)
            .build()

        val response = client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                TODO("Not yet implemented")
            }
            // main thread말고 별도의 thread에서 실행해야 함.
            override fun onResponse(call: Call, response: Response) {
                Thread{
                    var str = response.body?.string()
                    println(str)
                }.start()
            }
        })
    }
}

* 결과:

MainActivity의 xml을 딱히 수정하지 않아서 앱을 실행하면 앱 자체에선 아무일도 일어나지 않고, Run 창으로 가야 보인다.

Run에서 보면 하단에 이렇게 response가 출력된다.

I/System.out: {"message":{"@type":"response","@service":"naverservice.nmt.proxy","@version":"1.0.0","result":{"srcLangType":"ko","tarLangType":"en","translatedText":"It's nice to meet you.","engineType":"N2MT","pivot":null}}}

5. gson으로 json 파싱하기 

response.body를 보면 message 안에 result안에 translatedText 가 우리가 원하는 번역 결과이다. 해당 translatedText만 뽑아 낼 수 있게 PapagoDTO class를 작성했다. (Reference에 달린 하울님 유튜브에 매우 잘 나와있음!!)

null 이 가능한 변수들 모두 null check 먼저 하면서 class 작성하면 된다. 

 

<PapagoDTO.kt>

package com.example.myapplication


data class PapagoDTO(var message: Message? = null){
    data class Message(var result: Result? = null){
        data class Result(var translatedText: String? = null)
    }
}

변수 message는 Message 타입이니까 바로 아래에 Message class를 작성하고, 다시 result 변수는 Result 타입이니까 바로 밑에 result class를 연쇄적으로 작성해서 원하는 translatedText까지 접근하면 된다.

 

<MainAcitivity.kt>

val response = client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                TODO("Not yet implemented")
            }
            // main thread말고 별도의 thread에서 실행해야 함.
            override fun onResponse(call: Call, response: Response) {
                Thread{
                    var str = response.body?.string()
                    println(str)
                    var PapagoDTO = Gson().fromJson<PapagoDTO>(str, PapagoDTO::class.java)
                    println(PapagoDTO.message?.result?.translatedText)
                }.start()
            }
        })

PapagoDTO 객체를 만든 후 Gson().fromJson 을 이용해 파싱한다. (JSON 형식의 데이터를 지정한 타입의 데이터-자바 객체로 변환해준다.)

* fromJson(json 구조를 띄는 String, 변환할 Java class) 이 인자이므로, json구조인 response.body를 string으로 변환한 str을 첫 번째 인자로, 변환할 DTO class인 PapagoDTO를 두번째 인자로 넣은 것이다. 

* 결과:

아까와 마찬가지로 Run 하단에 보면 다음과 같이 파싱된 translatedText만 출력된다.

Reference

https://developers.naver.com/apps/#/myapps/Su1zPHvcoLrA49Cd2uMF/overview

https://www.youtube.com/watch?v=_j36_ZwNf2Y 

https://blog.naver.com/cosmosjs/221455368269 

https://square.github.io/okhttp/

https://galid1.tistory.com/501

Comments