대부분의 웹 서버는 REST(REpresentational State Transfer)
라는 일반적인 Stateless 웹 아키텍처를 사용해 웹 서비스를 실행한다.
Stateless란 client의 state를 기억할 필요 없다는 것
이 아키텍처를 제공하는 웹 서비스를 RESTful 서비스라고 한다.
Restful 서비스는 Client-server architecture로 이루어진다.
URI를 통해 RESTful 웹 서비스에 요청을 전송한다.
URI(Uniform Resource Identifier)는 리소스 위치나 리소스에 액세스하는 방법을 암시하는 것이 아니라 서버의 리소스를 이름으로 식별한다.
URL(Uniform Resource Locator)은 리소스 표현을 획득하거나 표현에 관해 조치를 취하는 수단을 지정하는 URI이다. 즉, 기본 액세스 메커니즘과 네트워크 위치를 모두 지정한다.
일반적인 HTTP 작업에는 GET, POST, PUT, DELETE가 있다.
GET
: 서버 데이터를 검색POST
PUT
: 서버에 새로운 데이터를 추가/생성/업데이트DELETE
: 서버에서 데이터를 삭제200-299
: Success400-499
: Client Errors500-599
: Server ErrorsRetrofit 라이브러리를 사용해 앱의 네트워크 계층을 구현한다. Retrofit으로 REST 웹 서비스에 연결하고 응답을 받을 수 있다.
Retrifit은 웹 서비스의 컨텐츠를 기반으로 앱의 네트워크 API를 만든다.
웹 서비스에서 데이터를 가져와 별도의 converter 라이브러리를 통해 라우팅한다. 이 converter 라이브러리는 데이터를 디코딩하여 String 같은 객체 형식으로 반환하는 방법을 알고있다.
build.gradle (Project)
repositories {
google()
jcenter()
}
build.gradle (Module)
dependencies
// Retrofit2 라이브러리
implementation "com.squareup.retrofit2:retrofit:2.9.0"
// Retrofit 스칼라 변환기. JSON을 String으로 변환
implementation "com.squareup.retrofit2:converter-scalars:2.9.0"
Network 패키지를 생성하고 그 아래 MarsApiService.kt
를 생성한다.
클래스 바깥에 웹 서비스의 base url을 상수 형태로 작성한다.
private const val BASE_URL = "https://android-kotlin-fun-mars-server.appspot.com"
BASE_URL 아래 Retrofit object를 빌드하고 생성할 Retrofit bilder를 작성한다.
ScalarsConverter
는 strings나 다른 원시 타입을 지원한다.baseUrl()
에 base URL을 담는다.build()
를 호출해 Retrofit object를 생성한다.private val retrofit = Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.baseUrl(BASE_URL)
.build()
여기서는 Retrofit이 HTTP requests를 사용해 어떻게 웹 서버와 통신하는지를 정의한다.
interface MarsApiService {
@GET("photos")
fun getPhotos(): String
}
getPhotos()
가 호출되면 retrofit은 base URL 끝에 photos
를 더해 요청을 시작한다.
Singleton 객체 선언을 위해 object declaration을 한다. Singleton pattern은 객체의 인스턴스가 오직 하나만 생성되고, 그 객체에 접근하는 global point가 하나임을 보장한다.
Object declaration은 object
키워드를 사용한다.
MarsApi
를 정의한다.retrofitService
: lazily initialized retrofit object propertyobject MarsApi {
val retrofitService : MarsApiService by lazy {
retrofit.create(MarsApiService::class.java) }
}
Retrofit에서 반환된 JSON 문자열을 처리하는 getMarsPhotos()
메서드를 구현한다.
MarsApiService
의 getPhotos()를 코루틴에서 호출할 수 있도록 suspend 함수로 수정한다.
@GET("photos")
suspend fun getPhotos(): String
ViewModelScope
는 앱의 각 ViewModel을 대상으로 정의된 코루틴 범위이다. 이 범위에서 실행된 모든 코루틴은 ViewModel이 삭제되면 자동으로 취소된다.
ViewModelScope
를 사용하여 코루틴을 실행하고 백그라운드에서 Retrofit 네트워크 호출을 실행한다.
viewModelScope.launch{}
를 사용하여 코루틴을 실행한다.
viewModelScope
내부에서 MarsApi
를 사용하여 retrofitService
인터페이스에서 getPhotos()
메서드를 호출하고 반환된 응답을 listResult
변수에 저장한다.
결과를 _status.value
에 할당한다.
private fun getMarsPhotos() {
viewModelScope.launch {
val listResult = MarsApi.retrofitService.getPhotos()
_status.value = listResult
}
}
AndroidManifest.xml
에 INTERNET 권한을 추가한다.
<uses-permission android:name="android.permission.INTERNET" />
이제 데이터가 포함된 JSON 텍스트가 화면에 표시된다.
예외
는 런타임 시 발생할 수 있는 오류로, 앱이 튕기면서 종료하는 경우를 말한다.
다음과 같이 try
catch
로 예외를 처리하면 인터넷이 연결안됨 등의 예외 상황 발생시 앱을 종료하는 대신 화면에 error message를 띄운다.
private fun getMarsPhotos() {
viewModelScope.launch {
try{
val listResult = MarsApi.retrofitService.getPhotos()
_status.value = listResult
} catch (e: Exception){
_status.value = "Failure:( ${e.message}"
}
}
}
Moshi 라이브러리를 사용해 JSON 응답을 LiveData 객체로 파싱한다.
https://android-kotlin-fun-mars-server.appspot.com/photos 서버에서 데이터를 보면, JSON 형식은 다음과 같다.
Moshi
는 JSON 문자열을 Kotlin 객체로 변환하는 JSON parser다. Retrofit은 Moshi와 연동되는 변환기가 있으니 코드를 수정해보자.
Moshi 종속 항목을 추가한다.
// Moshi
implementation 'com.squareup.moshi:moshi-kotlin:1.9.3'
이전에 작성한 Retrofit과 Retrofit with Moshi Converter를 지우고 다음으로 바꾼다.
// Retrofit with Moshi Converter
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
Moshi는 JSON 데이터를 파싱해 Kotlin 객체로 변환하다. 파싱된 결과를 저장하려면 이를 담을 data 클래스가 필요하다.
network 패키지 아래 MarsPhoto.kt를 생성했다. 코드는 다음과 같다.
data class MarsPhoto (
val id: String, @Json(name = "img_src") val imgSrcUrl: String
)
id
는 id
그대로 받아오면 되므로 그냥 string 변수로 작성했다.img_src
는 코틀린 네이밍 컨벤션(카멜 표기법)과 맞지 않다. 따라서 Json의 img_src
를 imgSrcUrl
변수로 받는다.Moshi 빌더를 사용해 Moshi 객체를 만드는 코드를 추가해야 한다.
ScalarsConverterFactory
관련 코드과 import를 삭제하고, Moshi 객체를 만든다.private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
. build()
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
interface MarsApiService {
@GET("photos")
fun getPhotos(): List<MarsPhoto>
}
listResult가 더 이상 String이 아니므로 에러가 나는 코드를 수정한다.