[안드로이드 코드랩] Android Kotlin Fundamentals:8.1 Getting data from the internet (2)

홍석규·2022년 2월 20일
0

안드로이드 코틀린 기초 코드랩을 학습한 내용입니다.
https://developer.android.com/courses/kotlin-android-fundamentals/overview?hl=ko

Parse the JSON response with Moshi

서버로부터 json string 데이터를 잘 받아왔지만 실제로 우리가 필요한건 코틀린 객체지 json string이 아니다. 그래서 안드로이드에는 json string을 코틀린 객체로 바꿔주는 Moshi 라는 JSON parser 라이브러리를 제공해준다. retrofit은 Moshi를 이용해 작업하는 converter를 제공해주고 있다.

먼저 의존성을 gradle에 추가해주자

implementation "com.squareup.moshi:moshi-kotlin:$version_moshi"

moshi 라이브러리를 사용하기 위해 의존성을 추가해준 다음, retrofit의 converter 의존성 코드도 수정 해줘야한다.

implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"

implementation "com.squareup.retrofit2:converter-moshi:$version_retrofit"
  • converter-scalars로 된 부분을 moshi를 사용할 수 있게 변경 해주자.

Implement the MarsProperty data class

웹 서버로부터 받는 json 데이터는 다음과 같은 형태일 것이다.

[{"price":450000,
"id":"424906",
"type":"rent",
"img_src":"http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631300305227E03_DXXX.jpg"},
...]
  • key: value 형태의 json data가 array를 이루고 있는것을 볼 수 있다. 각 json의 value는 string이 될 수도있고, number가 될 수도 있고, boolean, 또는 다른 객체나 배열들이 될 수도 있다. 만약 value가 string이라면 “”로 감싸져 있을것이다.

Moshi는 json data를 파싱해서 kotlin 객체로 변환 해준다고 했다. 해당 과정을 수행하기 위해 파싱된 결과를 저장할 데이터 클래스가 필요하다.

data class MarsProperty(
    val id: String, val img_src: String,
    val type: String, val price: Double
)
  • 파싱한 json 데이터를 저장할 수 있는 MarsProperty 클래스를 생성했다. 각 프로퍼티들의 이름은 json data의 key가 되고 type은 json data value의 type과 동일하게 지정 해줘야한다.
    • Double은 모든 json 숫자를 나타내는데 사용할 수 있다.

변수에 _ 가 들어가면 코틀린 사용시 권장 하는 변수 표현식인 camel case와 달라 lint 경고가 발생할 수 있는데 그럴경우 다음과 같이 변경해주면된다.

@Json(name = "img_src") *val* imgSrcUrl: String,

  • json key img_src를 imgSrcUrl 변수와 매핑시킬 수 있게 어노테이션으로 알려준다.

그 다음 새로운 moshi 객체를 생성 해주고 retrofit 객체를 생성할 때 ScalarConverterFactory를 넣어줬던 부분을 MoshiConverterFactory로 변경 해준다.

private val moshi = Moshi.Builder()
   .add(KotlinJsonAdapterFactory())
   .build()

private val retrofit = Retrofit.Builder()
   .addConverterFactory(MoshiConverterFactory.create(moshi))
   .baseUrl(BASE_URL)
   .build()

그리고 기존에 `String 타입으로 받아왔던 response를 Data class로 정의 해준 MarsProperty로 받아올 수 있게 제너릭 타입을 변경 해준다.

interfaceMarsApiService{
    @GET("realestate")
fungetProperties():
Call<List<MarsProperty>>
}
  • List 형태로 받은 이유는 api response가 json array 형태로 데이터를 반환 해주기 때문이다.

이제 해당 api를 호출하고 callback을 정의 해준 부분을 수정 해보자

MarsApi.retrofitService.getProperties().enqueue(
            object: Callback<List<MarsProperty>> {
                override fun onResponse(call: Call<List<MarsProperty>>, response: Response<List<MarsProperty>>) {
                    _response.value = "Success ${response.body()?.size} Mars properties retrived"
                }

                override fun onFailure(call: Call<List<MarsProperty>>, t: Throwable) {
                    _response.value = "Failure: " + t.message
                }
            })
  • String 으로 받아오던 부분이 List로 변경 되었기 때문에 관련된 Callback 제너릭 타입도 변경 해준다.
  • 실제로 이 앱의 목적은 overview화면에서 img_src url을 이용해 썸네일을 보여주는 것이기 때문에 우선 해당 단계에서는 api가 정상적으로 호출 됐는지 확인하기 위해 response.body().size 갯수를 출력 해주게 변경해두었다.

Use coroutines with Retrofit

지금까지 잘 따라왔다면 retrofit을 이용해 json array데이터를 받아서 moshi 라이브러리를 통해 kotlin object로 변경 해줄 수 있게 구현 했을것이다.

그런데 실제 api를 호출 할 때 enqueue()를 이용해 요청을 queue에 넣고 callback을 함께 전달해서 response와 failure 처리를 구현 해두었는데 코루틴을 사용하게 된다면 콜백을 사용하는 것 보다 훨씬 더 가독성이 좋아질 것이다. 그래서 이번 task에서는 콜백이 아닌 코루틴을 이용하는것으로 리팩토링 해보자.

ApiService Method suspend로 변경하기

  • 우선 코루틴을 사용하기 위해서 suspend 가능한 함수로 변경 해줘야한다. retrofit은 내부적으로 코루틴을 지원 하기 때문에 (해당 내용은 추후에 다시 다뤄볼 예정) getProperties()메서드를 suspend로 변경해주자.
  • 또한 Call 객체를 반환 했던 기존과 다르게 콜백을 사용하는 것이 아닌 코루틴을 사용 하므로 List<MarProperty>를 반환할 수 있게 변경하자
//기존
interface MarsApiService {
    @GET("realestate")
    fun getProperties():
            Call<List<MarsProperty>>
}
//코루틴 셋업
interface MarsApiService {
    @GET("realestate")
    suspend fun getProperties():
            List<MarsProperty>
}

Call.enque()로직을 코루틴을 이용하는것으로 변경하기

  • 기존 구현은 Call.enque()메서드를 호출하고 파라미터로 콜백을 전달해서 비동기 처리를 해주었는데 코드가 복잡해질 경우 가독성이 상당히 떨어지게 된다.
  • 기존 로직을 다 지우고 veiwModelScope를 이용해 코루틴을 생성하자.
//기존
private fun getMarsRealEstateProperties() {
        MarsApi.retrofitService.getProperties().enqueue(
            object: Callback<List<MarsProperty>> {
                override fun onResponse(call: Call<List<MarsProperty>>, response: Response<List<MarsProperty>>) {
                    _response.value = "Success ${response.body()?.size} Mars properties retrived"
                }

                override fun onFailure(call: Call<List<MarsProperty>>, t: Throwable) {
                    _response.value = "Failure: " + t.message
                }
            })
    }

//코루틴 적용
private fun getMarsRealEstateProperties() {
        viewModelScope.launch {
            try {
                val listResult = MarsApi.retrofitService.getProperties()
                _response.value =
                    "Success: ${listResult.size} Mars properties retrieved"
            } catch (e: Exception) {
                _response.value = "Failure: ${e.message}"
            }
        }
    }
  • 우선 viewModel에서 코루틴을 적용할 수 있게 지원해주는 viewModelScope를 이용해 코루틴을 생성 & 실행 시킨다.
  • api error handling을 위해 try-catch구문으로 감싸주고 try내에서 retrofit api를 호출한다.
  • suspend 함수를 호출하는 시점에 (api 호출 로직이 포함되는 부분) → 와 같이 일시중단되고 다시 실행될 수 있는 지점이라는것을 알려준다. api가 호출될 때 response를 받아오는 동안 해당 코루틴은 일시중단 되고 쓰레드는 다른 코드 블럭을 실행 시키다가 response가 오면 다시 해당 지점에서부터 값을 받아와 listResult에 넣어줄 수 있게 된다.
  • 콜백의 onFailure 메서드와 동일한 로직을 catch구문에 추가해준다.

이제 앱을 빌드하고 실행 해보면 기존과 동일하게 동작 하지만 코루틴을 적용 함으로써 훨씬 코드가 직관적이고 가독성이 좋아진다.

profile
학습한 내용을 공유하고 기록합니다.

0개의 댓글