Android Kotlin の基礎 08.1: インターネットからデータを取得する

この Codelab は、Android Kotlin の基礎コースの一部です。このコースを最大限に活用するには、Codelab を順番に進めることをおすすめします。コースのすべての Codelab は、Android Kotlin の基礎の Codelab のランディング ページに一覧表示されています。

はじめに

作成するほとんどの Android アプリは、どこかの時点でインターネットに接続する必要があります。この Codelab とそれに続く Codelab では、ウェブサービスに接続してデータを取得し、表示するアプリを構築します。また、これまでの Codelab で学習した ViewModelLiveDataRecyclerView についても復習します。

この Codelab では、コミュニティで開発されたライブラリを使用してネットワーク レイヤを構築します。この方法により、データと画像の取得が大幅に簡素化され、Android のベスト プラクティス(バックグラウンド スレッドでの画像の読み込みや、読み込んだ画像のキャッシュ保存など)に沿ってアプリを構築できます。ウェブ サービス レイヤとの通信など、コード内の非同期またはノンブロッキングのセクションについては、Kotlin のコルーチンを使用するようにアプリを変更します。また、インターネットが低速な場合や利用できない場合は、アプリのユーザー インターフェースを更新して、ユーザーに状況を知らせるようにします。

前提となる知識

  • フラグメントの作成方法と使用方法。
  • フラグメント間を移動する方法、safeArgs を使用してフラグメント間でデータを渡す方法。
  • ViewModelViewModelProvider.FactoryLiveDataLiveData 変換などのアーキテクチャ コンポーネントの使用方法。
  • 長時間実行タスクにコルーチンを使用する方法。

学習内容

  • REST ウェブサービスとは何か。
  • Retrofit ライブラリを使用して、インターネット上の REST ウェブサービスに接続し、レスポンスを取得する方法。
  • Moshi ライブラリを使用して、JSON レスポンスを解析し、データ オブジェクトに変換する方法。

演習内容

  • スターター アプリに変更を加え、ウェブサービス API リクエストを送信してレスポンスを処理します。
  • Retrofit ライブラリを使用して、アプリにネットワーク レイヤを実装します。
  • Moshi ライブラリを使用して、ウェブサービスからの JSON レスポンスを解析し、アプリのライブデータに変換します。
  • コルーチンに対する Retrofit のサポートを使用して、コードを簡素化します。

この Codelab(および以降の Codelab)では、火星で販売されている不動産物件を表示する MarsRealEstate というスターター アプリを使用します。このアプリは、ウェブサービスに接続して、価格や販売または賃貸が可能かどうかなどの詳細を含む物件データを取得し、表示します。各物件を表す画像は、NASA の火星探査機が撮影した火星の実際の写真です。

この Codelab で作成するバージョンのアプリには、視覚的に凝ったところはありません。このバージョンは、インターネットに接続し、ウェブサービスを使用して未加工のプロパティ データをダウンロードする、アプリのネットワーク レイヤ部分に焦点を当てています。データの取得と解析が適切に行われたことを確認するため、火星の不動産物件の数をテキストビューに出力するだけです。

.

MarsRealEstate アプリのアーキテクチャには、次の 2 つのメイン モジュールがあります。

  • 概要フラグメント。RecyclerView で作成されたサムネイル プロパティ画像のグリッドが含まれています。
  • 各プロパティに関する情報を含む詳細ビュー フラグメント。

アプリには、フラグメントごとに ViewModel があります。この Codelab では、ネットワーク サービスのレイヤを作成します。ViewModel は、このネットワーク レイヤと直接通信します。これは、前の Codelab で ViewModelRoom データベースと通信したときに行った作業と似ています。

概要 ViewModel は、火星の不動産情報を取得するためにネットワーク呼び出しを行う役割を担います。詳細 ViewModel には、詳細フラグメントに表示される火星の不動産の詳細が保持されます。各 ViewModel では、データが変更されたときにアプリ UI を更新するため、ライフサイクル対応のデータ バインディングで LiveData を使用します。

Navigation コンポーネントを使用して、2 つのフラグメント間を移動し、選択したプロパティを引数として渡します。

このタスクでは、MarsRealEstate のスターター アプリをダウンロードして実行し、プロジェクトの構造を把握します。

ステップ 1: フラグメントとナビゲーションを確認する

  1. MarsRealEstate スターター アプリをダウンロードして、Android Studio で開きます。
  2. app/java/MainActivity.kt を調べます。アプリは両方の画面でフラグメントを使用するため、アクティビティのタスクはアクティビティのレイアウトを読み込むことだけです。
  3. app/res/layout/activity_main.xml を調べます。アクティビティ レイアウトは、ナビゲーション ファイルで定義された 2 つのフラグメントのホストです。このレイアウトは、nav_graph リソースを使用して NavHostFragment とそれに関連付けられたナビゲーション コントローラをインスタンス化します。
  4. app/res/navigation/nav_graph.xml を開きます。ここでは、2 つのフラグメント間のナビゲーション関係を確認できます。ナビゲーション グラフ StartDestinationoverviewFragment を指しているため、アプリが起動されると、Overview フラグメントがインスタンス化されます。

ステップ 2: Kotlin ソースファイルとデータ バインディングを調べる

  1. [Project] ペインで、[app] > [java] を展開します。MarsRealEstate アプリには detailnetworkoverview の 3 つのパッケージ フォルダがあることに注目してください。これらは、アプリの 3 つの主要なコンポーネント(概要と詳細のフラグメント、ネットワーク レイヤのコード)に対応しています。
  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 の一種です。このコースでは、URL と URI の両方を同じ意味で使用します。たとえば、このレッスンのアプリでは、次のサーバーからすべてのデータを取得します。

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

ブラウザに次の URL を入力すると、利用可能な火星のすべての不動産物件のリストを取得できます。

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

ウェブサービスからのレスポンスは通常、構造化データを表す交換形式である JSON で書式設定されます。JSON については次のタスクで詳しく説明しますが、簡単に説明すると、JSON オブジェクトは Key-Value ペアのコレクションです。これは、辞書、ハッシュマップ、連想配列とも呼ばれます。JSON オブジェクトのコレクションは JSON 配列であり、ウェブサービスからのレスポンスとして返される配列です。

このデータをアプリに取り込むには、アプリでネットワーク接続を確立してサーバーと通信し、レスポンス データを受信して解析し、アプリが使用できる形式に変換する必要があります。この Codelab では、Retrofit という REST クライアント ライブラリを使用してこの接続を行います。

ステップ 1: Retrofit の依存関係を Gradle に追加する

  1. build.gradle(モジュール: app)を開きます。
  2. dependencies セクションに、Retrofit ライブラリ用に次の行を追加します。
implementation "com.squareup.retrofit2:retrofit:$version_retrofit"
implementation "com.squareup.retrofit2:converter-scalars:$version_retrofit"


バージョン番号はプロジェクトの Gradle ファイルで個別に定義されています。1 つ目の依存関係は Retrofit 2 ライブラリ用で、2 つ目の依存関係は Retrofit スカラー コンバータ用です。このコンバータにより、Retrofit は JSON 結果を String として返すことができます。2 つのライブラリは連携して機能します。

  1. [Sync Now] をクリックして、新しい依存関係でプロジェクトを再ビルドします。

ステップ 2: MarsApiService を実装する

Retrofit は、ウェブサービスからのコンテンツに基づいて、アプリ用のネットワーク API を作成します。ウェブサービスからデータを取得し、データのデコード方法を認識している別個のコンバータ ライブラリを経由して、有用なオブジェクトの形式でデータを返します。Retrofit には、XML や JSON などの一般的なウェブデータ形式のサポートが組み込まれています。最終的に Retrofit は、バックグラウンド スレッドでリクエストを実行するなどの重要な詳細情報を含め、ネットワーク レイヤのほとんどを作成します。

MarsApiService クラスはアプリのネットワーク レイヤを保持します。つまり、これは ViewModel がウェブサービスと通信するために使用する API です。このクラスで Retrofit サービス API を実装します。

  1. app/java/network/MarsApiService.kt を開きます。現時点では、このファイルにはウェブサービスのベース URL を表す定数が 1 つだけ含まれています。
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 とコンバータ ファクトリの少なくとも 2 つが必要です。コンバータは、ウェブサービスから返されたデータをどのように処理するかを 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() という 1 つのメソッドだけが必要です。このメソッドが何を行うべきかを Retrofit に伝えるには、@GET アノテーションを使用して、そのウェブサービス メソッドのパス(エンドポイント)を指定します。ここでのエンドポイントの名前は realestate です。getProperties() メソッドが呼び出されると、Retrofit はリクエストの開始に使用するベース URL(Retrofit ビルダーで定義した URL)の末尾にエンドポイント realestate を追加し、Call オブジェクトを作成します。この Call オブジェクトは、リクエストの開始に使用されます。

  1. MarsApiService インターフェースの下で、MarsApi という公開オブジェクトを定義して Retrofit サービスを初期化します。
object MarsApi {
    val retrofitService : MarsApiService by lazy { 
       retrofit.create(MarsApiService::class.java) }
}

Retrofit の create() メソッドは、MarsApiService インターフェースを使用して Retrofit サービス自体を作成します。この呼び出しはコストが高く、アプリに必要な Retrofit サービス インスタンスは 1 つだけであるため、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 をクリックします。[Code] > [Implement methods] を選択します。リストから onResponse()onFailure() の両方を選択します。


    Android Studio は、各メソッドに 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 Studio で [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 Studio からアプリを再起動します。


  1. 機内モードを再度オフにします。

これで、Mars ウェブサービスから JSON レスポンスを取得できます。スタートとしては上々です。しかし、実際に必要なのは Kotlin オブジェクトであり、長い JSON 文字列ではありません。そこで、Moshi というライブラリを利用します。これは、JSON 文字列を Kotlin オブジェクトに変換する Android JSON パーサーです。Retrofit は Moshi と連携するコンバータを備えているため、ここでの目的に最適のライブラリです。

このタスクでは、Moshi ライブラリと Retrofit を併用して、ウェブサービスからの JSON レスポンスを解析し、有用な Mars Property Kotlin オブジェクトに変換します。アプリを変更して、未加工の JSON を表示する代わりに、返された火星のプロパティの数を表示するようにします。

ステップ 1: Moshi ライブラリの依存関係を追加する

  1. build.gradle(モジュール: app)を開きます。
  2. dependencies セクションに下記のコードを追加して、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 形式の Key-Value ペアが含まれています。

  • price: 火星の物件の価格(数値)。
  • 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 の型をマッチングするには、priceDouble)を除くすべての値に対して 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 の missing-class エラーが表示されることがあります。これは、ステップ 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. ScalarConverterFactory の代わりに MoshiConverterFactory を使用するように Retrofit ビルダーを変更し、先ほど作成した moshi インスタンスを渡します。リクエストされたら、retrofit2.converter.moshi.MoshiConverterFactory をインポートします。
private val retrofit = Retrofit.Builder()
   .addConverterFactory(MoshiConverterFactory.create(moshi))
   .baseUrl(BASE_URL)
   .build()
  1. ScalarConverterFactory のインポートも削除します。

削除するコード:

import retrofit2.converter.scalars.ScalarsConverterFactory
  1. MarsApiService インターフェースを更新して、Retrofit が Call<String> を返すのではなく、MarsProperty オブジェクトのリストを返すようにします。
interface MarsApiService {
   @GET("realestate")
   fun getProperties():
      Call<List<MarsProperty>>
}
  1. OverviewViewModel.kt を開きます。下にスクロールして、getMarsRealEstateProperties() メソッド内の getProperties().enqueue() の呼び出しを表示します。
  2. 引数 enqueue()Callback<String> から Callback<List<MarsProperty>> に変更します。リクエストされたら、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 サービスが実行されますが、実装する必要がある 2 つのコールバック メソッドを含むコールバックを使用します。1 つのメソッドは成功を処理し、もう 1 つのメソッドは失敗を処理します。失敗の結果は例外を報告します。コールバックの代わりに例外処理でコルーチンを使用できれば、コードの効率が上がり、読みやすくなります。Retrofit には、コルーチンを統合するライブラリが用意されています。

このタスクでは、ネットワーク サービスと ViewModel を変換してコルーチンを使用します。

ステップ 1: コルーチンの依存関係を追加する

  1. build.gradle(モジュール: 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. 起動ブロック内で、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 Studio プロジェクト: MarsRealEstateNetwork

REST ウェブサービス

  • ウェブサービスは、アプリからリクエストを送信してデータを取得することを可能にするインターネット上のサービスです。
  • 一般的なウェブサービスは REST アーキテクチャを使用します。REST アーキテクチャを提供するウェブサービスを RESTful サービスと呼びます。RESTful ウェブサービスは、標準のウェブ コンポーネントとプロトコルを使用して構築されます。
  • REST ウェブサービスへのリクエストは、URI を介する標準的な方法で行います。
  • アプリでウェブサービスを使用するには、ネットワーク接続を確立してサービスと通信する必要があります。アプリは、レスポンス データを受信して解析し、アプリが使用できる形式に変換する必要があります。
  • Retrofit ライブラリは、アプリが REST ウェブサービスにリクエストを送信することを可能にするクライアント ライブラリです。
  • コンバータを使用して、ウェブサービスに送信するデータとウェブサービスから返されたデータをどのように処理するかを Retrofit に伝えます。たとえば、ScalarsConverter コンバータは、ウェブサービス データを String またはその他のプリミティブとして扱います。
  • アプリがインターネットに接続できるようにするには、Android マニフェストに "android.permission.INTERNET" 権限を追加します。

JSON 解析

  • ウェブサービスからのレスポンスは、多くの場合、構造化データを表す一般的な交換形式である JSON で書式設定されています。
  • JSON オブジェクトは Key-Value ペアのコレクションです。このコレクションは、ディクショナリ、ハッシュマップ、連想配列とも呼ばれます。
  • JSON オブジェクトのコレクションは JSON 配列です。ウェブサービスからのレスポンスは JSON 配列として取得されます。
  • Key-Value ペアのキーは引用符で囲まれています。値は数値または文字列です。文字列も引用符で囲まれています。
  • 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

ウェブサービス API を構築するために Retrofit で必要となる 2 つの重要なものは何ですか。

▢ ウェブサービスのベース URI と GET クエリ。

▢ ウェブサービスのベース URI とコンバータ ファクトリ。

▢ ウェブサービスへのネットワーク接続と認証トークン。

▢ コンバータ ファクトリとレスポンスのパーサー。

問題 2

Moshi ライブラリの目的は何ですか。

▢ ウェブサービスからデータを取得する。

▢ ウェブサービス リクエストを行うために Retrofit とやり取りする。

▢ ウェブサービスからの JSON レスポンスを解析して Kotlin データ オブジェクトに変換する。

▢ JSON レスポンスのキーと一致するように Kotlin オブジェクトの名前を変更する。

問題 3

Retrofit 呼び出しのアダプターは何に使用されますか。

▢ Retrofit でコルーチンを使用できるようする。

▢ ウェブサービス レスポンスを Kotlin データ オブジェクトに適合させる。

▢ Retrofit 呼び出しをウェブサービス呼び出しに変更する。

▢ Retrofit でデフォルトの Call クラス以外のものを返す機能を追加する。

次のレッスンに進む: 8.2 インターネットから画像を読み込んで表示する

このコースの他の Codelab へのリンクについては、Android Kotlin の基礎の Codelab のランディング ページをご覧ください。