본문 바로가기
학습(삽질) 노트/Android

REST API와 Retrofit

by -MAYDAY- 2022. 3. 26.

REST API와 Retrofit

 안드로이드 앱 개발하면서 서버와의 통신은 피할 수 없는 과제이다. 마켓에 출시한 대부분의 앱들은 간단한 기능을 제공하는 앱이 아닌 이상 인터넷 연결이 필수적인 상황이 많다. 앱 상에서 서버로부터 사용자가 요청한 데이터를 보여주기 위해서는 http 통신을 통해 서버가 반환한 결과값을 전달받아야 한다. 그렇기에 앱 개발자로서는 서버와의 통신을 항상 염두하여 계획한 기능을 구현해야 한다. 모바일 앱과 통신하는 대표적인 서버로 REST API가 자주 쓰이고 있다. 간편하게 요청할 수 있어 클라이언트 개발자 입장에서 쉽고 편하게 서버로부터 데이터를 가져올 수 있기 때문이다.

 

REST(RESTful) API란?

 먼저 REST는 Representational State Transfer의 약자로, 사전적 의미로는 자원을 이름으로 구분하여 해당 자원의 상태를 주고받는 모든 것을 의미한다. 결국 REST API는 HTTP URI를 통해 자원(문서, 그림, 동영상, 데이터 등)을 명시하고 Method를 통해 해당 자원에 대한 CRUD를 적용하는 API를 의미하는 것이다. REST API의 주요 메소드로는 GET, POST, DELETE, PUT 등이 있다. 좀 더 구체적인 REST API 특징을 소개하고 싶지만 자세하게 설명해주는 훌륭한 사이트 혹은 블로그가 많기에 궁금한 사항이 있다면 아래 참고 링크를 통해 확인하거나 별도로 검색하여 찾아보길 권장한다.

 

JSON 파일의 구조

 이 글의 작성한 목적 중 하나인 JSON 파일의 구조에 대해 알아보고자 한다. REST API를 처음 사용했을 때 겪었던 문제가 바로 이 JSON 파일의 구조를 제대로 이해하지 못하여 단순히 원하는 값만 선택하여 불러오다가 값이 제대로 넘어오지 않았던 것이었다. 몇 주간 이 문제로 인해 뼈저리게 삽질한 시간을 생각하면 지금도 아찔한데, 역시 혼자서 개발하는 것은 타인에게 피드백 받을 환경이 아니기에 개발에 자신있는 사람이 아니라면 조언을 구할 사람은 항상 있어야하는 것 같다. 아무튼 여담을 뒤로하고 JSON 파일의 구조를 그만큼 잘 알고 있어야 REST API를 사용할 때 불필요한 시간 낭비를 줄일 수 있다. 예시를 통해 JSON 파일을 확인해보자.

 

(1) JSON 예시 - 1

{
	“id” : 1,
	“name” : “홍길동”
	“phone” : “010-1111-1111”
}

 위의 예시를 보면 JSON 파일은 Map 구조 형태로 구성되어 있는 것을 확인할 수 있다. 하나의 Key에 하나의 Value로 데이터가 한 쌍을 이루며 여러 데이터가 나열되어 있는 것을 볼 수 있다. 그래서 특정한 Key의 Value를 가져오고 싶으면 원하는 Value의 Key를 호출하여 Value를 가져오면 된다. 예를 들어, 이름을 가져오고 싶으면 Map[‘name’]을 호출하여 ‘홍길동’이라는 Value를 얻을 수 있다.

 

(2) JSON 예시 - 2

{
	“id” : 1,
	“name” : “홍길동”
	“phone” : “010-1111-1111”,
	“address” : [
		“address1” : “서울특별시”,
		“address2” : “서초구”,
		“address3” : “방배동”
	]
}

 위의 Map 구조를 살펴보면 ‘개인정보’가 추가되어 개인정보에 해당하는 여러 값들이 나열되어 있는 것을 볼 수 있다. 이렇게 배열 형태로 구성되어 있는 특정 Key가 존재한다면 원하는 Value를 얻기 위해 다소 주의가 필요하다. 예를 들어, ‘address3’의 Value를 가져오고 싶다면 단순히 ‘address3’를 호출하는 것이 아닌, ‘address’의 ‘address3’를 호출해야 한다. Map[‘address’][‘address3’]를 호출해야 ’방배동'이라는 Value를 가져올 수 있다.

 

 이렇게 JSON 파일이 Map 구조를 가지고 있다는 것을 파악하고, 만약 내부에 배열 구조가 존재한다면 호출만 잘해도 충분히 원하는 값을 가져올 수 있다. 배열 구조를 생각하지 않고 원하는 Key만 호출하여 가져오려고 하는 방심은 금물이다.

 

 이런 REST API와의 통신 과정에서 요청한 JSON 파일의 결과값을 가져오긴 위해서는 라이브러리를 사용해야 편하다. 안드로이드 관련 게시글이니 안드로이드에서 자주 쓰이는 외부 라이브러리인 ‘Retrofit2’를 사용하여 REST API를 호출해보자.

 

Retrofit이란?

 ‘Retrofit’은 REST API를 간편하게 사용할 수 있도록 http 통신을 지원하는 라이브러리로써, 편리하게 사용할 수 있다는 장점으로 안드로이드 앱 개발자 사이에서 가장 많이 쓰이는 라이브러리이기도 하다. ‘Retrofit2’는 전에 많이 사용되었던 비슷한 기능을 수행하던 ‘Okhttp’보다 개선된 성능과 간단한 구현으로 개발자 사이에서 가장 선호하는 라이브러리로 자리 잡게 되었다. 

 

 그러면 실제로 Retrofit2를 사용하여 REST API와의 통신을 직접 실습하고, 이에 대한 결과값이 어떻게 넘어오는지 확인해보자.

테스트에 사용했던 REST API는 Naver Geocoding API를 사용하였고, 주소값을 요청값으로 설정하여 해당 주소에 따라 API가 반환하는 결과값을 확인해보려고 한다.

\Module 수준의 Gradle 파일 열기

dependencies {

    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.google.code.gson:gson:2.8.7'
    
}

 위와 같이 Module 수준의 Gradle로 이동하여 Retrofit 사용과 Json을 Parsing하기 위한 3개의 라이브러리를 추가한다.

 

import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.Query

interface NaverMapApi {

    @Headers("X-NCP-APIGW-API-KEY-ID: XXXXXXXXX", "X-NCP-APIGW-API-KEY: XXXXXXXXXXXXXXXXXXXXXX")
    @GET("/map-geocode/v2/geocode")
    fun getGeocode(@Query("query") query: String): Call<ResponseNaverMap>
}

 REST API와의 http 통신을 위해 Retrofit2 라이브러리를 활용한 Interface를 만든다. Interface 안에는 REST API와 통신하기 위한 함수를 정의하고, 함수에는 REST API가 요구하는 Header와 Method, 그리고 주소까지 입력하고 REST API가 리턴하는 형식을 확인하여 Data Class(ResponseNaverMap)를 만들어 그에 맞게끔 함수의 결과가 반환되도록 구성한다. Naver Geocoding API를 사용하기 위해서는 Header 값으로 ID와 KEY를 요구하는데 이는 Naver Cloud Platform에서 서비스 가입하면 받을 수 있다.

(Naver Geocoding API 사용법 : https://api.ncloud-docs.com/docs/ai-naver-mapsgeocoding-geocode)

 

{
    "status": "OK",
    "meta": {
        "totalCount": 1,
        "page": 1,
        "count": 1
    },
    "addresses": [
        {
            "roadAddress": "경기도 성남시 분당구 불정로 6 그린팩토리",
            "jibunAddress": "경기도 성남시 분당구 정자동 178-1 그린팩토리",
            "englishAddress": "6, Buljeong-ro, Bundang-gu, Seongnam-si, Gyeonggi-do, Republic of Korea",
            "addressElements": [
                {
                    "types": [
                        "POSTAL_CODE"
                    ],
                    "longName": "13561",
                    "shortName": "",
                    "code": ""
                }
            ],
            "x": "127.10522081658463",
            "y": "37.35951219616309",
            "distance": 20.925857741585514
        }
    ],
    "errorMessage": ""
}

 Naver Geocoding API가 반환하는 JSON 파일 형식을 보면 위와 같다. 이렇게 API가 반환하는 데이터 형식을 파악하여 그에 맞게 Data class를 구성하면 된다.

 

data class ResponseNaverMap(
    val status: String,
    var meta: Meta,
    var addresses: List<Addresses>,
    var errorMessage: String
)

 이처럼 API가 반환하는 결과값을 받기 위한 최상위 Data Class를 생성한다.

 

ResponseNaverMap 클래스의 하위 클래스를 만든다.

 상위 Data 밑에 존재하는 하위 Data를 받기 위한 Data Class를 마찬가지로 정의한다.

 

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import com.example.retrofit_test_project.databinding.ActivityMainBinding
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.*

class MainActivity : AppCompatActivity() {
    lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val retrofit = Retrofit.Builder()
            .baseUrl("https://naveropenapi.apigw.ntruss.com")
            .addConverterFactory(GsonConverterFactory.create())
            .build()


        binding.restButton.setOnClickListener {
            retrofit.create(NaverMapApi::class.java).getGeocode("경기도 성남시 분당구 불정로 6 그린팩토리").enqueue(object :
                Callback<ResponseNaverMap> {
                override fun onResponse(call: Call<ResponseNaverMap>, response: Response<ResponseNaverMap>) {
                    Log.d("NaverMap Response", "${response.body()}")
                }

                override fun onFailure(call: Call<ResponseNaverMap>, t: Throwable) {
                    Log.d("NaverMap Response", "${t.message}")
                }


            })
        }
    }
}

 위의 예시 코드를 볼 수 있듯이 간단하게 Retrofit을 사용하여 Naver Geocoding API를 호출해서 API가 반환하는 결과값을 Log로 출력하도록 코드를 작성했다.

 Retrofit.Builder 함수를 통해 REST API의 기본 주소를 세팅하고 GSON 형식을 반환받기 위한 설정을 진행한다. 그 뒤로는 create 함수를 통해서 REST API와 통신하는 Interface의 함수(getGeocode)를 호출하고 API와의 통신이 성공하면 onResponse 함수를 통해 결과값을 리턴받는다.

 

ResponseNaverMap (
status=OK, 
meta=Meta(totalCount=1, page=1, count=1), 
addresses=[
	Addresses(roadAddress=경기도 성남시 분당구 불정로 6 NAVER그린팩토리, 
	jibunAddress=경기도 성남시 분당구 정자동 178-1 NAVER그린팩토리, englishAddress=6, Buljeong-ro, Bundang-gu, Seongnam-si, Gyeonggi-do, Republic of Korea, 
	addressElements=[
		AddressElements(types=[SIDO], longName=경기도, shortName=경기도, code=),
		AddressElements(types=[SIGUGUN], longName=성남시 분당구, shortName=성남시 분당구, code=), 
        	AddressElements(types=[DONGMYUN], longName=정자동, shortName=정자동, code=), 
        	AddressElements(types=[RI], longName=, shortName=, code=), 
        	AddressElements(types=[ROAD_NAME], longName=불정로, shortName=불정로, code=), 
        	AddressElements(types=[BUILDING_NUMBER], longName=6, shortName=6, code=), 
        	AddressElements(types=[BUILDING_NAME], longName=NAVER그린팩토리, shortName=NAVER그린팩토리, code=), 
        	AddressElements(types=[LAND_NUMBER], longName=178-1, shortName=178-1, code=), 
        	AddressElements(types=[POSTAL_CODE], longName=13561, shortName=13561, code=)
            ], 
        x=127.1054065, 
        y=37.3595669, 
        distance=0.0)
        ], 
errorMessage=
)

 REST API인  Naver Geocoding API가 반환하는 결과를 출력해보면 위처럼 반환하는 것을 알 수 있다. 반환하는 Key에 따른 각 Data Class를 만들어 세팅을 하고, API가 반환하는 JSON을 객체로 받으면 원하는 결과를 추출하여 사용자에게 원하는 정보를 제공해줄 수 있다. 예를 들어, x값을 가져오려고 하면, 'ResponseNaverMap.addresses.x' 처럼 최상위 Data에서 하위 Data로 점차 안쪽으로 들어가서 지정하여 호출하면 '127.1054065 '라는 값을 얻을 수 있다.

 

 REST API와 JSON 파일 구조, 그리고 REST API와의 통신을 하기 위한 라이브러리인 'Retrofit2'에 대해 알아봤다. 처음 혼자서 안드로이드 앱 개발할 때 한 번도 써보지 못한 REST API를 사용하면서 여러 시행착오를 겪었다. 앞으로 다시는 이러한 실수를 겪지 않기 위함과 동시에 혹시나 나와 똑같은 어처구니 없는 실수를 할 수도 있는 초보개발자가 주의할 수 있도록 이번 글을 남겨본다. 앱 개발하면서 REST API를 사용할 경우가 많기에 REST API에 대한 개념과 활용법을 평소에 잘 숙지하여 구현하는데 어려움이 없도록 노력하자.

 

 

- 참고

https://gmlwjd9405.github.io/2018/09/21/rest-and-restful.html

 

[Network] REST란? REST API란? RESTful이란? - Heee's Development Blog

Step by step goes a long way.

gmlwjd9405.github.io

https://www.redhat.com/ko/topics/api/what-is-a-rest-api

 

REST API(RESTful API, 레스트풀 API)란 - 서버, 구현, 사용법

REST API(RESTful API)란 REST 아키텍처의 제약 조건을 준수하는 애플리케이션 프로그래밍 인터페이스를 뜻합니다. api 서버, rest api 구현 및 사용법을 설명합니다.

www.redhat.com

https://jaejong.tistory.com/33

 

[안드로이드] Retrofit2 '레트로핏' - 기본 사용법

Retrofit2 - REST API 통신 라이브러리 'Retrofit' - REST통신 라이브러리 기본 개념 & 사용법 통신 라이브러리 중 가장 많이 사용되는 대표적인 라이브러리 ( Squareup 사의 라이브러리) Retrofit 이란? REST API..

jaejong.tistory.com

https://velog.io/@hhb041127/AndroidKotlin-Retrofit2%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90

 

[Android/Kotlin] Retrofit2를 알아보자!

Android의 서버 통신에 필수라고 생각되는 Retrofit2의 사용 방법을 정리하고, 알아보았습니다!

velog.io

 

'학습(삽질) 노트 > Android' 카테고리의 다른 글

AutoCompleteTextView를 활용한 Custom Filtered Adapter  (0) 2022.08.20
Clean Architecture  (0) 2022.07.30
Android MVVM 구현하기  (0) 2022.05.01