この 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"
か)を示します。このロジックは複数の場所で使用するため、複製するよりもデータクラス内に用意することをおすすめします。
- 前回の 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
内)に、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"/>
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 つの方法は、サマリー グリッドで各 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 を提供するようにクラスを変更します。
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 にエラーが表示されます。MarsApiFilter
(使用可能なフィルタ値の列挙型)をパラメータとしてgetMarsRealEstateProperties()
呼び出しに追加します。
リクエストされたら、com.example.android.marsrealestate.network.MarsApiFilter
をインポートします。
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
- Retrofit サービス内で
getProperties()
の呼び出しを変更し、そのフィルタクエリを文字列として渡します。
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)
init {}
ブロックで、getMarsRealEstateProperties()
の引数としてMarsApiFilter.SHOW_ALL
を渡すと、アプリが最初に読み込まれるときにすべてのプロパティが表示されます。
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
}
- アプリをコンパイルして実行します。アプリは、すべてのプロパティ タイプと、ドルのアイコンが付いた販売物件のプロパティを含む、最初の概要グリッドを起動します。
- オプション メニューから [レンタル] を選択します。プロパティが再読み込みされ、ドルアイコンと一緒に表示されません。(賃貸物件のみが表示されます)。フィルタされたプロパティのみが表示されない場合は、しばらく待ってからディスプレイを更新してください。
- オプション メニューから [購入] を選択します。プロパティが再度読み込まれ、すべてドルアイコンが表示されます。(販売物件のみが表示されています)。
これで、火星のプロパティのアイコンがスクロール グリッドで表示されます。次は、詳細を確認しましょう。このタスクでは、詳細フラグメントを追加して、特定のプロパティの詳細を表示します。詳細フラグメントには、大きい画像、価格、物件タイプ(賃貸または販売)が表示されます。
このフラグメントは、ユーザーが概要グリッド内の画像をタップすると起動します。これを行うには、onClick
リスナーを RecyclerView
グリッド アイテムに追加してから、新しいフラグメントに移動する必要があります。これらのレッスン全体を通して行ったように、ViewModel
で LiveData
の変更をトリガーして操作します。また、Navigation コンポーネントの Safe Args プラグインを使用して、選択した MarsProperty
情報をサマリー フラグメントから詳細フラグメントに渡します。
ステップ 1: 詳細ビューモデルを作成し、詳細レイアウトを更新する
概要ビューモデルとフラグメントで使用したプロセスと同様に、詳細フラグメントのビューモデルとレイアウト ファイルを実装する必要があります。
detail/DetailViewModel.kt
を開きます。ネットワーク関連の Kotlin ファイルがoverview
のnetwork
フォルダと概要ファイルに含まれているのと同様に、detail
フォルダには、詳細ビューに関連付けられたファイルが含まれます。DetailViewModel
クラス(現時点では空)は、コンストラクタのパラメータとしてmarsProperty
を受け取ります。
class DetailViewModel( marsProperty: MarsProperty,
app: Application) : AndroidViewModel(app) {
}
- クラス定義内で、選択した火星プロパティに
LiveData
を追加し、その情報を詳細ビューに公開します。MutableLiveData
を作成する通常のパターンに従ってMarsProperty
自体を保持し、不変の公開LiveData
プロパティを公開します。
リクエストされた場合は、androidx.lifecycle.LiveData
をインポートし、androidx.lifecycle.MutableLiveData
をインポートします。
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
get() = _selectedProperty
init {}
ブロックを作成し、コンストラクタにあるMarsProperty
オブジェクトを使用して、選択した火星プロパティの値を設定します。
init {
_selectedProperty.value = marsProperty
}
res/layout/fragment_detail.xml
を開き、デザインビューでそれを確認します。
これは、詳細フラグメントのレイアウト ファイルです。これには、大きい写真のImageView
、不動産の種類(賃貸または販売)のTextView
、価格のTextView
が含まれます。制約レイアウトはScrollView
でラップされているため、ビューが大きすぎると(たとえば、ユーザーが横向きモードで表示した場合)自動的にスクロールされます。- レイアウトの [Text] タブに移動します。レイアウト最上部の
<ScrollView>
要素の直前に<data>
要素を追加して、詳細ビューモデルをレイアウトに関連付けます。
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>
ImageView
要素にapp:imageUrl
属性を追加します。ビューモデルの選択したプロパティからimgSrcUrl
に設定します。
Glide を使用して画像を読み込むバインディング アダプターも、app:imageUrl
属性をすべて監視するため、自動的に使用されます。
app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"
ステップ 2: 概要ビューのモデルでナビゲーションを定義する
ユーザーが概要モデルの写真をタップすると、クリックされたアイテムの詳細を表示するフラグメントへのナビゲーションがトリガーされます。
overview/OverviewViewModel.kt
を開きます。_navigateToSelectedProperty
MutableLiveData
プロパティを追加し、不変のLiveData
で公開します。
このLiveData
が null 以外に変更すると、ナビゲーションがトリガーされます。(すぐにこの変数を監視し、ナビゲーションをトリガーするコードが追加されます)。
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
get() = _navigateToSelectedProperty
- クラスの最後に、選択した火星プロパティに _
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
オブジェクトはまだありません。そのためには、ナビゲーション コンポーネントから 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 (
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 フラグメント間のナビゲーションを実装するための最後のビットを追加します。
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
クラスには、宿泊施設のタイプ(賃貸または購入)と不動産価格もあります。詳細画面にはこれらの値の両方を含める必要があります。賃貸物件が 1 か月あたりの料金であると示している場合は役に立つでしょう。ビューモデルで 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.R
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
}))
}
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
クラス
その他:
- グライド
- Retrofit クエリクラス
- Kotlin の Parcelable Generator 拡張機能
このセクションでは、インストラクターが主導するコースの一環として、この Codelab に取り組む生徒の課題について説明します。教師は以下のことを行えます。
- 必要に応じて課題を割り当てます。
- 宿題の提出方法を生徒に伝える。
- 宿題を採点します。
教師はこれらの提案を少しだけ使うことができます。また、他の課題は自由に割り当ててください。
この Codelab にご自分で取り組む場合は、これらの課題を使用して知識をテストしてください。
次の質問に答えてください
問題 1
XML レイアウト ファイルの <import>
タグの役割は何ですか。
▢ レイアウト ファイルを別のレイアウト ファイルに含める
▢ レイアウト ファイル内に Kotlin コードを埋め込む
▢ データバインドされたプロパティへのアクセスを提供する。
▢ バインディング式でクラスとクラスメンバーを参照できるようにする。
質問 2
Retrofit で REST ウェブサービス呼び出しにクエリ オプションを追加するには、どうすればよいですか。
▢ リクエスト URL の末尾にクエリを追加する
▢ リクエストを行う関数にクエリのパラメータを追加し、そのパラメータに @Query
アノテーションを付ける。
▢ Query
クラスを使用してリクエストを作成する
▢ Retrofit ビルダーで addQuery()
メソッドを使用します。
次のレッスンを開始する:
このコースの他の Codelab へのリンクについては、Android Kotlin の基礎 Codelab ランディング ページをご覧ください。