Android Kotlin 기초 08.1: 인터넷에서 데이터 가져오기

이 Codelab은 Android Kotlin 기초 과정의 일부입니다. Codelab을 순서대로 진행하면 이 과정의 학습 효과를 극대화할 수 있습니다. 모든 과정 Codelab은 Android Kotlin 기본사항 Codelab 방문 페이지에 나열되어 있습니다.

소개

빌드하는 거의 모든 Android 앱은 어느 시점에서든 인터넷에 연결해야 합니다. 이 Codelab과 후속 Codelab에서는 웹 서비스에 연결하여 데이터를 검색하고 표시하는 앱을 빌드합니다. 또한 이전 Codelab에서 ViewModel, LiveData, RecyclerView에 관해 알아본 내용을 바탕으로 빌드합니다.

이 Codelab에서는 커뮤니티에서 개발한 라이브러리를 사용하여 네트워크 계층을 빌드합니다. 이렇게 하면 데이터와 이미지를 가져오는 작업이 크게 간소화되고 백그라운드 스레드에서 이미지를 로드하고 로드된 이미지를 캐시하는 등의 Android 권장사항을 준수하는 앱을 빌드할 수 있습니다. 웹 서비스 레이어와 통신하는 등 코드 내 비동기 또는 비차단 섹션의 경우 Kotlin의 코루틴을 사용하도록 앱을 수정합니다. 또한 인터넷 속도가 느리거나 인터넷을 사용할 수 없는 경우 앱의 사용자 인터페이스를 업데이트하여 사용자에게 상황을 알립니다.

기본 요건

  • 프래그먼트를 만들고 사용하는 방법
  • 프래그먼트 간에 이동하고 safeArgs을 사용하여 프래그먼트 간에 데이터를 전달하는 방법
  • ViewModel, ViewModelProvider.Factory, LiveData, LiveData 변환을 비롯한 아키텍처 구성요소를 사용하는 방법
  • 장기 실행 작업에 코루틴을 사용하는 방법

학습할 내용

  • REST 웹 서비스의 정의
  • Retrofit 라이브러리를 사용하여 인터넷에서 REST 웹 서비스에 연결하고 응답 받기
  • Moshi 라이브러리를 사용하여 JSON 응답을 데이터 객체로 파싱하기

실행할 작업

  • 웹 서비스 API 요청을 실행하고 응답을 처리하도록 스타터 앱을 수정합니다.
  • Retrofit 라이브러리를 사용하여 앱의 네트워크 계층을 구현합니다.
  • Moshi 라이브러리를 사용하여 웹 서비스의 JSON 응답을 앱의 라이브 데이터로 파싱합니다.
  • Retrofit의 코루틴 지원을 사용하여 코드를 단순화합니다.

이 Codelab (및 다음 Codelab)에서는 화성에서 판매 중인 부동산을 표시하는 MarsRealEstate라는 스타터 앱을 사용합니다. 이 앱은 웹 서비스에 연결하여 가격, 매매 또는 임대 가능 여부와 같은 세부정보를 비롯한 부동산 데이터를 검색하고 표시합니다. 각 속성을 나타내는 이미지는 NASA의 화성 탐사 로봇이 화성에서 촬영한 실제 사진입니다.

이 Codelab에서 빌드하는 버전의 앱에는 시각적 플래시가 많지 않습니다. 인터넷에 연결하고 웹 서비스를 사용해 원시 속성 데이터를 다운로드하기 위해 앱의 네트워킹 계층 부분에 중점을 둡니다. 데이터가 올바르게 검색되고 파싱되도록 하기 위해 화성의 속성 수만 텍스트 뷰에 출력합니다.

.

MarsRealEstate 앱의 아키텍처에는 두 가지 주요 모듈이 있습니다.

  • RecyclerView로 빌드된 썸네일 속성 이미지 그리드가 포함된 개요 프래그먼트
  • 각 속성에 관한 정보가 포함된 세부정보 뷰 프래그먼트

앱에는 각 프래그먼트의 ViewModel가 있습니다. 이 Codelab에서는 네트워크 서비스 레이어를 만들고 ViewModel이 해당 네트워크 레이어와 직접 통신합니다. 이는 ViewModelRoom 데이터베이스와 통신할 때 이전 Codelab에서 수행한 작업과 유사합니다.

개요 ViewModel은 네트워크를 호출하여 화성 부동산 정보를 가져옵니다. 세부정보 ViewModel에는 세부정보 프래그먼트에 표시되는 단일 Mars 부동산의 세부정보가 포함됩니다. 각 ViewModel에서 수명 주기 인식 데이터 결합과 함께 LiveData를 사용하여 데이터 변경 시 앱 UI를 업데이트합니다.

Navigation 구성요소를 사용하여 두 프래그먼트 간에 이동하고 선택한 속성을 인수로 전달합니다.

이 작업에서는 MarsRealEstate의 시작 앱을 다운로드하여 실행하고 프로젝트 구조를 숙지합니다.

1단계: 프래그먼트 및 탐색 살펴보기

  1. MarsRealEstate 시작 앱을 다운로드하여 Android 스튜디오에서 엽니다.
  2. app/java/MainActivity.kt를 살펴봅니다. 앱은 두 화면 모두에 프래그먼트를 사용하므로 활동의 유일한 작업은 활동의 레이아웃을 로드하는 것입니다.
  3. app/res/layout/activity_main.xml를 살펴봅니다. 활동 레이아웃은 탐색 파일에 정의된 두 프래그먼트의 호스트입니다. 이 레이아웃은 nav_graph 리소스를 사용하여 NavHostFragment와 연결된 탐색 컨트롤러를 인스턴스화합니다.
  4. app/res/navigation/nav_graph.xml를 엽니다. 여기에서 두 프래그먼트 간의 탐색 관계를 확인할 수 있습니다. 탐색 그래프 StartDestinationoverviewFragment를 가리키므로 앱이 실행될 때 개요 프래그먼트가 인스턴스화됩니다.

2단계: Kotlin 소스 파일과 데이터 결합 살펴보기

  1. Project 창에서 app > java를 펼칩니다. MarsRealEstate 앱에는 detail, network, overview의 세 가지 패키지 폴더가 있습니다. 이는 앱의 세 가지 주요 구성요소인 개요 및 세부정보 프래그먼트와 네트워크 레이어의 코드에 해당합니다.
  2. app/java/overview/OverviewFragment.kt를 엽니다. OverviewFragmentOverviewViewModel를 지연 초기화합니다. 즉, OverviewViewModel는 처음 사용될 때 생성됩니다.
  3. onCreateView() 메서드를 확인합니다. 이 메서드는 데이터 결합을 사용하여 fragment_overview 레이아웃을 확장하고 결합 수명 주기 소유자를 자체 (this)로 설정하고 binding 객체의 viewModel 변수를 자체로 설정합니다. 수명 주기 소유자를 설정했으므로 데이터 결합에 사용된 모든 LiveData의 변경이 자동으로 관찰되며, 변경사항에 따라 UI가 업데이트됩니다.
  4. app/java/overview/OverviewViewModel를 엽니다. 대답이 LiveData이고 결합 변수의 수명 주기를 설정했으므로 변경사항이 있으면 앱 UI가 업데이트됩니다.
  5. init 블록을 검사합니다. ViewModel가 생성되면 getMarsRealEstateProperties() 메서드를 호출합니다.
  6. getMarsRealEstateProperties() 메서드를 확인합니다. 이 스타터 앱에서 이 메서드에는 자리표시자 응답이 포함되어 있습니다. 이 Codelab의 목표는 인터넷에서 가져오는 실제 데이터를 사용하여 ViewModel 내에서 응답 LiveData를 업데이트하는 것입니다.
  7. app/res/layout/fragment_overview.xml를 엽니다. 이것이 이 Codelab에서 작업하는 개요 프래그먼트의 레이아웃이며 뷰 모델의 데이터 바인딩이 포함되어 있습니다. OverviewViewModel를 가져온 다음 ViewModel의 응답을 TextView에 바인딩합니다. 이후 Codelab에서는 텍스트 뷰를 RecyclerView의 이미지 그리드로 대체합니다.
  8. 앱을 컴파일하고 실행합니다. 이 앱의 현재 버전에는 시작 응답인 'Set the Mars API Response here!'만 표시됩니다.

화성 부동산 데이터는 REST 웹 서비스로 웹 서버에 저장됩니다. REST 아키텍처를 사용하는 웹 서비스는 표준 웹 구성요소 및 프로토콜을 사용하여 빌드됩니다.

URI를 통해 표준화된 방법으로 웹 서비스에 요청을 전송합니다. 익숙한 웹 URL은 실제로 URI의 한 유형이며, 이 과정에서는 두 용어가 서로 바꿔서 사용됩니다. 예를 들어 이 과정의 앱에서는 다음 서버에서 모든 데이터를 가져옵니다.

https://android-kotlin-fun-mars-server.appspot.com

브라우저에 다음 URL을 입력하면 사용 가능한 화성 부동산 속성의 목록이 표시됩니다.

https://android-kotlin-fun-mars-server.appspot.com/realestate

웹 서비스의 응답은 일반적으로 구조화된 데이터를 나타내는 교환 형식인 JSON 형식으로 지정됩니다. 다음 작업에서 JSON에 대해 자세히 알아보겠지만 간단히 설명하면 JSON 객체는 키-값 쌍의 모음으로, 사전, 해시 맵 또는 연관 배열이라고도 합니다. JSON 객체 모음은 JSON 배열이며, 웹 서비스의 응답으로 다시 가져오는 배열입니다.

이 데이터를 앱으로 가져오려면 앱이 네트워크 연결을 설정하고 해당 서버와 통신한 후 앱이 사용할 수 있는 형식으로 응답 데이터를 수신하고 파싱해야 합니다. 이 Codelab에서는 Retrofit이라는 REST 클라이언트 라이브러리를 사용하여 이 연결을 만듭니다.

1단계: Gradle에 Retrofit 종속 항목 추가

  1. build.gradle (Module: app)을 엽니다.
  2. dependencies 섹션에서 다음과 같은 Retrofit 라이브러리 줄을 추가합니다.
implementation "com.squareup.retrofit2:retrofit:$version_retrofit"
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"


버전 번호는 프로젝트 Gradle 파일에 별도로 정의되어 있습니다. 첫 번째 종속 항목은 Retrofit 2 라이브러리 자체를 위한 것이며, 두 번째 종속 항목은 Retrofit 스칼라 변환기를 위한 것입니다. 이 변환기를 사용하면 Retrofit에서 JSON 결과를 String으로 반환할 수 있습니다. 두 라이브러리는 함께 작동합니다.

  1. Sync Now를 클릭하여 새 종속 항목으로 프로젝트를 다시 빌드합니다.

2단계: MarsApiService 구현

Retrofit은 웹 서비스의 콘텐츠를 기반으로 앱의 네트워크 API를 만듭니다. 웹 서비스에서 데이터를 가져오고 데이터를 디코딩하여 유용한 객체 형식으로 반환하는 방법을 알고 있는 별도의 변환기 라이브러리를 통해 데이터를 라우팅합니다. Retrofit에는 XML 및 JSON과 같이 많이 사용되는 웹 데이터 형식을 위한 지원이 내장되어 있습니다. Retrofit은 궁극적으로 백그라운드 스레드에서 요청을 실행하는 등의 중요한 세부정보를 포함하여 대부분의 네트워크 레이어를 만듭니다.

MarsApiService 클래스는 앱의 네트워크 레이어를 보유합니다. 즉, ViewModel가 웹 서비스와 통신하는 데 사용하는 API입니다. Retrofit 서비스 API를 구현할 클래스입니다.

  1. app/java/network/MarsApiService.kt를 엽니다. 현재 파일에는 웹 서비스의 기본 URL을 위한 상수 하나만 포함되어 있습니다.
private const val BASE_URL = 
   "https://android-kotlin-fun-mars-server.appspot.com"
  1. 이 상수 바로 아래에 Retrofit 빌더를 사용하여 Retrofit 객체를 만듭니다. 요청이 있는 경우 retrofit2.Retrofitretrofit2.converter.scalars.ScalarsConverterFactory를 가져옵니다.
private val retrofit = Retrofit.Builder()
   .addConverterFactory(ScalarsConverterFactory.create())
   .baseUrl(BASE_URL)
   .build()

Retrofit이 웹 서비스 API를 빌드하려면 웹 서비스의 기본 URI와 변환기 팩토리라는 두 가지 이상의 요소가 필요합니다. 변환기는 웹 서비스에서 얻은 데이터로 해야 할 일을 Retrofit에 알립니다. 이 경우에는 Retrofit에서 웹 서비스의 JSON 응답을 가져와 String으로 반환하려고 합니다. Retrofit에는 문자열 및 기타 프리미티브 유형을 지원하는 ScalarsConverter가 있으므로 ScalarsConverterFactory의 인스턴스를 사용하여 빌더에서 addConverterFactory()를 호출합니다. 마지막으로 build()를 호출하여 Retrofit 객체를 만듭니다.

  1. Retrofit 빌더 호출 바로 아래에 Retrofit이 HTTP 요청을 사용하여 웹 서버와 통신하는 방법을 정의하는 인터페이스를 정의합니다. 요청이 있는 경우 retrofit2.http.GETretrofit2.Call를 가져옵니다.
interface MarsApiService {
    @GET("realestate")
    fun getProperties():
            Call<String>
}

지금은 웹 서비스에서 JSON 응답 문자열을 가져오는 것이 목표이며, 이를 위해서는 getProperties() 메서드 하나만 있으면 됩니다. 이 메서드가 실행해야 하는 작업을 Retrofit에 알리려면 @GET 주석을 사용하고 해당 웹 서비스 메서드의 경로 또는 엔드포인트를 지정합니다. 이 경우 엔드포인트는 realestate입니다. getProperties() 메서드가 호출되면 Retrofit은 엔드포인트 realestate를 기본 URL (Retrofit 빌더에서 정의함)에 추가하고 Call 객체를 만듭니다. 이 Call 객체는 요청을 시작하는 데 사용됩니다.

  1. MarsApiService 인터페이스 아래에서 MarsApi라는 공개 객체를 정의하여 Retrofit 서비스를 초기화합니다.
object MarsApi {
    val retrofitService : MarsApiService by lazy { 
       retrofit.create(MarsApiService::class.java) }
}

Retrofit create() 메서드는 MarsApiService 인터페이스를 사용하여 Retrofit 서비스를 만듭니다. 이 호출은 비용이 많이 들고 앱에는 Retrofit 서비스 인스턴스가 하나만 필요하므로 MarsApi라는 공개 객체를 사용하여 앱의 나머지 부분에 서비스를 노출하고 Retrofit 서비스를 지연 초기화합니다. 이제 모든 설정이 완료되었으므로 앱이 MarsApi.retrofitService를 호출할 때마다 MarsApiService를 구현하는 싱글톤 Retrofit 객체를 가져옵니다.

3단계: OverviewViewModel에서 웹 서비스 호출하기

  1. app/java/overview/OverviewViewModel.kt를 엽니다. 아래로 스크롤하여 getMarsRealEstateProperties() 메서드를 찾습니다.
private fun getMarsRealEstateProperties() {
   _response.value = "Set the Mars API Response here!"
}

여기에서 Retrofit 서비스를 호출하고 반환된 JSON 문자열을 처리합니다. 현재는 응답에 자리표시자 문자열만 있습니다.

  1. 응답을 'Set the Mars API Response here!'로 설정하는 자리표시자 줄을 삭제합니다.
  2. getMarsRealEstateProperties() 내에 아래 코드를 추가합니다. 요청이 있는 경우 retrofit2.Callbackcom.example.android.marsrealestate.network.MarsApi를 가져옵니다.

    MarsApi.retrofitService.getProperties() 메서드는 Call 객체를 반환합니다. 그런 다음 해당 객체에서 enqueue()를 호출하여 백그라운드 스레드에서 네트워크 요청을 시작할 수 있습니다.
MarsApi.retrofitService.getProperties().enqueue( 
   object: Callback<String> {
})
  1. 빨간색으로 밑줄이 그어진 단어 object을 클릭합니다. 코드 > 메서드 구현을 선택합니다. 목록에서 onResponse()onFailure()을 모두 선택합니다.


    Android 스튜디오는 각 메서드에 TODO가 있는 코드를 추가합니다.
override fun onFailure(call: Call<String>, t: Throwable) {
       TODO("not implemented") 
}

override fun onResponse(call: Call<String>, 
   response: Response<String>) {
       TODO("not implemented") 
}
  1. onFailure()에서 TODO를 삭제하고 아래와 같이 _response을 실패 메시지로 설정합니다. _response는 텍스트 뷰에 표시되는 내용을 결정하는 LiveData 문자열입니다. 각 상태는 _response LiveData.
    을 업데이트해야 합니다.
    웹 서비스 응답이 실패하면 onFailure() 콜백이 호출됩니다. 이 응답의 경우 _response 상태를 Throwable 인수의 메시지와 연결된 "Failure: "로 설정합니다.
override fun onFailure(call: Call<String>, t: Throwable) {
   _response.value = "Failure: " + t.message
}
  1. onResponse()에서 TODO를 삭제하고 _response을 응답 본문으로 설정합니다. 요청이 성공하고 웹 서비스가 응답을 반환하면 onResponse() 콜백이 호출됩니다.
override fun onResponse(call: Call<String>, 
   response: Response<String>) {
      _response.value = response.body()
}

4단계: 인터넷 권한 정의

  1. MarsRealEstate 앱을 컴파일하고 실행합니다. 앱이 오류와 함께 즉시 닫힙니다.
  2. Android 스튜디오에서 Logcat 탭을 클릭하고 로그에서 다음과 같은 줄로 시작하는 오류를 확인합니다.
Process: com.example.android.marsrealestate, PID: 10646
java.lang.SecurityException: Permission denied (missing INTERNET permission?)

오류 메시지는 앱에 INTERNET 권한이 없을 수도 있음을 나타냅니다. 인터넷에 연결하면 보안 문제가 발생할 수 있으므로 앱은 기본적으로 인터넷에 연결되어 있지 않습니다. 앱이 인터넷에 액세스해야 한다고 Android에 명시적으로 알려야 합니다.

  1. app/manifests/AndroidManifest.xml를 엽니다. <application> 태그 바로 앞에 다음 줄을 추가합니다.
<uses-permission android:name="android.permission.INTERNET" />
  1. 앱을 다시 컴파일하고 실행합니다. 인터넷 연결이 제대로 작동하면 화성 속성 데이터가 포함된 JSON 텍스트가 표시됩니다.
  2. 기기 또는 에뮬레이터에서 뒤로 버튼을 탭하여 앱을 닫습니다.
  3. 기기나 에뮬레이터를 비행기 모드로 전환한 다음 최근 항목 메뉴에서 앱을 다시 열거나 Android 스튜디오에서 앱을 다시 시작합니다.


  1. 비행기 모드를 다시 사용 중지합니다.

이제 Mars 웹 서비스에서 JSON 응답을 받게 되며, 이것은 훌륭한 출발입니다. 그러나 정말 필요한 것은 큰 JSON 문자열이 아닌 Kotlin 객체입니다. Moshi라는 라이브러리가 있습니다. 이 라이브러리는 JSON 문자열을 Kotlin 객체로 변환하는 Android JSON 파서입니다. Retrofit은 Moshi와 연동되는 변환기가 있어서 여기서의 목적에 적합한 라이브러리입니다.

이 작업에서는 Retrofit과 함께 Moshi 라이브러리를 사용하여 웹 서비스의 JSON 응답을 유용한 Mars Property Kotlin 객체로 파싱합니다. 앱이 원시 JSON을 표시하는 대신 반환되는 화성 속성의 개수를 표시하도록 앱을 변경합니다.

1단계: Moshi 라이브러리 종속 항목 추가

  1. build.gradle (Module: app)을 엽니다.
  2. 종속 항목 섹션에서 아래와 같은 코드를 추가하여 Moshi 종속 항목을 포함합니다. Retrofit과 마찬가지로 $version_moshi는 프로젝트 수준 Gradle 파일에 별도로 정의됩니다. 이러한 종속 항목은 핵심 Moshi JSON 라이브러리와 Moshi의 Kotlin 지원을 추가합니다.
implementation "com.squareup.moshi:moshi:$version_moshi"
implementation "com.squareup.moshi:moshi-kotlin:$version_moshi"
  1. dependencies 블록에서 Retrofit 스칼라 변환기를 나타내는 줄을 찾습니다.
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"
  1. converter-moshi을 사용하도록 해당 줄을 변경합니다.
implementation "com.squareup.retrofit2:converter-moshi:$version_retrofit"
  1. Sync Now를 클릭하여 새 종속 항목으로 프로젝트를 다시 빌드합니다.

2단계: MarsProperty 데이터 클래스 구현하기

웹 서비스에서 가져오는 JSON 응답의 샘플 항목은 다음과 같습니다.

[{"price":450000,
"id":"424906",
"type":"rent",
"img_src":"http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631300305227E03_DXXX.jpg"},
...]

위에 표시된 JSON 응답은 대괄호로 표시된 배열입니다. 이 배열에는 중괄호로 묶인 JSON 객체가 포함됩니다. 각 객체에는 콜론으로 구분된 이름-값 쌍의 집합이 포함됩니다. 이름은 따옴표로 묶여 있습니다. 값은 숫자 또는 문자열일 수 있으며 문자열은 따옴표로 묶여 있습니다. 예를 들어 이 속성의 price는 450,000달러이고 img_src는 서버에 있는 이미지 파일의 위치인 URL입니다.

위의 예에서 각 화성 속성 항목에는 다음과 같은 JSON 키와 값의 쌍이 있습니다.

  • price: Mars 속성의 가격입니다(숫자).
  • id: 속성의 ID로, 문자열입니다.
  • type: "rent" 또는 "buy"입니다.
  • img_src: 이미지의 URL로, 문자열입니다.

Moshi는 이 JSON 데이터를 파싱하여 Kotlin 객체로 변환합니다. 이를 위해 파싱된 결과를 저장할 Kotlin 데이터 클래스가 있어야 하므로 다음 단계는 해당 클래스를 만드는 것입니다.

  1. app/java/network/MarsProperty.kt를 엽니다.
  2. 기존 MarsProperty 클래스 정의를 다음 코드로 바꿉니다.
data class MarsProperty(
   val id: String, val img_src: String,
   val type: String,
   val price: Double
)

MarsProperty 클래스의 각 변수는 JSON 객체의 키 이름에 대응합니다. JSON의 유형과 일치하도록 하려면 Doubleprice을 제외한 모든 값에 String 객체를 사용합니다. Double은 모든 JSON 숫자를 나타내는 데 사용할 수 있습니다.

Moshi는 JSON을 파싱할 때 이름과 일치하는 키를 찾아 데이터 객체를 적절한 값으로 채웁니다.

  1. img_src 키에 관한 줄을 아래에 나온 줄로 바꿉니다. 요청이 있는 경우 com.squareup.moshi.Json를 가져옵니다.
@Json(name = "img_src") val imgSrcUrl: String,

JSON 응답의 키 이름으로 인해 Kotlin 속성이 혼란스러워지거나 코딩 스타일과 일치하지 않을 수 있습니다. 예를 들어 JSON 파일에서 img_src 키는 밑줄을 사용하지만 Kotlin 속성은 일반적으로 대문자와 소문자 ('카멜 표기법')를 사용합니다.

데이터 클래스에 JSON 응답의 키 이름과 다른 변수 이름을 사용하려면 @Json 주석을 사용합니다. 이 예에서 데이터 클래스의 변수 이름은 imgSrcUrl입니다. @Json(name = "img_src")를 사용하여 변수를 JSON 속성 img_src에 매핑합니다.

3단계: MarsApiService 및 OverviewViewModel 업데이트하기

이제 MarsProperty 데이터 클래스가 있으므로 Moshi 데이터를 포함하도록 네트워크 API와 ViewModel을 업데이트할 수 있습니다.

  1. network/MarsApiService.kt를 엽니다. ScalarsConverterFactory에 누락된 클래스 오류가 표시될 수 있습니다. 이는 1단계에서 Retrofit 종속 항목을 변경했기 때문에 발생한 것입니다. 이 오류는 곧 수정할 예정입니다.
  2. 파일 상단에서 Retrofit 빌더 바로 앞에 다음 코드를 추가하여 Moshi 인스턴스를 만듭니다. 요청이 있는 경우 com.squareup.moshi.Moshicom.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory를 가져옵니다.
private val moshi = Moshi.Builder()
   .add(KotlinJsonAdapterFactory())
   .build()

Retrofit에서와 마찬가지로 여기서는 Moshi 빌더를 사용하여 moshi 객체를 만듭니다. Moshi의 주석이 Kotlin과 원활하게 작동하려면 KotlinJsonAdapterFactory를 추가한 다음 build()를 호출합니다.

  1. Retrofit 빌더에 ScalarConverterFactory 대신 MoshiConverterFactory가 사용되도록 변경하고 방금 만든 moshi 인스턴스를 전달합니다. 요청이 있는 경우 retrofit2.converter.moshi.MoshiConverterFactory를 가져옵니다.
private val retrofit = Retrofit.Builder()
   .addConverterFactory(MoshiConverterFactory.create(moshi))
   .baseUrl(BASE_URL)
   .build()
  1. ScalarConverterFactory의 import도 삭제합니다.

삭제할 코드:

import retrofit2.converter.scalars.ScalarsConverterFactory
  1. Retrofit이 Call<String>이 아닌 MarsProperty 객체 목록을 반환하도록 MarsApiService 인터페이스를 업데이트합니다.
interface MarsApiService {
   @GET("realestate")
   fun getProperties():
      Call<List<MarsProperty>>
}
  1. OverviewViewModel.kt를 엽니다. getMarsRealEstateProperties() 메서드에서 getProperties().enqueue() 호출까지 아래로 스크롤합니다.
  2. 인수를 Callback<String>에서 Callback<List<MarsProperty>>로 변경합니다.enqueue() 요청이 있는 경우 com.example.android.marsrealestate.network.MarsProperty를 가져옵니다.
MarsApi.retrofitService.getProperties().enqueue( 
   object: Callback<List<MarsProperty>> {
  1. onFailure()에서 인수를 Call<String>에서 Call<List<MarsProperty>>로 변경합니다.
override fun onFailure(call: Call<List<MarsProperty>>, t: Throwable) {
  1. onResponse()의 두 인수를 모두 동일하게 변경합니다.
override fun onResponse(call: Call<List<MarsProperty>>, 
   response: Response<List<MarsProperty>>) {
  1. onResponse() 본문에서 _response.value에 대한 기존 할당을 아래에 표시된 할당으로 바꿉니다. 이제 response.body()MarsProperty 객체 목록이므로 이 목록의 크기는 파싱된 속성의 수입니다. 이 응답 메시지는 속성 수를 출력합니다.
_response.value = 
   "Success: ${response.body()?.size} Mars properties retrieved"
  1. 비행기 모드가 사용 중지되어 있는지 확인합니다. 앱을 컴파일하고 실행합니다. 이번에는 메시지에 웹 서비스에서 반환된 속성의 수가 표시됩니다.

이제 Retrofit API 서비스가 실행되지만 구현해야 하는 두 개의 콜백 메서드가 있는 콜백을 사용합니다. 한 메서드는 성공을 처리하고 다른 메서드는 실패를 처리하며 실패 결과는 예외를 보고합니다. 콜백을 사용하는 대신 예외 처리를 사용하여 코루틴을 사용하면 코드가 더 효율적이고 읽기 쉬워집니다. Retrofit에는 코루틴을 통합하는 라이브러리가 있습니다.

이 작업에서는 네트워크 서비스와 ViewModel를 코루틴을 사용하도록 변환합니다.

1단계: 코루틴 종속 항목 추가

  1. build.gradle (Module: app)을 엽니다.
  2. dependencies 섹션에서 핵심 Kotlin 코루틴 라이브러리와 Retrofit 코루틴 라이브러리 지원을 추가합니다.
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version_kotlin_coroutines"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version_kotlin_coroutines"

implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$version_retrofit_coroutines_adapter"
  1. Sync Now를 클릭하여 새 종속 항목으로 프로젝트를 다시 빌드합니다.

2단계: MarsApiService 및 OverviewViewModel 업데이트

  1. MarsApiService.kt에서 CoroutineCallAdapterFactory를 사용하도록 Retrofit 빌더를 업데이트합니다. 이제 전체 빌더는 다음과 같습니다.
private val retrofit = Retrofit.Builder()
        .addConverterFactory(MoshiConverterFactory.create(moshi))
        .addCallAdapterFactory(CoroutineCallAdapterFactory())
        .baseUrl(BASE_URL)
        .build()

호출 어댑터를 사용하면 Retrofit에서 기본 Call 클래스가 아닌 다른 항목을 반환하는 API를 만들 수 있습니다. 이 경우 CoroutineCallAdapterFactory를 사용하면 getProperties()가 반환하는 Call 객체를 Deferred 객체로 대체할 수 있습니다.

  1. getProperties() 메서드에서 Call<List<MarsProperty>>Deferred<List<MarsProperty>>로 변경합니다. 요청이 있는 경우 kotlinx.coroutines.Deferred를 가져옵니다. 전체 getProperties() 메서드는 다음과 같습니다.
@GET("realestate")
fun getProperties():
   Deferred<List<MarsProperty>>

Deferred 인터페이스는 결과 값을 반환하는 코루틴 작업을 정의합니다 (DeferredJob에서 상속됨). Deferred 인터페이스에는 await()라는 메서드가 포함되어 있으며, 이 메서드는 값이 준비될 때까지 차단하지 않고 코드가 대기하도록 한 후 해당 값을 반환합니다.

  1. OverviewViewModel.kt를 엽니다. init 블록 바로 앞에 코루틴 작업을 추가합니다.
private var viewModelJob = Job()
  1. 기본 디스패처를 사용하여 새 작업의 코루틴 범위를 만듭니다.
private val coroutineScope = CoroutineScope(
   viewModelJob + Dispatchers.Main )

Dispatchers.Main 디스패처는 UI 스레드를 사용하여 작업을 실행합니다. Retrofit은 백그라운드 스레드에서 모든 작업을 실행하므로 범위에 다른 스레드를 사용할 이유가 없습니다. 이렇게 하면 결과를 얻을 때 MutableLiveData의 값을 쉽게 업데이트할 수 있습니다.

  1. getMarsRealEstateProperties() 내부의 모든 코드를 삭제합니다. 여기서는 enqueue() 호출과 onFailure()onResponse() 콜백 대신 코루틴을 사용합니다.
  2. getMarsRealEstateProperties() 내에서 코루틴을 실행합니다.
coroutineScope.launch { 

}


Retrofit이 네트워크 작업에 대해 반환하는 Deferred 객체를 사용하려면 코루틴 내에 있어야 하므로 여기에서 방금 만든 코루틴을 실행합니다. 여전히 기본 스레드에서 코드를 실행하지만 이제 코루틴이 동시성을 관리하도록 합니다.

  1. launch 블록 내에서 retrofitService 객체에 getProperties()를 호출합니다.
var getPropertiesDeferred = MarsApi.retrofitService.getProperties()

MarsApi 서비스에서 getProperties()를 호출하면 백그라운드 스레드에서 네트워크 호출이 생성되고 시작되어 해당 작업의 Deferred 객체가 반환됩니다.

  1. launch 블록 내부에서 예외를 처리하는 try/catch 블록도 추가합니다.
try {

} catch (e: Exception) {
  
}
  1. try {} 블록 내에서 Deferred 객체에서 await()을 호출합니다.
var listResult = getPropertiesDeferred.await()

Deferred 객체에서 await()를 호출하면 값이 준비되었을 때 네트워크 호출의 결과가 반환됩니다. await() 메서드는 차단되지 않으므로 Mars API 서비스는 UI 스레드의 범위에 있기 때문에 중요한 현재 스레드를 차단하지 않고 네트워크에서 데이터를 가져옵니다. 작업이 완료되면 코드는 중단된 지점부터 계속 실행됩니다. 이는 예외를 포착할 수 있도록 try {} 내에 있습니다.

  1. 또한 try {} 블록 내에서 await() 메서드 뒤에 성공적인 응답의 응답 메시지를 업데이트합니다.
_response.value = 
   "Success: ${listResult.size} Mars properties retrieved"
  1. catch {} 블록 내부에서 실패 응답을 처리합니다.
_response.value = "Failure: ${e.message}"


이제 전체 getMarsRealEstateProperties() 메서드는 다음과 같습니다.

private fun getMarsRealEstateProperties() {
   coroutineScope.launch {
       var getPropertiesDeferred = 
          MarsApi.retrofitService.getProperties()
       try {          
           _response.value = 
              "Success: ${listResult.size} Mars properties retrieved"
       } catch (e: Exception) {
           _response.value = "Failure: ${e.message}"
       }
   }
}
  1. 클래스 하단에 다음 코드를 사용하여 onCleared() 콜백을 추가합니다.
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}

ViewModel를 사용하는 OverviewFragment가 사라지므로 ViewModel가 소멸되면 데이터 로딩이 중지되어야 합니다. ViewModel가 소멸될 때 로드를 중지하려면 onCleared()를 재정의하여 작업을 취소합니다.

  1. 앱을 컴파일하고 실행합니다. 이번에도 이전 작업과 동일한 결과 (속성 수 보고서)가 표시되지만 코드가 더 간단하고 오류 처리가 더 간단합니다.

Android 스튜디오 프로젝트: MarsRealEstateNetwork

REST 웹 서비스

  • 웹 서비스는 앱이 요청을 실행하고 데이터를 다시 가져올 수 있도록 지원하는 인터넷 서비스입니다.
  • 일반적인 웹 서비스는 REST 아키텍처를 사용합니다. REST 아키텍처를 제공하는 웹 서비스를 RESTful 서비스라고 합니다. RESTful 웹 서비스는 표준 웹 구성요소 및 프로토콜을 사용하여 빌드됩니다.
  • 표준화된 방법으로 URI를 통해 REST 웹 서비스에 요청을 전송합니다.
  • 웹 서비스를 사용하려면 앱은 네트워크 연결을 설정하고 서비스와 통신해야 합니다. 그런 다음 앱은 사용할 수 있는 형식으로 응답 데이터를 수신하고 파싱해야 합니다.
  • Retrofit 라이브러리는 앱의 REST 웹 서비스 요청을 지원하는 클라이언트 라이브러리입니다.
  • 변환기를 사용하여 웹 서비스에 전송하고 웹 서비스에서 가져오는 데이터로 해야 할 일을 Retrofit에 알립니다. 예를 들어 ScalarsConverter 변환기는 웹 서비스 데이터를 String 또는 다른 프리미티브로 취급합니다.
  • 앱이 인터넷에 연결할 수 있으려면 Android 매니페스트에 "android.permission.INTERNET" 권한을 추가합니다.

JSON 파싱

  • 웹 서비스의 응답은 구조화된 데이터를 나타내는 일반적인 교환 형식인 JSON 형식으로 지정되는 경우가 많습니다.
  • JSON 객체는 키-값 쌍 모음입니다. 이 컬렉션을 사전, 해시 맵 또는 연관 배열이라고도 합니다.
  • JSON 객체 모음은 JSON 배열입니다. 웹 서비스의 응답으로 JSON 배열을 받게 됩니다.
  • 키-값 쌍의 키는 따옴표로 묶입니다. 이 값은 숫자이거나 문자열일 수 있습니다. 문자열도 따옴표로 묶여 있습니다.
  • Moshi 라이브러리는 JSON 문자열을 Kotlin 객체로 변환하는 Android JSON 파서입니다. Retrofit에는 Moshi와 호환되는 변환기가 있습니다.
  • Moshi는 JSON 응답의 키와 이름이 같은 데이터 객체의 속성을 일치시킵니다.
  • 키에 다른 속성 이름을 사용하려면 해당 속성에 @Json 주석과 JSON 키 이름으로 주석을 추가합니다.

Retrofit 및 코루틴

  • 호출 어댑터를 사용하면 Retrofit에서 기본 Call 클래스 이외의 항목을 반환하는 API를 만들 수 있습니다. CoroutineCallAdapterFactory 클래스를 사용하여 Call을 코루틴 Deferred로 바꿉니다.
  • Deferred 객체에서 await() 메서드를 사용하여 값이 준비될 때까지 차단 없이 코루틴 코드가 대기하도록 한 다음 값을 반환합니다.

Udacity 과정:

Android 개발자 문서:

Kotlin 문서:

기타:

이 섹션에는 강사가 진행하는 과정의 일부로 이 Codelab을 진행하는 학생에게 출제할 수 있는 과제가 나열되어 있습니다. 다음 작업은 강사가 결정합니다.

  • 필요한 경우 과제를 할당합니다.
  • 과제 제출 방법을 학생에게 알립니다.
  • 과제를 채점합니다.

강사는 이러한 추천을 원하는 만큼 사용할 수 있으며 적절하다고 생각되는 다른 과제를 출제해도 됩니다.

이 Codelab을 직접 진행하는 경우 이러한 과제를 자유롭게 사용하여 배운 내용을 테스트해 보세요.

질문에 답하세요

질문 1

Retrofit이 웹 서비스 API를 빌드하는 데 필요한 두 가지 핵심 요소는 무엇인가요?

▢ 웹 서비스의 기본 URI 및 GET 쿼리

▢ 웹 서비스의 기본 URI 및 변환기 팩토리

▢ 웹 서비스 네트워크 연결 및 승인 토큰

▢ 변환기 팩토리 및 응답의 파서

질문 2

Moshi 라이브러리의 용도는 무엇인가요?

▢ 웹 서비스에서 데이터를 다시 가져오는 것

▢ Retrofit과 상호작용하여 웹 서비스를 요청하는 것

▢ 웹 서비스의 JSON 응답을 Kotlin 데이터 객체로 파싱합니다.

▢ JSON 응답의 키와 일치하도록 Kotlin 객체의 이름을 바꾸는 것

질문 3

Retrofit 호출 어댑터는 어떤 용도로 사용되나요?

▢ Retrofit이 코루틴을 사용할 수 있게 합니다.

▢ 웹 서비스 응답을 Kotlin 데이터 객체에 맞게 조정합니다.

▢ Retrofit 호출을 웹 서비스 호출로 변경합니다.

▢ Retrofit에서 기본 Call 클래스가 아닌 다른 항목을 반환하는 기능을 추가합니다.

다음 강의를 시작합니다. 8.2 인터넷에서 이미지 로드 및 표시

이 과정의 다른 Codelab 링크는 Android Kotlin 기초 Codelab 방문 페이지를 참고하세요.