Android Kotlin の基礎 08.3 インターネット データのフィルタリングと詳細表示

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

はじめに

このレッスンでこれまでの Codelab では、ウェブサービスから火星の不動産に関するデータを取得する方法と、そのデータから画像を読み込んで表示するグリッド レイアウトの RecyclerView を作成する方法を学習しました。この Codelab では、火星の不動産物件を賃貸用か販売用かでフィルタする機能を実装して、MarsRealEstate アプリを完成させます。また、概要でユーザーが物件の写真をタップしたときに、その物件の詳細が表示される詳細ビューも作成します。

前提となる知識

  • フラグメントの作成方法と使用方法。
  • フラグメント間を移動する方法と、Safe Args(Gradle プラグイン)を使用してフラグメント間でデータを渡す方法。
  • ビューモデル、ビューモデル ファクトリ、変換、LiveData などのアーキテクチャ コンポーネントの使用方法。
  • REST ウェブサービスから JSON エンコードされたデータを取得し、Retrofit ライブラリと Moshi ライブラリを使用してそのデータを解析して Kotlin オブジェクトに変換する方法。

学習内容

  • レイアウト ファイルで複雑なバインディング式を使用する方法
  • クエリ オプションを使用してウェブサービスに対する Retrofit リクエストを作成する方法。

演習内容

  • MarsRealEstate アプリを変更して、販売中の火星の不動産物件(賃貸中の物件ではない)をドル記号アイコンでマークします。
  • 概要ページのオプション メニューを使用して、火星の不動産物件をタイプでフィルタするウェブサービス リクエストを作成します。
  • 火星のプロパティの詳細フラグメントを作成し、ナビゲーションを使用してそのフラグメントを概要グリッドに接続して、プロパティ データをそのフラグメントに渡します。

この Codelab(および関連する Codelab)では、火星で販売されている不動産物件を表示する MarsRealEstate というアプリを使用します。このアプリは、インターネット サーバーに接続して、価格や販売または賃貸が可能かどうかなどの詳細を含む物件データを取得して表示します。各物件を表す画像は、NASA の火星探査機が撮影した火星の実際の写真です。前の Codelab では、すべての物件の写真のグリッド レイアウトを含む RecyclerView を作成しました。

このバージョンのアプリでは、プロパティのタイプ(賃貸か購入か)を扱い、グリッド レイアウトにアイコンを追加して、販売中のプロパティをマークします。

アプリのオプション メニューを変更して、賃貸または販売用のプロパティのみを表示するようにグリッドをフィルタします。

最後に、個々のプロパティの詳細ビューを作成し、概要グリッドのアイコンをナビゲーションでその詳細フラグメントに接続します。

これまでは、火星の不動産データのうち、不動産画像の URL のみを使用していました。ただし、MarsProperty クラスで定義したプロパティ データには、ID、価格、タイプ(賃貸または販売)も含まれます。ウェブサービスから取得する JSON データのスニペットを次に示します。

{
   "price":8000000,
   "id":"424908",
   "type":"rent",
   "img_src": "http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631290305226E03_DXXX.jpg"
},

このタスクでは、火星の不動産物件のタイプを操作して、概要ページの販売用物件にドル記号の画像を追加します。

ステップ 1: 型を含むように MarsProperty を更新する

MarsProperty クラスは、ウェブサービスが提供する各プロパティのデータ構造を定義します。前の Codelab では、Moshi ライブラリを使用して、Mars ウェブサービスからの未加工の JSON レスポンスを個々の MarsProperty データ オブジェクトに解析しました。

このステップでは、プロパティが賃貸用かどうか(つまり、タイプが文字列 "rent""buy" か)を示すロジックを MarsProperty クラスに追加します。このロジックは複数の場所で使用するため、複製するよりもデータクラスに配置する方が適切です。

  1. 前回の Codelab で作成した MarsRealEstate アプリを開きます。(アプリをお持ちでない場合は、MarsRealEstateGrid をダウンロードできます)。
  2. network/MarsProperty.kt を開きます。MarsProperty クラス定義に本文を追加し、オブジェクトが "rent" 型の場合に true を返す isRental のカスタム ゲッターを追加します。
data class MarsProperty(
       val id: String,
       @Json(name = "img_src") val imgSrcUrl: String,
       val type: String,
       val price: Double)  {
   val isRental
       get() = type == "rent"
}

ステップ 2: グリッド アイテムのレイアウトを更新する

次に、画像のグリッドのアイテム レイアウトを更新して、販売中の物件の画像にのみドル記号のドローアブルを表示します。

データ バインディング式を使用すると、このテストをグリッド アイテムの XML レイアウトで完全に実行できます。

  1. res/layout/grid_view_item.xml を開きます。これは、RecyclerView のグリッド レイアウト内の個々のセルごとのレイアウト ファイルです。現在、ファイルにはプロパティ画像の <ImageView> 要素のみが含まれています。
  2. <data> 要素内に、View クラスの <import> 要素を追加します。インポートは、レイアウト ファイルのデータ バインディング式内でクラスのコンポーネントを使用する場合に使用します。このケースでは、View.GONE 定数と View.VISIBLE 定数を使用するため、View クラスにアクセスする必要があります。
<import type="android.view.View"/>
  1. 画像ビュー全体を FrameLayout で囲み、ドル記号のドローアブルをプロパティ画像の最上部に重ねられるようにします。
<FrameLayout
   android:layout_width="match_parent"
   android:layout_height="170dp">
             <ImageView 
                    android:id="@+id/mars_image"
            ...
</FrameLayout>
  1. ImageView で、android:layout_height 属性を match_parent に変更して、新しい親 FrameLayout 全体を埋めるようにします。
android:layout_height="match_parent"
  1. FrameLayout 内の最初の <ImageView> 要素のすぐ下に、2 つ目の <ImageView> 要素を追加します。以下の定義を使用します。この画像は、グリッド アイテムの右下隅に火星の画像の上に表示され、ドル記号アイコンの res/drawable/ic_for_sale_outline.xml で定義されたドローアブルを使用します。
<ImageView
   android:id="@+id/mars_property_type"
   android:layout_width="wrap_content"
   android:layout_height="45dp"
   android:layout_gravity="bottom|end"
   android:adjustViewBounds="true"
   android:padding="5dp"
   android:scaleType="fitCenter"
   android:src="@drawable/ic_for_sale_outline"
   tools:src="@drawable/ic_for_sale_outline"/>
  1. android:visibility 属性を mars_property_type 画像ビューに追加します。バインディング式を使用してプロパティ タイプをテストし、可視性を View.GONE(レンタルの場合)または View.VISIBLE(購入の場合)に割り当てます。
 android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"

これまで、<data> 要素で定義された個々の変数を使用するレイアウトでのみバインディング式を見てきました。バインディング式は非常に強力で、テストや数学計算などのオペレーションを XML レイアウト内で実行できます。この場合は、三項演算子(?:)を使用してテスト(このオブジェクトはレンタルですか?)を実行します。true の場合は 1 つの結果(View.GONE でドル記号アイコンを非表示にする)、false の場合は別の結果(View.VISIBLE でアイコンを表示する)を指定します。

新しい完全な grid_view_item.xml ファイルは次のとおりです。

<layout xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:app="http://schemas.android.com/apk/res-auto"
       xmlns:tools="http://schemas.android.com/tools">
   <data>
       <import type="android.view.View"/>
       <variable
           name="property"
           type="com.example.android.marsrealestate.network.MarsProperty" />
   </data>
   <FrameLayout
       android:layout_width="match_parent"
       android:layout_height="170dp">

       <ImageView
           android:id="@+id/mars_image"
           android:layout_width="match_parent"
           android:layout_height="match_parent"
           android:scaleType="centerCrop"
           android:adjustViewBounds="true"
           android:padding="2dp"
           app:imageUrl="@{property.imgSrcUrl}"
           tools:src="@tools:sample/backgrounds/scenic"/>

       <ImageView
           android:id="@+id/mars_property_type"
           android:layout_width="wrap_content"
           android:layout_height="45dp"
           android:layout_gravity="bottom|end"
           android:adjustViewBounds="true"
           android:padding="5dp"
           android:scaleType="fitCenter"
           android:src="@drawable/ic_for_sale_outline"
           android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"
           tools:src="@drawable/ic_for_sale_outline"/>
   </FrameLayout>
</layout>
  1. アプリをコンパイルして実行します。賃貸物件ではないプロパティにドル記号のアイコンが表示されることを確認します。

現在、アプリは概要グリッドに火星の不動産物件をすべて表示しています。火星で賃貸物件を探しているユーザーにとって、利用可能な物件のうち販売中の物件を示すアイコンは便利ですが、ページに表示される物件の数はまだ多く、スクロールする必要があります。このタスクでは、概要フラグメントにオプション メニューを追加して、ユーザーが賃貸物件のみ、販売物件のみ、またはすべての物件を表示できるようにします。

このタスクを達成する方法の 1 つは、概要グリッド内の各 MarsProperty の型をテストし、一致するプロパティのみを表示することです。ただし、実際の火星のウェブサービスには、rent 型または buy 型のプロパティのみを取得できるクエリ パラメータまたはオプション(filter)があります。このフィルタ クエリは、次のようにブラウザで realestate ウェブサービス URL とともに使用できます。

https://android-kotlin-fun-mars-server.appspot.com/realestate?filter=buy

このタスクでは、MarsApiService クラスを変更して、Retrofit を使用したウェブサービス リクエストにクエリ オプションを追加します。次に、そのクエリ オプションを使用して火星の不動産物件データをすべて再ダウンロードするようにオプション メニューをフックします。ウェブサービスから取得するレスポンスには、必要なプロパティのみが含まれているため、概要グリッドのビュー表示ロジックを変更する必要はありません。

ステップ 1: Mars API サービスを更新する

リクエストを変更するには、このシリーズの最初の Codelab で実装した MarsApiService クラスを再確認する必要があります。クラスを変更して、フィルタリング API を提供します。

  1. network/MarsApiService.kt を開きます。インポートのすぐ下に、MarsApiFilter という enum を作成して、ウェブサービスが想定するクエリ値と一致する定数を定義します。
enum class MarsApiFilter(val value: String) {
   SHOW_RENT("rent"),
   SHOW_BUY("buy"),
   SHOW_ALL("all") }
  1. 次の例に示すように、フィルタ クエリの文字列入力を受け取るように getProperties() メソッドを変更し、その入力を @Query("filter") でアノテーションします。

    メッセージが表示されたら、retrofit2.http.Query をインポートします。

    @Query アノテーションは、フィルタ オプションを使用してウェブサービス リクエストを行うよう getProperties() メソッド(つまり Retrofit)に指示します。getProperties() が呼び出されるたびに、リクエスト URL に ?filter=type 部分が含まれます。これにより、ウェブサービスはクエリに一致する結果を返すよう指示されます。
fun getProperties(@Query("filter") type: String):  

ステップ 2: 概要ビューモデルを更新する

OverviewViewModelgetMarsRealEstateProperties() メソッドで MarsApiService からデータをリクエストします。次に、フィルタ引数を取得するようにリクエストを更新する必要があります。

  1. overview/OverviewViewModel.kt を開きます。前のステップで行った変更により、Android Studio にエラーが表示されます。getMarsRealEstateProperties() 呼び出しにパラメータとして MarsApiFilter(可能なフィルタ値の列挙型)を追加します。

    リクエストされたら、com.example.android.marsrealestate.network.MarsApiFilter をインポートします。
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
  1. Retrofit サービスの getProperties() 呼び出しを変更して、そのフィルタ クエリを文字列として渡します。
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)
  1. init {} ブロックで、MarsApiFilter.SHOW_ALLgetMarsRealEstateProperties() の引数として渡し、アプリの初回読み込み時にすべてのプロパティを表示します。
init {
   getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
  1. クラスの末尾に、MarsApiFilter 引数を受け取り、その引数で getMarsRealEstateProperties() を呼び出す updateFilter() メソッドを追加します。
fun updateFilter(filter: MarsApiFilter) {
   getMarsRealEstateProperties(filter)
}

ステップ 3: フラグメントをオプション メニューに接続する

最後のステップとして、オーバーフロー メニューをフラグメントに接続し、ユーザーがメニュー オプションを選択したときにビューモデルで updateFilter() を呼び出します。

  1. res/menu/overflow_menu.xml を開きます。MarsRealEstate アプリには、利用可能な 3 つのオプション(すべての物件を表示、賃貸物件のみを表示、販売物件のみを表示)を提供する既存のオーバーフロー メニューがあります。
<menu xmlns:android="http://schemas.android.com/apk/res/android">
   <item
       android:id="@+id/show_all_menu"
       android:title="@string/show_all" />
   <item
       android:id="@+id/show_rent_menu"
       android:title="@string/show_rent" />
   <item
       android:id="@+id/show_buy_menu"
       android:title="@string/show_buy" />
</menu>
  1. overview/OverviewFragment.kt を開きます。クラスの最後に、メニュー項目の選択を処理する onOptionsItemSelected() メソッドを実装します。
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} 
  1. onOptionsItemSelected() で、適切なフィルタを使用してビューモデルの updateFilter() メソッドを呼び出します。Kotlin の when {} ブロックを使用して、オプションを切り替えます。デフォルトのフィルタ値には MarsApiFilter.SHOW_ALL を使用します。メニュー項目を処理したので、true を返します。リクエストされたら、MarsApiFiltercom.example.android.marsrealestate.network.MarsApiFilter)をインポートします。完成した onOptionsItemSelected() メソッドを以下に示します。
override fun onOptionsItemSelected(item: MenuItem): Boolean {
   viewModel.updateFilter(
           when (item.itemId) {
               R.id.show_rent_menu -> MarsApiFilter.SHOW_RENT
               R.id.show_buy_menu -> MarsApiFilter.SHOW_BUY
               else -> MarsApiFilter.SHOW_ALL
           }
   )
   return true
}
  1. アプリをコンパイルして実行します。アプリが最初の概要グリッドを起動し、すべてのプロパティ タイプと、販売中のプロパティがドルアイコンでマークされます。
  2. オプション メニューから [レンタル] を選択します。プロパティが再読み込みされ、ドルアイコンが表示されなくなります。(賃貸物件のみが表示されます)。フィルタされたプロパティのみが表示されるように表示が更新されるまで、しばらくお待ちください。
  3. オプション メニューから [購入] を選択します。プロパティが再度読み込まれ、すべてのプロパティにドルアイコンが表示されます。(販売中の物件のみが表示されます)。

これで、火星のプロパティのアイコンのスクロール グリッドが表示されましたが、詳細情報を取得する段階に入ります。このタスクでは、特定のプロパティの詳細を表示する詳細フラグメントを追加します。詳細フラグメントには、大きな画像、価格、物件の種類(賃貸か販売か)が表示されます。

このフラグメントは、ユーザーが概要グリッド内の画像をタップしたときに起動されます。これを実現するには、RecyclerView グリッド アイテムに onClick リスナーを追加してから、新しいフラグメントに移動する必要があります。ナビゲーションは、これらのレッスンでこれまで行ってきたように、ViewModelLiveData の変更をトリガーすることで行います。また、Navigation コンポーネントの Safe Args プラグインを使用して、選択した MarsProperty 情報を概要フラグメントから詳細フラグメントに渡します。

ステップ 1: 詳細ビューモデルを作成し、詳細レイアウトを更新する

概要ビューモデルとフラグメントで使用したプロセスと同様に、詳細フラグメントのビューモデルとレイアウト ファイルを実装する必要があります。

  1. detail/DetailViewModel.kt を開きます。ネットワーク関連の Kotlin ファイルが network フォルダに、概要ファイルが overview に含まれているのと同様に、detail フォルダには詳細ビューに関連付けられたファイルが含まれています。DetailViewModel クラス(現在は空)は、コンストラクタで marsProperty をパラメータとして受け取ります。
class DetailViewModel( marsProperty: MarsProperty,
                     app: Application) : AndroidViewModel(app) {
}
  1. クラス定義内で、選択した Mars プロパティに LiveData を追加して、その情報を詳細ビューに公開します。MarsProperty 自体を保持する MutableLiveData を作成し、不変の公開 LiveData プロパティを公開するという通常のパターンに従います。

    リクエストされたら、androidx.lifecycle.LiveData をインポートし、androidx.lifecycle.MutableLiveData をインポートします。
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
   get() = _selectedProperty
  1. init {} ブロックを作成し、コンストラクタの MarsProperty オブジェクトを使用して、選択した Mars プロパティの値を設定します。
    init {
        _selectedProperty.value = marsProperty
    }
  1. res/layout/fragment_detail.xml を開き、デザイン ビューで確認します。

    これは、詳細フラグメントのレイアウト ファイルです。大きな写真の ImageView、物件のタイプ(賃貸または販売)の TextView、価格の TextView が含まれています。制約レイアウトは ScrollView でラップされているため、ビューがディスプレイに対して大きすぎる場合(ユーザーが横向きモードで表示する場合など)は自動的にスクロールされます。
  2. レイアウトの [テキスト] タブに移動します。レイアウトの上部、<ScrollView> 要素の直前に、詳細ビューモデルをレイアウトに関連付けるための <data> 要素を追加します。
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>
  1. app:imageUrl 属性を ImageView 要素に追加します。ビューモデルの選択されたプロパティから imgSrcUrl に設定します。

    Glide を使用して画像を読み込むバインディング アダプターは、すべての app:imageUrl 属性を監視するため、ここでも自動的に使用されます。
 app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"

ステップ 2: 概要ビューモデルでナビゲーションを定義する

ユーザーが概要モデルで写真をタップすると、クリックしたアイテムの詳細を表示するフラグメントに移動する必要があります。

  1. overview/OverviewViewModel.kt を開きます。_navigateToSelectedProperty MutableLiveData プロパティを追加し、不変の LiveData で公開します。

    この LiveData が null 以外に変更されると、ナビゲーションがトリガーされます。(この変数を監視してナビゲーションをトリガーするコードは、まもなく追加します)。
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
   get() = _navigateToSelectedProperty
  1. クラスの末尾に、選択した Mars プロパティに _navigateToSelectedProperty を設定する displayPropertyDetails() メソッドを追加します。
fun displayPropertyDetails(marsProperty: MarsProperty) {
   _navigateToSelectedProperty.value = marsProperty
}
  1. _navigateToSelectedProperty の値を null にする displayPropertyDetailsComplete() メソッドを追加します。これは、ナビゲーションの状態を完了としてマークし、ユーザーが詳細ビューから戻ったときにナビゲーションが再度トリガーされないようにするために必要です。
fun displayPropertyDetailsComplete() {
   _navigateToSelectedProperty.value = null
}

ステップ 3: グリッド アダプターとフラグメントでクリック リスナーを設定する

  1. overview/PhotoGridAdapter.kt を開きます。クラスの最後に、marsProperty パラメータを含むラムダを受け取るカスタム OnClickListener クラスを作成します。クラス内で、ラムダ パラメータに設定される onClick() 関数を定義します。
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
     fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
  1. PhotoGridAdapter のクラス定義までスクロールし、プライベート OnClickListener プロパティをコンストラクタに追加します。
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
       ListAdapter<MarsProperty,              
           PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
  1. onBindviewHolder() メソッドのグリッド アイテムに onClickListener を追加して、写真をクリック可能にします。getItem() and bind() の呼び出しの間にクリック リスナーを定義します。
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
   val marsProperty = getItem(position)
   holder.itemView.setOnClickListener {
       onClickListener.onClick(marsProperty)
   }
   holder.bind(marsProperty)
}
  1. overview/OverviewFragment.kt を開きます。onCreateView() メソッドで、binding.photosGrid.adapter プロパティを初期化する行を下記の行に置き換えます。

    このコードでは、PhotoGridAdapter.onClickListener オブジェクトを PhotoGridAdapter コンストラクタに追加し、渡された MarsProperty オブジェクトを使用して viewModel.displayPropertyDetails() を呼び出します。これにより、ナビゲーションのビューモデルで LiveData がトリガーされます。
binding.photosGrid.adapter = PhotoGridAdapter(PhotoGridAdapter.OnClickListener {
   viewModel.displayPropertyDetails(it)
})

ステップ 4: ナビゲーション グラフを変更し、MarsProperty を Parcelable にする

ユーザーが概要グリッドで写真をタップすると、アプリは詳細フラグメントに移動し、選択した火星のプロパティの詳細を渡して、詳細ビューでその情報を表示できるようにする必要があります。

現在、タップを処理する PhotoGridAdapter のクリック リスナーと、ビューモデルからナビゲーションをトリガーする方法があります。しかし、詳細フラグメントに渡される MarsProperty オブジェクトはまだありません。これには、Navigation コンポーネントの Safe Args を使用します。

  1. res/navigation/nav_graph.xml を開きます。[Text] タブをクリックして、ナビゲーション グラフの XML コードを表示します。
  2. 詳細フラグメントの <fragment> 要素内に、以下に示す <argument> 要素を追加します。この引数(selectedProperty)の型は MarsProperty です。
<argument
   android:name="selectedProperty"
   app:argType="com.example.android.marsrealestate.network.MarsProperty"
   />
  1. アプリをコンパイルします。MarsPropertyparcelable ではないため、ナビゲーションでエラーが発生します。Parcelable インターフェースを使用すると、オブジェクトをシリアル化して、オブジェクトのデータをフラグメントやアクティビティ間で受け渡すことができます。この場合、MarsProperty オブジェクト内のデータを Safe Args 経由で詳細フラグメントに渡すには、MarsPropertyParcelable インターフェースを実装する必要があります。Kotlin には、そのインターフェースを簡単に実装できるショートカットが用意されています。
  2. network/MarsProperty.kt を開きます。クラス定義に @Parcelize アノテーションを追加します。

    リクエストされたら、kotlinx.android.parcel.Parcelize をインポートします。

    @Parcelize アノテーションは、Kotlin Android 拡張機能を使用して、このクラスの Parcelable インターフェースのメソッドを自動的に実装します。これ以上の操作は必要ありません。
@Parcelize
data class MarsProperty (
  1. Parcelable を拡張するように、MarsProperty のクラス定義を変更します。

    リクエストされたら、android.os.Parcelable をインポートします。

    MarsProperty クラスの定義は次のようになります。
@Parcelize
data class MarsProperty (
       val id: String,
       @Json(name = "img_src") val imgSrcUrl: String,
       val type: String,
       val price: Double) : Parcelable {

ステップ 5: フラグメントを接続する

まだナビゲーションは行われていません。実際のナビゲーションはフラグメントで行われます。このステップでは、概要フラグメントと詳細フラグメント間のナビゲーションを実装するための最後の部分を追加します。

  1. overview/OverviewFragment.kt を開きます。onCreateView() で、写真グリッド アダプターを初期化する行の下に、次の行を追加して、概要ビューモデルから navigatedToSelectedProperty を監視します。

    リクエストされたら、androidx.lifecycle.Observer をインポートし、androidx.navigation.fragment.findNavController をインポートします。

    オブザーバーは、MarsProperty(ラムダの it)が null でないかどうかをテストし、null でない場合は、findNavController() を使用してフラグメントからナビゲーション コントローラを取得します。displayPropertyDetailsComplete() を呼び出して、ビューモデルに LiveData を null 状態にリセットするよう指示します。これにより、アプリが OverviewFragment に戻ったときに、誤ってナビゲーションが再度トリガーされるのを防ぐことができます。
viewModel.navigateToSelectedProperty.observe(this, Observer {
   if ( null != it ) {   
      this.findNavController().navigate(
              OverviewFragmentDirections.actionShowDetail(it))             
      viewModel.displayPropertyDetailsComplete()
   }
})
  1. detail/DetailFragment.kt を開きます。この行を onCreateView() メソッドの setLifecycleOwner() 呼び出しの直下に追加します。この行は、Safe Args から選択された MarsProperty オブジェクトを取得します。

    Kotlin の非 null アサーション演算子(!!)が使用されていることに注意してください。selectedProperty が存在しない場合は、重大な問題が発生しており、コードで null ポインタをスローする必要があります。(本番環境のコードでは、このエラーを何らかの方法で処理する必要があります)。
 val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty
  1. 次に、この行を追加して新しい DetailViewModelFactory を取得します。DetailViewModelFactory を使用して DetailViewModel のインスタンスを取得します。スターター アプリには DetailViewModelFactory の実装が含まれているため、ここでは初期化するだけで済みます。
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
  1. 最後に、この行を追加して、工場から DetailViewModel を取得し、すべてのパーツを接続します。
      binding.viewModel = ViewModelProviders.of(
                this, viewModelFactory).get(DetailViewModel::class.java)
  1. アプリをコンパイルして実行し、火星の物件の写真のいずれかをタップします。そのプロパティの詳細のフラグメントが表示されます。[戻る] ボタンをタップして概要ページに戻ると、詳細画面がまだ少しまばらであることがわかります。次のタスクでは、その詳細ページにプロパティ データの追加を完了します。

現在、詳細ページには概要ページで表示されているのと同じ火星の写真のみが表示されています。MarsProperty クラスには、物件のタイプ(賃貸または購入)と物件の価格もあります。詳細画面にはこれらの両方の値を含める必要があります。また、賃貸物件で価格が月額であることを示すと便利です。ビューモデルで LiveData 変換を使用して、両方を実装します。

  1. res/values/strings.xml を開きます。スターター コードには、以下に示すように、詳細ビューの文字列の作成に役立つ文字列リソースが含まれています。価格には、プロパティのタイプに応じて display_price_monthly_rental リソースまたは display_price リソースを使用します。
<string name="type_rent">Rent</string>
<string name="type_sale">Sale</string>
<string name="display_type">For %s</string>
<string name="display_price_monthly_rental">$%,.0f/month</string>
<string name="display_price">$%,.0f</string>
  1. detail/DetailViewModel.kt を開きます。クラスの一番下に、次のコードを追加します。

    リクエストされた場合は、androidx.lifecycle.Transformations をインポートします。

    この変換では、最初のタスクと同じテストを使用して、選択したプロパティが賃貸物件かどうかをテストします。プロパティがレンタルである場合、変換は Kotlin when {} スイッチを使用して、リソースから適切な文字列を選択します。どちらの文字列にも最後に数字が必要なので、後で property.price を連結します。
val displayPropertyPrice = Transformations.map(selectedProperty) {
   app.applicationContext.getString(
           when (it.isRental) {
               true -> R.string.display_price_monthly_rental
               false -> R.string.display_price
           }, it.price)
}
  1. 生成された R クラスをインポートして、プロジェクト内の文字列リソースにアクセスします。
import com.example.android.marsrealestate.R
  1. displayPropertyPrice 変換の後に、次のコードを追加します。この変換では、プロパティ タイプが賃貸物件かどうかに基づいて、複数の文字列リソースが連結されます。
val displayPropertyType = Transformations.map(selectedProperty) {
   app.applicationContext.getString(R.string.display_type,
           app.applicationContext.getString(
                   when (it.isRental) {
                       true -> R.string.type_rent
                       false -> R.string.type_sale
                   }))
}
  1. res/layout/fragment_detail.xml を開きます。もう 1 つだけ行うことがあります。それは、新しい文字列(LiveData 変換で作成した文字列)を詳細ビューにバインドすることです。そのためには、プロパティ タイプ テキストのテキスト フィールドの値を viewModel.displayPropertyType に設定し、価格値テキストのテキスト フィールドの値を viewModel.displayPropertyPrice に設定します。
<TextView
   android:id="@+id/property_type_text"
...
android:text="@{viewModel.displayPropertyType}"
...
   tools:text="To Rent" />

<TextView
   android:id="@+id/price_value_text"
...
android:text="@{viewModel.displayPropertyPrice}"
...
   tools:text="$100,000" />
  1. アプリをコンパイルして実行します。これで、すべての物件データが詳細ページに適切にフォーマットされて表示されます。

Android Studio プロジェクト: MarsRealEstateFinal

バインディング式

  • XML レイアウト ファイルでバインディング式を使用して、バインドされたデータに対して、数学演算や条件テストなどの簡単なプログラム操作を実行します。
  • レイアウト ファイル内のクラスを参照するには、<data> タグ内で <import> タグを使用します。

ウェブサービス クエリ オプション

  • ウェブサービスへのリクエストには、オプションのパラメータを含めることができます。
  • リクエストでクエリ パラメータを指定するには、Retrofit@Query アノテーションを使用します。

Udacity コース:

Android デベロッパー ドキュメント:

その他:

このセクションでは、インストラクター主導のコースの一環として、この Codelab に取り組んでいる生徒向けに考えられる宿題をいくつか示します。インストラクターは、以下のようなことを行えます。

  • 必要に応じて宿題を与える
  • 宿題の提出方法を生徒に伝える
  • 宿題を採点する

インストラクターは、これらの提案を必要なだけ使用し、必要に応じて他の宿題も自由に与えることができます。

この Codelab に独力で取り組む場合は、これらの宿題を自由に使用して知識をテストしてください。

以下の質問に回答してください

問題 1

XML レイアウト ファイルの <import> タグは何をしますか?

▢ 1 つのレイアウト ファイルを別のレイアウト ファイルに含める。

▢ レイアウト ファイルに Kotlin コードを埋め込む。

▢ データバインドされたプロパティへのアクセスを提供する。

▢ バインディング式でクラスとクラスメンバーを参照できる。

問題 2

Retrofit で REST ウェブサービス呼び出しにクエリ オプションを追加するには、どうすればよいですか。

▢ リクエスト URL の末尾にクエリを追加する。

▢ リクエストを行う関数にクエリのパラメータを追加し、パラメータに @Query でアノテーションを付ける。

Query クラスを使用してリクエストを作成します。

▢ Retrofit ビルダーで addQuery() メソッドを使用する。

次のレッスンを開始する: 9.1: リポジトリ

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