この 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 クラスに追加します。このロジックは複数の場所で使用するため、複製するよりもデータクラスに配置する方が適切です。
- 前回の Codelab で作成した MarsRealEstate アプリを開きます。(アプリをお持ちでない場合は、MarsRealEstateGrid をダウンロードできます)。
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 レイアウトで完全に実行できます。
res/layout/grid_view_item.xmlを開きます。これは、RecyclerViewのグリッド レイアウト内の個々のセルごとのレイアウト ファイルです。現在、ファイルにはプロパティ画像の<ImageView>要素のみが含まれています。<data>要素内に、Viewクラスの<import>要素を追加します。インポートは、レイアウト ファイルのデータ バインディング式内でクラスのコンポーネントを使用する場合に使用します。このケースでは、View.GONE定数とView.VISIBLE定数を使用するため、Viewクラスにアクセスする必要があります。
<import type="android.view.View"/>- 画像ビュー全体を
FrameLayoutで囲み、ドル記号のドローアブルをプロパティ画像の最上部に重ねられるようにします。
<FrameLayout
android:layout_width="match_parent"
android:layout_height="170dp">
<ImageView
android:id="@+id/mars_image"
...
</FrameLayout>ImageViewで、android:layout_height属性をmatch_parentに変更して、新しい親FrameLayout全体を埋めるようにします。
android:layout_height="match_parent"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"/>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 つは、概要グリッド内の各 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 を提供します。
network/MarsApiService.ktを開きます。インポートのすぐ下に、MarsApiFilterというenumを作成して、ウェブサービスが想定するクエリ値と一致する定数を定義します。
enum class MarsApiFilter(val value: String) {
SHOW_RENT("rent"),
SHOW_BUY("buy"),
SHOW_ALL("all") }- 次の例に示すように、フィルタ クエリの文字列入力を受け取るように
getProperties()メソッドを変更し、その入力を@Query("filter")でアノテーションします。
メッセージが表示されたら、retrofit2.http.Queryをインポートします。@Queryアノテーションは、フィルタ オプションを使用してウェブサービス リクエストを行うようgetProperties()メソッド(つまり Retrofit)に指示します。getProperties()が呼び出されるたびに、リクエスト URL に?filter=type部分が含まれます。これにより、ウェブサービスはクエリに一致する結果を返すよう指示されます。
fun getProperties(@Query("filter") type: String): ステップ 2: 概要ビューモデルを更新する
OverviewViewModel の getMarsRealEstateProperties() メソッドで MarsApiService からデータをリクエストします。次に、フィルタ引数を取得するようにリクエストを更新する必要があります。
overview/OverviewViewModel.ktを開きます。前のステップで行った変更により、Android Studio にエラーが表示されます。getMarsRealEstateProperties()呼び出しにパラメータとしてMarsApiFilter(可能なフィルタ値の列挙型)を追加します。
リクエストされたら、com.example.android.marsrealestate.network.MarsApiFilterをインポートします。
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {- Retrofit サービスの
getProperties()呼び出しを変更して、そのフィルタ クエリを文字列として渡します。
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)init {}ブロックで、MarsApiFilter.SHOW_ALLをgetMarsRealEstateProperties()の引数として渡し、アプリの初回読み込み時にすべてのプロパティを表示します。
init {
getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}- クラスの末尾に、
MarsApiFilter引数を受け取り、その引数でgetMarsRealEstateProperties()を呼び出すupdateFilter()メソッドを追加します。
fun updateFilter(filter: MarsApiFilter) {
getMarsRealEstateProperties(filter)
}ステップ 3: フラグメントをオプション メニューに接続する
最後のステップとして、オーバーフロー メニューをフラグメントに接続し、ユーザーがメニュー オプションを選択したときにビューモデルで updateFilter() を呼び出します。
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>overview/OverviewFragment.ktを開きます。クラスの最後に、メニュー項目の選択を処理するonOptionsItemSelected()メソッドを実装します。
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} onOptionsItemSelected()で、適切なフィルタを使用してビューモデルのupdateFilter()メソッドを呼び出します。Kotlin のwhen {}ブロックを使用して、オプションを切り替えます。デフォルトのフィルタ値にはMarsApiFilter.SHOW_ALLを使用します。メニュー項目を処理したので、trueを返します。リクエストされたら、MarsApiFilter(com.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
}- アプリをコンパイルして実行します。アプリが最初の概要グリッドを起動し、すべてのプロパティ タイプと、販売中のプロパティがドルアイコンでマークされます。
- オプション メニューから [レンタル] を選択します。プロパティが再読み込みされ、ドルアイコンが表示されなくなります。(賃貸物件のみが表示されます)。フィルタされたプロパティのみが表示されるように表示が更新されるまで、しばらくお待ちください。
- オプション メニューから [購入] を選択します。プロパティが再度読み込まれ、すべてのプロパティにドルアイコンが表示されます。(販売中の物件のみが表示されます)。
これで、火星のプロパティのアイコンのスクロール グリッドが表示されましたが、詳細情報を取得する段階に入ります。このタスクでは、特定のプロパティの詳細を表示する詳細フラグメントを追加します。詳細フラグメントには、大きな画像、価格、物件の種類(賃貸か販売か)が表示されます。

このフラグメントは、ユーザーが概要グリッド内の画像をタップしたときに起動されます。これを実現するには、RecyclerView グリッド アイテムに onClick リスナーを追加してから、新しいフラグメントに移動する必要があります。ナビゲーションは、これらのレッスンでこれまで行ってきたように、ViewModel で LiveData の変更をトリガーすることで行います。また、Navigation コンポーネントの Safe Args プラグインを使用して、選択した MarsProperty 情報を概要フラグメントから詳細フラグメントに渡します。
ステップ 1: 詳細ビューモデルを作成し、詳細レイアウトを更新する
概要ビューモデルとフラグメントで使用したプロセスと同様に、詳細フラグメントのビューモデルとレイアウト ファイルを実装する必要があります。
detail/DetailViewModel.ktを開きます。ネットワーク関連の Kotlin ファイルがnetworkフォルダに、概要ファイルがoverviewに含まれているのと同様に、detailフォルダには詳細ビューに関連付けられたファイルが含まれています。DetailViewModelクラス(現在は空)は、コンストラクタでmarsPropertyをパラメータとして受け取ります。
class DetailViewModel( marsProperty: MarsProperty,
app: Application) : AndroidViewModel(app) {
}- クラス定義内で、選択した Mars プロパティに
LiveDataを追加して、その情報を詳細ビューに公開します。MarsProperty自体を保持するMutableLiveDataを作成し、不変の公開LiveDataプロパティを公開するという通常のパターンに従います。
リクエストされたら、androidx.lifecycle.LiveDataをインポートし、androidx.lifecycle.MutableLiveDataをインポートします。
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
get() = _selectedPropertyinit {}ブロックを作成し、コンストラクタのMarsPropertyオブジェクトを使用して、選択した Mars プロパティの値を設定します。
init {
_selectedProperty.value = marsProperty
}res/layout/fragment_detail.xmlを開き、デザイン ビューで確認します。
これは、詳細フラグメントのレイアウト ファイルです。大きな写真のImageView、物件のタイプ(賃貸または販売)のTextView、価格のTextViewが含まれています。制約レイアウトはScrollViewでラップされているため、ビューがディスプレイに対して大きすぎる場合(ユーザーが横向きモードで表示する場合など)は自動的にスクロールされます。- レイアウトの [テキスト] タブに移動します。レイアウトの上部、
<ScrollView>要素の直前に、詳細ビューモデルをレイアウトに関連付けるための<data>要素を追加します。
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>app:imageUrl属性をImageView要素に追加します。ビューモデルの選択されたプロパティからimgSrcUrlに設定します。
Glide を使用して画像を読み込むバインディング アダプターは、すべてのapp:imageUrl属性を監視するため、ここでも自動的に使用されます。
app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"ステップ 2: 概要ビューモデルでナビゲーションを定義する
ユーザーが概要モデルで写真をタップすると、クリックしたアイテムの詳細を表示するフラグメントに移動する必要があります。
overview/OverviewViewModel.ktを開きます。_navigateToSelectedPropertyMutableLiveDataプロパティを追加し、不変のLiveDataで公開します。
このLiveDataが null 以外に変更されると、ナビゲーションがトリガーされます。(この変数を監視してナビゲーションをトリガーするコードは、まもなく追加します)。
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
get() = _navigateToSelectedProperty- クラスの末尾に、選択した Mars プロパティに _
navigateToSelectedPropertyを設定するdisplayPropertyDetails()メソッドを追加します。
fun displayPropertyDetails(marsProperty: MarsProperty) {
_navigateToSelectedProperty.value = marsProperty
}_navigateToSelectedPropertyの値を null にするdisplayPropertyDetailsComplete()メソッドを追加します。これは、ナビゲーションの状態を完了としてマークし、ユーザーが詳細ビューから戻ったときにナビゲーションが再度トリガーされないようにするために必要です。
fun displayPropertyDetailsComplete() {
_navigateToSelectedProperty.value = null
}ステップ 3: グリッド アダプターとフラグメントでクリック リスナーを設定する
overview/PhotoGridAdapter.ktを開きます。クラスの最後に、marsPropertyパラメータを含むラムダを受け取るカスタムOnClickListenerクラスを作成します。クラス内で、ラムダ パラメータに設定されるonClick()関数を定義します。
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}PhotoGridAdapterのクラス定義までスクロールし、プライベートOnClickListenerプロパティをコンストラクタに追加します。
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {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)
}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 を使用します。
res/navigation/nav_graph.xmlを開きます。[Text] タブをクリックして、ナビゲーション グラフの XML コードを表示します。- 詳細フラグメントの
<fragment>要素内に、以下に示す<argument>要素を追加します。この引数(selectedProperty)の型はMarsPropertyです。
<argument
android:name="selectedProperty"
app:argType="com.example.android.marsrealestate.network.MarsProperty"
/>- アプリをコンパイルします。
MarsPropertyが parcelable ではないため、ナビゲーションでエラーが発生します。Parcelableインターフェースを使用すると、オブジェクトをシリアル化して、オブジェクトのデータをフラグメントやアクティビティ間で受け渡すことができます。この場合、MarsPropertyオブジェクト内のデータを Safe Args 経由で詳細フラグメントに渡すには、MarsPropertyがParcelableインターフェースを実装する必要があります。Kotlin には、そのインターフェースを簡単に実装できるショートカットが用意されています。 network/MarsProperty.ktを開きます。クラス定義に@Parcelizeアノテーションを追加します。
リクエストされたら、kotlinx.android.parcel.Parcelizeをインポートします。@Parcelizeアノテーションは、Kotlin Android 拡張機能を使用して、このクラスのParcelableインターフェースのメソッドを自動的に実装します。これ以上の操作は必要ありません。
@Parcelize
data class MarsProperty (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: フラグメントを接続する
まだナビゲーションは行われていません。実際のナビゲーションはフラグメントで行われます。このステップでは、概要フラグメントと詳細フラグメント間のナビゲーションを実装するための最後の部分を追加します。
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()
}
})detail/DetailFragment.ktを開きます。この行をonCreateView()メソッドのsetLifecycleOwner()呼び出しの直下に追加します。この行は、Safe Args から選択されたMarsPropertyオブジェクトを取得します。
Kotlin の非 null アサーション演算子(!!)が使用されていることに注意してください。selectedPropertyが存在しない場合は、重大な問題が発生しており、コードで null ポインタをスローする必要があります。(本番環境のコードでは、このエラーを何らかの方法で処理する必要があります)。
val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty- 次に、この行を追加して新しい
DetailViewModelFactoryを取得します。DetailViewModelFactoryを使用してDetailViewModelのインスタンスを取得します。スターター アプリにはDetailViewModelFactoryの実装が含まれているため、ここでは初期化するだけで済みます。
val viewModelFactory = DetailViewModelFactory(marsProperty, application)- 最後に、この行を追加して、工場から
DetailViewModelを取得し、すべてのパーツを接続します。
binding.viewModel = ViewModelProviders.of(
this, viewModelFactory).get(DetailViewModel::class.java)- アプリをコンパイルして実行し、火星の物件の写真のいずれかをタップします。そのプロパティの詳細のフラグメントが表示されます。[戻る] ボタンをタップして概要ページに戻ると、詳細画面がまだ少しまばらであることがわかります。次のタスクでは、その詳細ページにプロパティ データの追加を完了します。
現在、詳細ページには概要ページで表示されているのと同じ火星の写真のみが表示されています。MarsProperty クラスには、物件のタイプ(賃貸または購入)と物件の価格もあります。詳細画面にはこれらの両方の値を含める必要があります。また、賃貸物件で価格が月額であることを示すと便利です。ビューモデルで LiveData 変換を使用して、両方を実装します。
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>detail/DetailViewModel.ktを開きます。クラスの一番下に、次のコードを追加します。
リクエストされた場合は、androidx.lifecycle.Transformationsをインポートします。
この変換では、最初のタスクと同じテストを使用して、選択したプロパティが賃貸物件かどうかをテストします。プロパティがレンタルである場合、変換は Kotlinwhen {}スイッチを使用して、リソースから適切な文字列を選択します。どちらの文字列にも最後に数字が必要なので、後で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)
}- 生成された
Rクラスをインポートして、プロジェクト内の文字列リソースにアクセスします。
import com.example.android.marsrealestate.RdisplayPropertyPrice変換の後に、次のコードを追加します。この変換では、プロパティ タイプが賃貸物件かどうかに基づいて、複数の文字列リソースが連結されます。
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
}))
}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" />- アプリをコンパイルして実行します。これで、すべての物件データが詳細ページに適切にフォーマットされて表示されます。

Android Studio プロジェクト: MarsRealEstateFinal
バインディング式
- XML レイアウト ファイルでバインディング式を使用して、バインドされたデータに対して、数学演算や条件テストなどの簡単なプログラム操作を実行します。
- レイアウト ファイル内のクラスを参照するには、
<data>タグ内で<import>タグを使用します。
ウェブサービス クエリ オプション
- ウェブサービスへのリクエストには、オプションのパラメータを含めることができます。
- リクエストでクエリ パラメータを指定するには、Retrofit の
@Queryアノテーションを使用します。
Udacity コース:
Android デベロッパー ドキュメント:
- ViewModel の概要
- LiveData の概要
- バインディング アダプター
- レイアウトとバインディング式
- ナビゲーション
- Navigation コンポーネント スタートガイド
- デスティネーション間でデータを渡す(Safe Args についても説明しています)
TransformationsクラスViewModelProviderクラスViewModelProvider.Factoryクラス
その他:
- Glide
- Retrofit Query クラス
- Kotlin の Parcelable 生成ツール拡張機能
このセクションでは、インストラクター主導のコースの一環として、この Codelab に取り組んでいる生徒向けに考えられる宿題をいくつか示します。インストラクターは、以下のようなことを行えます。
- 必要に応じて宿題を与える
- 宿題の提出方法を生徒に伝える
- 宿題を採点する
インストラクターは、これらの提案を必要なだけ使用し、必要に応じて他の宿題も自由に与えることができます。
この Codelab に独力で取り組む場合は、これらの宿題を自由に使用して知識をテストしてください。
以下の質問に回答してください
問題 1
XML レイアウト ファイルの <import> タグは何をしますか?
▢ 1 つのレイアウト ファイルを別のレイアウト ファイルに含める。
▢ レイアウト ファイルに Kotlin コードを埋め込む。
▢ データバインドされたプロパティへのアクセスを提供する。
▢ バインディング式でクラスとクラスメンバーを参照できる。
問題 2
Retrofit で REST ウェブサービス呼び出しにクエリ オプションを追加するには、どうすればよいですか。
▢ リクエスト URL の末尾にクエリを追加する。
▢ リクエストを行う関数にクエリのパラメータを追加し、パラメータに @Query でアノテーションを付ける。
▢ Query クラスを使用してリクエストを作成します。
▢ Retrofit ビルダーで addQuery() メソッドを使用する。
次のレッスンを開始する:
このコースの他の Codelab へのリンクについては、Android Kotlin の基礎の Codelab のランディング ページをご覧ください。