우선 회사에서 api 통신시 null 값이 오는 부분들이 좀 있었고 null로 인해 굉장히 불필요한 코드들이 적히는 것이 싫었다.. 우선 null 체크같은 것들 이런 것들이 기본적으로 보일러 플레이트가 되었다.
또한 이제 Gson도 잘 안쓰고 Moshi를 더 쓴다는 것을 알았고,, 이 Gson 을 이제 어떻게 할까 고민을 하는 계기가 되었다.
고민을 하는 도중 Kotlin Serialization 이 있는 것을 알게되었고, 성능도 Gson 보다 좋고 나쁘지 않은 것 같아 이를 적용해보았다.
우선 retrofit의 컨버터를 GsonConverterFactory를 사용하는 경우를 예로 들면
우리가 만약 데이터 클래스를 아래와 같이 구현하고,
data class ServerResponseData(
@SerializedName("NAME")
val name: String = "CHANGMIN",
@SerializedName("DESCRIPTION")
val description: String = "Junior Android"
)
서버에서 응답 데이터가 아래와 같이 왔다면
{
"NAME": "NAME",
"DESCRIPTION": null
}
ServerResponseData
클래스에서 default value를 설정했더라도 description은 null 값이 된다.
이런 상황에서 해당 모델의 값들을 사용한다면 거의 모든 대다수의 상황에서 아래와 같은 일이 일어날 것이다.
otherName = name ?: ""
otherDescription = description ?: ""
?: ""
를 매번 사용 하다보니 보일러 플레이트가 발생한다..
만약 ServerResponseData
를 아래와 같이 작성했다면 어떻게 될까
@Serializable
data class ServerResponseData(
@SerialName("NAME")
val name: String = "CHANGMIN",
@SerialName("DESCRIPTION")
val description: String = "Junior Android"
)
Json의 Description 값이 null 이지만 Junior Android 로 값이 설정된 것을 확인할 수 있을 것이다.
그러면 같이 사용해보자.
기존 구조에서 Retrofit의 Converter를 GsonConverterFactory를 사용하고 있었으니..
갑자기 Kotlin Serialization Converter 만 사용하는 것으로 모든 코드를 변경하면
무척 힘들고.. 어디서 터질지 모르는 오류가 팡팡 터질 수 도 있으니 점진적으로 적용하고자
Kotlin Serialization Converter 를 같이 사용하기 위해 CustomConverterFactory를 만들어 준다.
class CustomConverterFactory @Inject constructor(
private val gsonConverterFactory: GsonConverterFactory,
private val kotlinSerializationConverterFactory: Converter.Factory
) : Converter.Factory() {
override fun requestBodyConverter(type: Type, parameterAnnotations: Array<out Annotation>, methodAnnotations: Array<out Annotation>, retrofit: Retrofit): Converter<*, RequestBody>? {
return kotlinSerializationConverterFactory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit)
}
override fun responseBodyConverter(type: Type, annotations: Array<out Annotation>, retrofit: Retrofit): Converter<ResponseBody, *>? {
return when {
annotations.any { it.annotationClass == KotlinSerialization::class }
-> {
kotlinSerializationConverterFactory.responseBodyConverter(type, annotations, retrofit)
}
else -> {
gsonConverterFactory.responseBodyConverter(type, annotations, retrofit)
}
}
}
}
responseBodyConverter
를 한번 살펴보자.
gson
혹은 KotlinSerialization
을 사용한다.
@KotlinSerialization
어노테이션을 만들고
API 함수에 KotlinSerialization
의 어노테이션을 붙여
annotations.any { it.annotationClass == KotlinSerialization::class }
부분을 통해 KotlinSerialization 부분 어노테이션이 존재하는 함수 호출시에만 KotlinSerializationConverter를 사용할 수 있도록 했다.
이러면 API 함수는 아래처럼 작성되면 된다.
interface ApiInterface {
@GET("calls/test")
@KotlinSerialization
fun getTest(...): Call<ServerResponseData>
}
@KotlinSerialization
어노테이션이 붙은 함수들은 전부 KotlinSerializationConverter를 통해 역직렬화가 이뤄지는 것을 확인할 수 있을 것이다.
이제 점진적으로.. 기존 코드를 조금씩 바꿔가고, 추가되는 api에는 위 어노테이션을 붙여 KotlinSerializationConverter
를 사용할 수 있다
KotlinSerialization
은 Ktype 을 처리할 때는 문제가 없지만. 만약 서버에서 다른 type 들을 내려준다면 변환하는 Serializer
를 만들거나 다른 방법을 찾아봐야한다.
또한 별 것 아니지만 릴리즈모드에서 프로가드를 사용한다면 프로가드에 KotlinSerialization 깃허브에 들어가서 넣어야하는 것을 꼭 넣어주자.