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 データ オブジェクトに変換しました。

このステップでは、MarsProperty クラスにロジックを追加して、プロパティがレンタル用かどうか(つまり、型が文字列 "rent""buy" か)を示します。このロジックは複数の場所で使用するため、複製するよりもデータクラス内に用意することをおすすめします。

  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.GONEView.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. ImageViewandroid:layout_height 属性を match_parent に変更して、新しい親 FrameLayout を埋めます。
android:layout_height="match_parent"
  1. 最初の要素のすぐ下(FrameLayout 内)に、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. mars_property_type 画像ビューに android:visibility 属性を追加します。バインディング式を使用して宿泊施設のタイプをテストし、公開設定を View.GONE(レンタルの場合)または View.VISIBLE(購入の場合)に割り当てます。
 android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"

これまで、<data> 要素で定義された個々の変数を使用するレイアウトでバインディング式しか見られていませんでした。バインディング式は非常に強力で、テストや数学計算などの操作を XML レイアウト内で完全に実行できます。この場合、3 項演算子(?:)を使用してテストを実行します(このオブジェクトはレンタルですか)。その場合、true(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 のタイプをテストし、一致するプロパティのみを表示することです。ただし、実際の Mars ウェブサービスには、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 にエラーが表示されます。MarsApiFilter(使用可能なフィルタ値の列挙型)をパラメータとして getMarsRealEstateProperties() 呼び出しに追加します。

    リクエストされたら、com.example.android.marsrealestate.network.MarsApiFilter をインポートします。
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
  1. Retrofit サービス内で getProperties() の呼び出しを変更し、そのフィルタクエリを文字列として渡します。
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)
  1. init {} ブロックで、getMarsRealEstateProperties() の引数として MarsApiFilter.SHOW_ALL を渡すと、アプリが最初に読み込まれるときにすべてのプロパティが表示されます。
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. オプション メニューから [購入] を選択します。プロパティが再度読み込まれ、すべてドルアイコンが表示されます。(販売物件のみが表示されています)。

これで、火星のプロパティのアイコンがスクロール グリッドで表示されます。次は、詳細を確認しましょう。このタスクでは、詳細フラグメントを追加して、特定のプロパティの詳細を表示します。詳細フラグメントには、大きい画像、価格、物件タイプ(賃貸または販売)が表示されます。

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

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

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

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

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

    これは、詳細フラグメントのレイアウト ファイルです。これには、大きい写真の ImageView、不動産の種類(賃貸または販売)の TextView、価格の TextView が含まれます。制約レイアウトは ScrollView でラップされているため、ビューが大きすぎると(たとえば、ユーザーが横向きモードで表示した場合)自動的にスクロールされます。
  2. レイアウトの [Text] タブに移動します。レイアウト最上部の <ScrollView> 要素の直前に <data> 要素を追加して、詳細ビューモデルをレイアウトに関連付けます。
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>
  1. ImageView 要素に app:imageUrl 属性を追加します。ビューモデルの選択したプロパティから 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. クラスの最後に、選択した火星プロパティに _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 オブジェクトはまだありません。そのためには、ナビゲーション コンポーネントから 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. アプリをコンパイルします。MarsProperty Parcelable ではないため、ナビゲーションでエラーが発生します。Parcelable インターフェースを使用すると、オブジェクトをシリアル化して、フラグメント間やアクティビティ間でオブジェクト間を渡すことができます。この場合、MarsProperty オブジェクト内のデータを Safe Args を介して詳細フラグメントに渡すには、MarsPropertyParcelable インターフェースを実装する必要があります。幸いなことに、Kotlin には、そのインターフェースを実装するための簡単なショートカットが用意されています。
  2. network/MarsProperty.kt を開きます。@Parcelize アノテーションをクラス定義に追加します。

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

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

    リクエストされたら、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: フラグメントを接続する

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

  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 クラスには、宿泊施設のタイプ(賃貸または購入)と不動産価格もあります。詳細画面にはこれらの値の両方を含める必要があります。賃貸物件が 1 か月あたりの料金であると示している場合は役に立つでしょう。ビューモデルで 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> タグの役割は何ですか。

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

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

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

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

質問 2

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

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

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

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

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

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

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