この Codelab は、Android Kotlin の基礎コースの一部です。このコースを最大限に活用するには、Codelab を順番に進めることをおすすめします。コースのすべての Codelab は、Android Kotlin の基礎の Codelab のランディング ページに一覧表示されています。
はじめに
前回の Codelab では、ウェブサービスからデータを取得し、レスポンスを解析してデータ オブジェクトに変換する方法を学習しました。この Codelab では、その知識に基づいて、ウェブ URL から写真を読み込んで表示します。また、RecyclerView
を作成して概要ページに画像のグリッドを表示する方法もおさらいします。
前提となる知識
- フラグメントの作成方法と使用方法。
- ビューモデル、ビューモデル ファクトリ、変換、
LiveData
などのアーキテクチャ コンポーネントの使用方法。 - REST ウェブサービスから JSON を取得する方法と、Retrofit ライブラリおよび Moshi ライブラリを使用してそのデータを解析し、Kotlin オブジェクトに変換する方法。
RecyclerView
を使用してグリッド レイアウトを作成する方法。Adapter
、ViewHolder
、DiffUtil
の機能。
学習内容
- Glide ライブラリを使用してウェブ URL から画像を読み込んで表示する方法。
RecyclerView
とグリッド アダプターを使用して画像のグリッドを表示する方法。- 画像をダウンロードして表示する際に発生するエラーの処理方法。
演習内容
- MarsRealEstate アプリを変更して、火星の不動産物件データから画像 URL を取得し、Glide を使用してその画像を読み込んで表示します。
- 読み込み中のアニメーションとエラーアイコンをアプリに追加します。
RecyclerView
を使用して火星の物件画像のグリッドを表示します。- ステータス処理とエラー処理を
RecyclerView
に追加します。
この Codelab(および関連する Codelab)では、火星で販売されている不動産物件を表示する MarsRealEstate というアプリを使用します。アプリはインターネット サーバーに接続して、価格や販売または賃貸が可能かどうかなどの詳細を含む不動産データを取得して表示します。各物件を表す画像は、NASA の火星探査機が撮影した火星の実際の写真です。
この Codelab で作成するバージョンのアプリは概要ページにデータを書き込みます。概要ページでは画像のグリッドを表示します。これらの画像は、アプリが Mars 不動産ウェブサービスから取得した物件データの一部です。アプリは Glide ライブラリを使用して画像の読み込みと表示を行い、RecyclerView
を使用して画像のグリッド レイアウトを作成します。また、アプリはネットワーク エラーを適切に処理します。
ウェブ URL からの写真を表示するのは簡単に思えますが、適切に処理するにはかなりの作業が必要です。画像をダウンロードしてバッファリングし、圧縮形式から Android が使用できる形式にデコードする必要があります。メモリ内キャッシュとストレージベースのキャッシュの両方またはいずれかに画像を保存する必要があります。これらすべてを優先度の低いバックグラウンド スレッドで行いつつ、UI の応答性を維持する必要があります。また、ネットワークと CPU のパフォーマンスを最適化するため、複数の画像を一度に取得してデコードする必要もあります。ネットワークから画像を効果的に読み込む方法を学ぶことは、それ自体が Codelab になり得ます。
幸いなことに、コミュニティで開発された Glide というライブラリを使用して、画像のダウンロード、バッファリング、デコード、キャッシュ保存を行うことができます。Glide を使用すると、すべてをゼロから行うよりもはるかに手間が省けます。
Glide は、基本的に次の 2 つのものを必要とします。
- 読み込んで表示する画像の URL。
- 画像を表示するための
ImageView
オブジェクト。
このタスクでは、Glide を使用して、不動産ウェブサービスから取得した単一の画像を表示する方法を学びます。ウェブサービスから返された不動産物件のリストに含まれる最初の火星の不動産物件を表す画像を表示します。画像の表示前と表示後のスクリーンショットを次に示します。
ステップ 1: Glide の依存関係を追加する
- 前回の Codelab で作成した MarsRealEstate アプリを開きます。(アプリをお持ちでない場合は、MarsRealEstateNetwork をこちらからダウンロードできます)。
- アプリを実行して動作を確認します(火星で利用可能な仮想の物件のテキストの詳細が表示されます)。
- build.gradle(モジュール: app)を開きます。
dependencies
セクションで、Glide ライブラリ用に次の行を追加します。
implementation "com.github.bumptech.glide:glide:$version_glide"
バージョン番号は、プロジェクトの Gradle ファイルで個別に定義されています。
- [Sync Now] をクリックし、新しい依存関係でプロジェクトを再ビルドします。
ステップ 2: ビューモデルを更新する
次に、OverviewViewModel
クラスを更新して、1 つの Mars プロパティのライブデータを含めます。
overview/OverviewViewModel.kt
を開きます。_response
のLiveData
のすぐ下に、単一のMarsProperty
オブジェクトの内部(可変)ライブデータと外部(不変)ライブデータの両方を追加します。
リクエストされたら、MarsProperty
クラス(com.example.android.marsrealestate.network.MarsProperty
)をインポートします。
private val _property = MutableLiveData<MarsProperty>()
val property: LiveData<MarsProperty>
get() = _property
getMarsRealEstateProperties()
メソッドのtry/catch {}
ブロック内で、_response.value
をプロパティの数に設定している行を見つけます。以下のテストを追加します。MarsProperty
オブジェクトが利用可能な場合、このテストでは_property
LiveData
の値をlistResult
の最初のプロパティに設定します。
if (listResult.size > 0) {
_property.value = listResult[0]
}
完全な try/catch {}
ブロックは次のようになります。
try {
var listResult = getPropertiesDeferred.await()
_response.value = "Success: ${listResult.size} Mars properties retrieved"
if (listResult.size > 0) {
_property.value = listResult[0]
}
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}
res/layout/fragment_overview.xml
ファイルを開きます。<TextView>
要素で、android:text
を変更してproperty
LiveData
のimgSrcUrl
コンポーネントにバインドします。
android:text="@{viewModel.property.imgSrcUrl}"
- アプリを実行します。
TextView
には、最初の火星プロパティの画像の URL のみが表示されます。ここまでの作業では、その URL 用にビューモデルとライブデータを設定しただけです。
ステップ 3: バインディング アダプターを作成して Glide を呼び出す
表示する画像の URL が取得できたので、Glide を使用してその画像を読み込む作業を開始します。このステップでは、バインディング アダプターを使用して、ImageView
に関連付けられた XML 属性から URL を取得し、Glide を使用して画像を読み込みます。バインディング アダプターは、ビューとバインドされたデータをつなげる拡張メソッドで、データが変更されたときのカスタム動作を提供します。この場合、カスタム動作は Glide を呼び出して URL から ImageView
に画像を読み込むことです。
BindingAdapters.kt
を開きます。このファイルは、アプリ全体で使用するバインディング アダプターを保持します。- パラメータとして
ImageView
とString
を受け取るbindImage()
関数を作成します。関数に@BindingAdapter
アノテーションを付けます。@BindingAdapter
アノテーションは、XML アイテムにimageUrl
属性がある場合にこのバインディング アダプターを実行するようデータ バインディングに指示します。
リクエストされたら、androidx.databinding.BindingAdapter
とandroid.widget.ImageView
をインポートします。
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
}
bindImage()
関数内で、imgUrl
引数にlet {}
ブロックを追加します。
imgUrl?.let {
}
let {}
ブロック内に次の行を追加し、URL 文字列(XML から)をUri
オブジェクトに変換します。リクエストされたらandroidx.core.net.toUri
をインポートします。
最終的なUri
オブジェクトで HTTPS スキームを使用します。これは、画像の取得元サーバーでこのスキームが必要になるためです。HTTPS スキームを使用するには、buildUpon.scheme("https")
をtoUri
ビルダーの末尾に追加します。toUri()
メソッドは Android KTX Core ライブラリの Kotlin 拡張関数であるため、String
クラスの一部のように見えます。
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
let {}
内で、Glide.with()
を呼び出して、Uri
オブジェクトからImageView
に画像を読み込みます。リクエストされたら、com.bumptech.glide.Glide
をインポートします。
Glide.with(imgView.context)
.load(imgUri)
.into(imgView)
ステップ 4: レイアウトとフラグメントを更新する
Glide は画像を読み込みましたが、まだ何も表示されていません。次のステップでは、レイアウトとフラグメントを ImageView
で更新して画像を表示します。
res/layout/gridview_item.xml
を開きます。これは、この Codelab の後半でRecyclerView
の各アイテムに使用するレイアウト リソース ファイルです。ここでは、単一の画像のみを表示するために一時的に使用します。<ImageView>
要素の上にデータ バインディング用の<data>
要素を追加して、OverviewViewModel
クラスにバインドします。
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>
- 新しい画像読み込みバインディング アダプターを使用するため、
ImageView
要素にapp:imageUrl
属性を追加します。
app:imageUrl="@{viewModel.property.imgSrcUrl}"
overview/OverviewFragment.kt
を開きます。onCreateView()
メソッドで、FragmentOverviewBinding
クラスをインフレートする行をコメントアウトし、バインディング変数に割り当てます。これは一時的なものであり、後で戻ります。
//val binding = FragmentOverviewBinding.inflate(inflater)
- 次の行を追加して、
GridViewItemBinding
クラスをインフレートします。リクエストされたら、com.example.android.marsrealestate. databinding.GridViewItemBinding
をインポートします。
val binding = GridViewItemBinding.inflate(inflater)
- アプリを実行します。結果リストの最初の
MarsProperty
の画像の写真が表示されます。
ステップ 5: 読み込み中とエラーのシンプルな画像を追加する
Glide を使用すると、画像の読み込み中のプレースホルダ画像と、読み込みが失敗した場合(画像がない、画像が破損しているなど)のエラー画像を表示して、ユーザー エクスペリエンスを改善できます。このステップでは、バインディング アダプターとレイアウトにその機能を追加します。
res/drawable/ic_broken_image.xml
を開いて、右側の [Preview] タブをクリックします。エラー画像としては、組み込みのアイコン ライブラリにある破損した画像のアイコンを使用します。このベクター型ドローアブルでは、android:tint
属性によりアイコンがグレーに色付けされています。
res/drawable/loading_animation.xml
を開きます。このドローアブルは、<animate-rotate>
タグで定義されたアニメーションです。アニメーションでは、中心点の周りで画像ドローアブル(loading_img.xml
)が回転します。(アニメーションはプレビューでは確認できません)。
BindingAdapters.kt
ファイルに戻ります。bindImage()
メソッドで、Glide.with()
の呼び出しを更新して、load()
とinto()
の間でapply()
関数を呼び出すようにします。リクエストされたらcom.bumptech.glide.request.RequestOptions
をインポートします。
このコードは、読み込み時に使用する読み込み中のプレースホルダ画像(loading_animation
ドローアブル)を設定します。また、画像の読み込みが失敗した場合に使用する画像(broken_image
ドローアブル)も設定します。完全なbindImage()
メソッドは次のようになります。
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
imgUrl?.let {
val imgUri =
imgUrl.toUri().buildUpon().scheme("https").build()
Glide.with(imgView.context)
.load(imgUri)
.apply(RequestOptions()
.placeholder(R.drawable.loading_animation)
.error(R.drawable.ic_broken_image))
.into(imgView)
}
}
- アプリを実行します。ネットワーク接続の速度によっては、Glide がプロパティ画像をダウンロードして表示する間、一瞬だけ読み込み中の画像が表示されます。しかし、ネットワークを切断しても、破損した画像のアイコンは表示されません。この問題は、Codelab の最後の部分で修正します。
以上で、アプリはインターネットから物件情報を読み込むようになりました。最初の MarsProperty
リストアイテムからのデータを使用して、ビューモデルに LiveData
プロパティを作成し、そのプロパティ データからの画像 URL を使用して ImageView
にデータを入力しました。しかし、アプリの目標は画像のグリッドを表示することです。そこで、GridLayoutManager
で RecyclerView
を使用します。
ステップ 1: ビューモデルを更新する
現在、ビューモデルには _property
LiveData
があり、ウェブサービスからのレスポンス リストに含まれる最初のオブジェクトである MarsProperty
オブジェクトを保持しています。このステップでは、この LiveData
を変更して、MarsProperty
オブジェクトのリスト全体を保持するようにします。
overview/OverviewViewModel.kt
を開きます。- 非公開の
_property
変数を_properties
に変更します。型をMarsProperty
オブジェクトのリストに変更します。
private val _properties = MutableLiveData<List<MarsProperty>>()
- 外部の
property
ライブデータをproperties
に置き換えます。ここでも、リストをLiveData
型に追加します。
val properties: LiveData<List<MarsProperty>>
get() = _properties
- 下にスクロールして
getMarsRealEstateProperties()
メソッドを表示します。try {}
ブロック内で、前のタスクで追加したテスト全体を次の行に置き換えます。listResult
変数にはMarsProperty
オブジェクトのリストが保持されているため、レスポンスが成功したかどうかをテストする代わりに、_properties.value
に割り当てることができます。
_properties.value = listResult
try/catch
ブロック全体は次のようになります。
try {
var listResult = getPropertiesDeferred.await()
_response.value = "Success: ${listResult.size} Mars properties retrieved"
_properties.value = listResult
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}
ステップ 2: レイアウトとフラグメントを更新する
次のステップでは、アプリのレイアウトとフラグメントを変更して、単一の画像ビューを使用する代わりに、RecyclerView とグリッド レイアウトを使用するようにします。
res/layout/gridview_item.xml
を開きます。データ バインディングをOverviewViewModel
からMarsProperty
に変更し、変数の名前を"property"
に変更します。
<variable
name="property"
type="com.example.android.marsrealestate.network.MarsProperty" />
<ImageView>
内でapp:imageUrl
属性を変更して、MarsProperty
オブジェクトに含まれる画像 URL を参照するようにします。
app:imageUrl="@{property.imgSrcUrl}"
overview/OverviewFragment.kt
を開きます。onCreateview()
で、FragmentOverviewBinding
をインフレートする行のコメント化を解除します。GridViewBinding
をインフレートする行を削除するか、コメントアウトします。上記の変更により、前のタスクで行った一時的な変更が取り消されます。
val binding = FragmentOverviewBinding.inflate(inflater)
// val binding = GridViewItemBinding.inflate(inflater)
res/layout/fragment_overview.xml
を開きます。<TextView>
要素全体を削除します。- 代わりに、次の
<RecyclerView>
要素を追加します。この要素は、単一アイテムにGridLayoutManager
とgrid_view_item
レイアウトを使用します。
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/photos_grid"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="6dp"
android:clipToPadding="false"
app:layoutManager=
"androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:spanCount="2"
tools:itemCount="16"
tools:listitem="@layout/grid_view_item" />
ステップ 3: 写真グリッド アダプターを追加する
以上で、fragment_overview
レイアウトに RecyclerView
が含まれ、grid_view_item
レイアウトに単一の ImageView
が含まれるようになりました。このステップでは、RecyclerView
アダプターを使用して、データを RecyclerView
にバインドします。
overview/PhotoGridAdapter.kt
を開きます。- 下記のコンストラクタ パラメータを使って、
PhotoGridAdapter
クラスを作成します。PhotoGridAdapter
クラスはListAdapter
を拡張します。このクラスのコンストラクタには、リストアイテムの型、ビューホルダー、DiffUtil.ItemCallback
実装が必要です。
リクエストされた場合は、androidx.recyclerview.widget.ListAdapter
クラスとcom.example.android.marsrealestate.network.MarsProperty
クラスをインポートします。次のステップでは、このコンストラクタに欠けている(それによってエラーを引き起こしている)別の機能を実装します。
class PhotoGridAdapter : ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
}
PhotoGridAdapter
クラス内の任意の場所をクリックし、Control+i
を押して、ListAdapter
メソッド(onCreateViewHolder()
とonBindViewHolder()
)を実装します。
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoGridAdapter.MarsPropertyViewHolder {
TODO("not implemented")
}
override fun onBindViewHolder(holder: PhotoGridAdapter.MarsPropertyViewHolder, position: Int) {
TODO("not implemented")
}
- 以下に示すように、
PhotoGridAdapter
クラス定義の最後で、追加したメソッドの後に、DiffCallback
のコンパニオン オブジェクト定義を追加します。
リクエストされたら、androidx.recyclerview.widget.DiffUtil
をインポートします。DiffCallback
オブジェクトは、比較したいオブジェクト(MarsProperty
)の型を使ってDiffUtil.ItemCallback
を拡張します。
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}
Control+i
を押して、このオブジェクトのコンパレータ メソッド(areItemsTheSame()
とareContentsTheSame()
)を実装します。
override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented")
}
override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented") }
areItemsTheSame()
メソッドの TODO を削除します。Kotlin の参照の等価性演算子(===
)を使用します。oldItem
とnewItem
のオブジェクト参照が同じ場合はtrue
を返します。
override fun areItemsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem === newItem
}
areContentsTheSame()
の場合は、oldItem
とnewItem
の ID に対して標準の等価演算子を使用します。
override fun areContentsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem.id == newItem.id
}
PhotoGridAdapter
クラス内で、コンパニオン オブジェクトの下に、RecyclerView.ViewHolder
を拡張するMarsPropertyViewHolder
の内部クラス定義を追加します。
必要に応じてandroidx.recyclerview.widget.RecyclerView
とcom.example.android.marsrealestate.databinding.GridViewItemBinding
をインポートします。MarsProperty
をレイアウトにバインドするためにGridViewItemBinding
変数が必要なので、変数をMarsPropertyViewHolder
に渡します。基本クラスViewHolder
ではコンストラクタ内にビューが必要なので、バインディング ルートビューを渡します。
class MarsPropertyViewHolder(private var binding:
GridViewItemBinding):
RecyclerView.ViewHolder(binding.root) {
}
MarsPropertyViewHolder
内で、MarsProperty
オブジェクトを引数として受け取り、binding.property
をそのオブジェクトに設定するbind()
メソッドを作成します。プロパティを設定した後、executePendingBindings()
を呼び出します。これにより、更新が直ちに実行されます。
fun bind(marsProperty: MarsProperty) {
binding.property = marsProperty
binding.executePendingBindings()
}
onCreateViewHolder()
で、TODO を削除して下記の行を追加します。リクエストされたら、android.view.LayoutInflater
をインポートします。onCreateViewHolder()
メソッドは新しいMarsPropertyViewHolder
を返す必要があります。このビューホルダーは、GridViewItemBinding
をインフレートし、親ViewGroup
コンテキストからのLayoutInflater
を使用することにより、作成されます。
return MarsPropertyViewHolder(GridViewItemBinding.inflate(
LayoutInflater.from(parent.context)))
onBindViewHolder()
メソッドで、TODO を削除して下記の行を追加します。ここでgetItem()
を呼び出し、現在のRecyclerView
の位置に関連付けられているMarsProperty
オブジェクトを取得して、そのプロパティをMarsPropertyViewHolder
のbind()
メソッドに渡します。
val marsProperty = getItem(position)
holder.bind(marsProperty)
ステップ 4: バインディング アダプターを追加して要素を接続する
最後に、BindingAdapter
を使用して、MarsProperty
オブジェクトのリストで PhotoGridAdapter
を初期化します。BindingAdapter
を使用して RecyclerView
のデータを設定すると、LiveData
に MarsProperty
オブジェクトのリストがあるかどうかがデータ バインディングによって自動的に監視されるようになります。MarsProperty
リストが変更されると、バインディング アダプターが自動的に呼び出されます。
BindingAdapters.kt
を開きます。- ファイルの末尾に、
RecyclerView
とMarsProperty
オブジェクトのリストを引数として受け取るbindRecyclerView()
メソッドを追加します。そのメソッドに@BindingAdapter
アノテーションを付けます。
リクエストされたら、androidx.recyclerview.widget.RecyclerView
とcom.example.android.marsrealestate.network.MarsProperty
をインポートします。
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView,
data: List<MarsProperty>?) {
}
bindRecyclerView()
関数内で、recyclerView.adapter
をPhotoGridAdapter
にキャストし、データを使用してadapter.submitList()
を呼び出します。これにより、新しいリストが利用可能であることがRecyclerView
に通知されます。
リクエストされたら、com.example.android.marsrealestate.overview.PhotoGridAdapter
をインポートします。
val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)
res/layout/fragment_overview.xml
を開きます。RecyclerView
要素にapp:listData
属性を追加し、データ バインディングを使用してそれをviewmodel.properties
に設定します。
app:listData="@{viewModel.properties}"
overview/OverviewFragment.kt
を開きます。onCreateView()
のsetHasOptionsMenu()
の呼び出しの直前で、binding.photosGrid
のRecyclerView
アダプターを新しいPhotoGridAdapter
オブジェクトに初期化します。
binding.photosGrid.adapter = PhotoGridAdapter()
- アプリを実行します。
MarsProperty
画像のグリッドが表示されます。スクロールして新しい画像を表示すると、画像自体が表示される前に、読み込みが進行中であることを示すアイコンが表示されます。機内モードをオンにすると、まだ読み込まれていない画像が、破損した画像のアイコンとして表示されます。
MarsRealEstate アプリは、画像を取得できない場合、破損した画像のアイコンを表示します。しかし、ネットワーク接続がない場合は、空白の画面を表示します。
これはユーザー エクスペリエンスとしては不適切です。このタスクでは、基本的なエラー処理を追加して、何が起こっているかをユーザーが理解できるようにします。インターネットが利用できない場合、アプリは接続エラーアイコンを表示します。MarsProperty
リストの取得中は、読み込み中のアニメーションを表示します。
ステップ 1: ビューモデルにステータスを追加する
まず、ウェブ リクエストのステータスを表す LiveData
をビューモデルに作成します。考慮する必要があるステータスは、読み込み中、成功、失敗の 3 つです。「読み込み中」ステータスは、await()
の呼び出しでデータを待機している状態を示します。
overview/OverviewViewModel.kt
を開きます。ファイルの先頭部分(インポートの後、クラス定義の前)に、すべての使用可能なステータスを表すenum
を追加します。
enum class MarsApiStatus { LOADING, ERROR, DONE }
OverviewViewModel
クラス全体で、内部と外部の両方の_response
ライブデータ定義の名前を_status
に変更します。この Codelab の前半で_properties
LiveData
のサポートを追加したため、ウェブサービス レスポンス全体が未使用になっています。現在のステータスを追跡するにはLiveData
が必要です。既存の変数の名前を変更するだけで済みます。
また、型を String
から MarsApiStatus.
に変更します。
private val _status = MutableLiveData<MarsApiStatus>()
val status: LiveData<MarsApiStatus>
get() = _status
- 下にスクロールして
getMarsRealEstateProperties()
メソッドを表示し、ここでも_response
を_status
に更新します。"Success"
文字列をMarsApiStatus.DONE
状態に変更し、"Failure"
文字列をMarsApiStatus.ERROR
に変更します。 await()
の呼び出しの前に、try {}
ブロックの先頭にMarsApiStatus.LOADING
ステータスを追加します。これは、コルーチンを実行してデータを待機しているときの初期ステータスです。完全なtry/catch {}
ブロックは次のようになります。
try {
_status.value = MarsApiStatus.LOADING
var listResult = getPropertiesDeferred.await()
_status.value = MarsApiStatus.DONE
_properties.value = listResult
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
}
catch {}
ブロックのエラー状態の後で、_properties
LiveData
を空のリストに設定します。これにより、RecyclerView
がクリアされます。
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
_properties.value = ArrayList()
}
ステップ 2: ステータス ImageView のバインディング アダプターを追加する
これでビューモデルにステータスが追加されましたが、これは単なる状態のセットです。アプリ自体に表示するにはどうすればよいですか?このステップでは、データ バインディングに接続された ImageView
を使用して、読み込み中とエラーの状態を表すアイコンを表示します。アプリが読み込み中またはエラーの状態にあるときは、ImageView
を表示する必要があります。アプリが読み込みを完了したときは、ImageView
を非表示にする必要があります。
BindingAdapters.kt
を開きます。ImageView
とMarsApiStatus
値を引数として受け取るbindStatus()
という名前の新しいバインディング アダプターを追加します。リクエストされたら、com.example.android.marsrealestate.overview.MarsApiStatus
をインポートします。
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView,
status: MarsApiStatus?) {
}
- ステータスに応じて処理を切り替えるため、
bindStatus()
メソッド内にwhen {}
を追加します。
when (status) {
}
when {}
内に、「読み込み中」状態(MarsApiStatus.LOADING
)の処理を追加します。この状態の場合は、ImageView
を「表示」に設定し、読み込み中のアニメーションを割り当てます。これは、前のタスクで Glide について使用したのと同じアニメーション ドローアブルです。リクエストされたら、android.view.View
をインポートします。
when (status) {
MarsApiStatus.LOADING -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.loading_animation)
}
}
- エラー状態(
MarsApiStatus.ERROR
)の処理を追加します。LOADING
状態の処理と同様に、ステータスImageView
を「表示」に設定し、接続エラー ドローアブルを再利用します。
MarsApiStatus.ERROR -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.ic_connection_error)
}
- 完了状態(
MarsApiStatus.DONE
)の処理を追加します。この場合は成功レスポンスが返されるので、ステータスImageView
の表示をオフにして、非表示にします。
MarsApiStatus.DONE -> {
statusImageView.visibility = View.GONE
}
ステップ 3: レイアウトにステータス ImageView を追加する
res/layout/fragment_overview.xml
を開きます。ConstraintLayout
内のRecyclerView
要素の下に、下記のImageView
を追加します。
このImageView
には、RecyclerView
と同じ制約があります。ただし、幅と高さについてはwrap_content
が使用され、画像を拡大してビューを埋めるのではなく、中央に配置します。また、app:marsApiStatus
属性にも注目してください。これにより、ビューモデルのステータス プロパティが変更されたときに、ビューがBindingAdapter
を呼び出すようになります。
<ImageView
android:id="@+id/status_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:marsApiStatus="@{viewModel.status}" />
- エミュレータまたはデバイスで機内モードをオンにして、ネットワーク接続がない状態をシミュレートします。アプリをコンパイルして実行します。エラー画像が表示されることを確認してください。
- 戻るボタンをタップしてアプリを閉じ、機内モードをオフにします。最近使ったアプリの画面を使用してアプリに戻ります。ネットワーク接続の速度によっては、画像の読み込みを開始する前にアプリがウェブサービスをクエリしている間、一瞬だけ読み込み中のアイコンが表示されることがあります。
Android Studio プロジェクト: MarsRealEstateGrid
- 画像を管理するプロセスを簡素化するには、Glide ライブラリを使用して、アプリで画像をダウンロード、バッファリング、デコード、キャッシュ保存します。
- Glide がインターネットから画像を読み込むには、画像の URL と、画像を配置する
ImageView
オブジェクトの 2 つが必要です。これらのオプションを指定するには、Glide でload()
メソッドとinto()
メソッドを使用します。 - バインディング アダプターは、ビューとビューのバインドされたデータをつなげる拡張メソッドです。バインディング アダプターは、データが変更されたときのカスタム動作を提供します。たとえば、Glide を呼び出して URL から
ImageView
に画像を読み込むことができます。 - バインディング アダプターは、
@BindingAdapter
アノテーション付きの拡張メソッドです。 - Glide リクエストにオプションを追加するには、
apply()
メソッドを使用します。たとえば、placeholder()
とともにapply()
を使用して読み込み可能なドローアブルを指定し、error()
とともにapply()
を使用してエラー ドローアブルを指定します。 - 画像のグリッドを生成するには、
GridLayoutManager
でRecyclerView
を使用します。 - プロパティが変更されたときにプロパティのリストを更新するには、
RecyclerView
とレイアウトの間でバインディング アダプターを使用します。
Udacity コース:
Android デベロッパー ドキュメント:
その他:
このセクションでは、インストラクター主導のコースの一環として、この Codelab に取り組んでいる生徒向けに考えられる宿題をいくつか示します。インストラクターは、以下のようなことを行えます。
- 必要に応じて宿題を与える
- 宿題の提出方法を生徒に伝える
- 宿題を採点する
インストラクターは、これらの提案を必要なだけ使用し、必要に応じて他の宿題も自由に与えることができます。
この Codelab に独力で取り組む場合は、これらの宿題を自由に使用して知識をテストしてください。
以下の質問に回答してください
問題 1
読み込まれた画像を含む ImageView
を示すには、どの Glide メソッドを使用しますか?
▢ into()
▢ with()
▢ imageview()
▢ apply()
問題 2
Glide の読み込み時に表示するプレースホルダ画像を指定するには、どうすればよいですか。
▢ ドローアブルで into()
メソッドを使用する。
▢ RequestOptions()
を使用し、ドローアブルで placeholder()
メソッドを呼び出す。
▢ Glide.placeholder
プロパティをドローアブルに割り当てます。
▢ RequestOptions()
を使用し、ドローアブルで loadingImage()
メソッドを呼び出す。
問題 3
メソッドがバインディング アダプターであることを示すには、どうすればよいですか。
▢ LiveData
で setBindingAdapter()
メソッドを呼び出します。
▢ メソッドを BindingAdapters.kt
という Kotlin ファイルに配置する。
▢ XML レイアウトで android:adapter
属性を使用します。
▢ メソッドに @BindingAdapter
アノテーションを付けます。
次のレッスン(
このコースの他の Codelab へのリンクについては、Android Kotlin の基礎の Codelab のランディング ページをご覧ください。