본문 바로가기

안드로이드 Android/Android Study Jams

8. Connect to the internet - Get data from the internet

github.com/google-developer-training/android-kotlin-fundamentals-starter-apps/tree/master/MarsRealEstate-Starter

 

google-developer-training/android-kotlin-fundamentals-starter-apps

android-kotlin-fundamentals-starter-apps. Contribute to google-developer-training/android-kotlin-fundamentals-starter-apps development by creating an account on GitHub.

github.com

 

Explore the MarsRealEstate starter app

 

fragment_overview.xml을 보면

    <data>
        <variable
            name="viewModel"
            type="com.example.android.marsrealestate.overview.OverviewViewModel" />
    </data>

OverviewViewModel이 import 되어 있다.

 

 

Connect to a web service with Retrofit

Note: 익숙한 웹 URL은 사실 URI의 한 종류이다. URL과 URI 모두 이 코스 전반적으로 서로 바꿔가며 사용된다.

 

이번 레슨의 앱에서는 다음 서버 URI를 사용해 모든 데이터를 가져올 것이다.

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

다음 URL을 브라우저에 타이핑하면 화성의 모든 부동산 값 리스트를 얻을 수 있다.

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

 

Step 1: Gradle에 Retrofit 디펜던시 추가하기

build.gradle (Module: app)

dependencies {
    ...

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

 

Step 2: Java 8 language feature를 위한 support 추가하기

build.gradle (Module: app)

android {
  ...

  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
  
  kotlinOptions {
    jvmTarget = JavaVersion.VERSION_1_8.toString()
  }
}

 

Step 3: MarsApiService 구현하기

MarsApiService.kt

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

Retrofit은 웹 서비스 API를 빌드하기 위해서는 웹 서비스를 위한 base URI와 converter factory가 필요하다. converter는 Retrofit에게 웹 서비스로부터 돌아오는 데이터로 무엇을 할 지 알려준다. 여기서는 Retrofit이 웹 서비스로부터 JSON 응답을 가져오고 String으로 리턴하는 것을 원한다. Retrofit은 문자열과 다른 primitive type을 지원하는 ScalarsConverter가 있으므로 builder에 ScalarsConverterFactory의 인스턴스와 함께 addConverterFactory()를 호출한다. 마지막으로 Retrofit 객체를 생성하기 위해 build()를 호출한다.

 

HTTP request를 사용하여 Retrofit이 웹 서버에 어떻게 말하는지 정의하는 인터페이스를 정의한다.

interface MarsApiService {
    @GET("realestate")
    fun getProperties():
            Call<String>
}

 

Retrofit에게 이 메서드가 무엇을 해야 하는지 말하기 위해 @GET을 사용하고 웹 서비스 메서드를 위해 path나 endpoint를 지정한다. 이 경우, endpoint는 realestate이다. getProperties()가 호출되면 Retrofit은 (Retrofit builder에 정의한) base URL에 endpoint를 덧붙이고 Call 객체를 생성한다. Call 객체는 request를 시작하기 위해 사용된다.

 

Retrofit 서비스를 초기화하기 위해 MarsApi라는 public object를 정의한다. 이는 서비스 object를 생성할 때 사용하는 일반적인 Kotlin 코드 패턴이다.

object MarsApi {
    val retrofitService : MarsApiService by lazy { 
       retrofit.create(MarsApiService::class.java) }
}

이 호출은 계산적으로 비효율적이므로 Retrofit 서비스를 lazy하게 초기화한다. 앱은 Retrofit 서비스 인스턴스를 하나만 필요로 하므로 MarsApi라는 public object를 사용하여 앱의 다른 곳에 이 서비스를 드러낸다. 이제 한번 설정이 되면 앱이 MarsApi.retrofitService를 호출할 때마다 MarsApiService를 구현하는 singleton Retrofit object를 얻는다.

Note: "lazy instantication"은 불필요한 계산이나 다른 계산하는 리소스를 피하기 위해 실제로 객체가 필요할 때까지 객체 생성이 의도적으로 지연되는 것이라는 것을 기억하라.

 

Step 4: OverviewViewModel에 웹 서비스 호출하기

OverviewViewModel.kt

    private fun getMarsRealEstateProperties() {
        // Start the network request on a background thread.
        MarsApi.retrofitService.getProperties().enqueue(
                object: Callback<String> {
                    override fun onFailure(call: Call<String>, t: Throwable) {
                        _response.value = "Failure " + t.message
                    }

                    override fun onResponse(call: Call<String>, response: Response<String>) {
                        _response.value = response.body()
                    }

                }
        )
    }

Retrofit 서비스를 호출하고 JSON 문자열을 다룰 메서드이다. MarsApi.retrofitService.getProperties() 메서드는 Call object를 리턴한다. 그러고 나서 백그라운드 스레드에 network request를 시작하기 위해 그 object에 enqueue()를 호출할 수 있다.

 

Step 5: 인터넷 퍼미션 정의하기

AndroidManifest.xml

    <uses-permission android:name="android.permission.INTERNET" />

 

 

 

 

Parse the JSON response with Moshi

Step 1: Moshi 라이브러리 디펜던시 추가

기존에 있던 retrofit2 관련 디펜던시 두 줄을 지우고 다음을 추가한다.

build.gradle (Module: app)

    implementation "com.squareup.retrofit2:converter-moshi:$version_retrofit"
    implementation "com.squareup.moshi:moshi-kotlin:$version_moshi"

 

Moshi라는 라이브러리는 JSON 문자열을 Kotlin object로 변환하는 Android JSON parser이다.

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

MarsProperty.kt

data class MarsProperty(
        val id: String,
        @Json(name = "img_src") val imgSrcUrl: String,
        val type: String,
        val price: Double
)

Double은 아무 JSON 숫자에 사용될 수 있다.

간혹 JSON response의 키 이름이 혼동시키는 Kotlin 값을 만들거나 본인의 코딩 스타일에 맞지 않을 수 있다. 예를 들어 Kotlin 값은 일반적으로 camel case를 사용하는 반면, JSON 파일에서는 underscore를 사용한다. 데이터 클래스에서 JSON response의 키 이름과 다른 변수 이름을 사용하려면 @Json 어노테이션을 사용한다.

 

Step 3: MarsApiService와 OverviewViewModel 업데이트 하기

MarsApiService.kt

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

interface MarsApiService {
    @GET("realestate")
    fun getProperties():
            Call<List<MarsProperty>>
}

...

 

OverviewViewModel.kt

...

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

                    override fun onResponse(call: Call<List<MarsProperty>>,
                                            response: Response<List<MarsProperty>>) {
                        _response.value =
                                "Success: ${response.body()?.size} Mars properties retrieved"
                    }

                }
                
...

 

 

 

 

Use coroutines with Retrofit

Step 1: MarsApiService와 OverviewViewModel 업데이트 하기

MarsApiService.kt

...

interface MarsApiService {
    @GET("realestate")
    suspend fun getProperties(): List<MarsProperty>
}

...

 

OverviewViewModel.kt

...

    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}"
            }
        }
    }
    
...

ViewModelScope는 앱의 각 ViewModel을 위해 정의된 빌트인 코루틴 스코프이다. 이 scope에서 launch 된 어떤 코루틴이든 ViewModel이 사라지면 자동으로 취소된다.

 

developer.android.com/codelabs/kotlin-android-training-internet-data?continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fkotlin-fundamentals-eight%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fkotlin-android-training-internet-data#7

 

Android Kotlin Fundamentals: 8.1 Getting data from the internet

Learn how to use community-developed libraries to connect to a web service to retrieve and display data in your Android Kotlin app. Also learn how to handle potential network errors.

developer.android.com

 

Homework

Answer these questions

Q1. 2 / Q2. 3