Dieses Codelab ist Teil des Kurses „Grundlagen von Android und Kotlin“. Sie können diesen Kurs am besten nutzen, wenn Sie die Codelabs der Reihe nach durcharbeiten. Alle Codelabs des Kurses sind auf der Landingpage für Codelabs zu den Grundlagen von Android und Kotlin aufgeführt.
Einführung
Im vorherigen Codelab haben Sie gelernt, wie Sie Daten von einem Webdienst abrufen und die Antwort in ein Datenobjekt parsen. In diesem Codelab bauen Sie auf diesem Wissen auf, um Fotos über eine Web-URL zu laden und anzuzeigen. Außerdem wird noch einmal gezeigt, wie Sie ein RecyclerView erstellen und damit ein Raster mit Bildern auf der Übersichtsseite anzeigen.
Was Sie bereits wissen sollten
- Fragmente erstellen und verwenden
- Verwendung von Architekturkomponenten wie Viewmodels, Viewmodelfactories, Transformationen und
LiveData. - So rufen Sie JSON von einem REST-Webdienst ab und parsen die Daten mithilfe der Bibliotheken Retrofit und Moshi in Kotlin-Objekte.
- So erstellen Sie ein Rasterlayout mit einem
RecyclerView. - So funktionieren
Adapter,ViewHolderundDiffUtil.
Lerninhalte
- Verwendung der Glide-Bibliothek zum Laden und Anzeigen eines Bildes aus einer Web-URL.
- So verwenden Sie einen
RecyclerViewund einen Rasteradapter, um ein Raster mit Bildern anzuzeigen. - So behandeln Sie potenzielle Fehler beim Herunterladen und Anzeigen der Bilder.
Aufgabe
- Ändern Sie die MarsRealEstate-App so, dass die Bild-URL aus den Mars-Property-Daten abgerufen und das Bild mit Glide geladen und angezeigt wird.
- Fügen Sie der App eine Ladeanimation und ein Fehlersymbol hinzu.
- Verwenden Sie ein
RecyclerView, um ein Raster mit Bildern von Mars-Grundstücken anzuzeigen. - Fügen Sie dem
RecyclerViewStatus- und Fehlerbehandlung hinzu.
In diesem Codelab (und den zugehörigen Codelabs) arbeiten Sie mit einer App namens MarsRealEstate, in der zum Verkauf stehende Immobilien auf dem Mars angezeigt werden. Die App stellt eine Verbindung zu einem Internetserver her, um Immobiliendaten abzurufen und anzuzeigen, einschließlich Details wie Preis und ob die Immobilie zum Verkauf oder zur Vermietung verfügbar ist. Die Bilder, die die einzelnen Grundstücke darstellen, sind echte Fotos vom Mars, die von den Mars-Rovern der NASA aufgenommen wurden.

Die Version der App, die Sie in diesem Codelab erstellen, füllt die Übersichtsseite mit einem Raster aus Bildern. Die Bilder sind Teil der Property-Daten, die Ihre App vom Mars-Webdienst für Immobilien erhält. Ihre App verwendet die Glide-Bibliothek zum Laden und Anzeigen der Bilder und ein RecyclerView zum Erstellen des Rasterlayouts für die Bilder. Ihre App sollte auch Netzwerkfehler problemlos verarbeiten können.
Ein Foto über eine Web-URL anzuzeigen, klingt vielleicht einfach, aber es ist einiges an technischer Entwicklung erforderlich, damit es gut funktioniert. Das Bild muss heruntergeladen, gepuffert und aus seinem komprimierten Format in ein Bild decodiert werden, das von Android verwendet werden kann. Das Bild sollte in einem In-Memory-Cache, einem speicherbasierten Cache oder in beiden gespeichert werden. All dies muss in Hintergrundthreads mit niedriger Priorität erfolgen, damit die Benutzeroberfläche reaktionsfähig bleibt. Für eine optimale Netzwerk- und CPU-Leistung sollten Sie außerdem mehr als ein Bild gleichzeitig abrufen und decodieren. Das effektive Laden von Bildern aus dem Netzwerk könnte ein Codelab für sich sein.
Glücklicherweise können Sie die von der Community entwickelte Bibliothek Glide verwenden, um Ihre Bilder herunterzuladen, zu puffern, zu decodieren und im Cache zu speichern. Mit Glide haben Sie viel weniger Arbeit, als wenn Sie alles von Grund auf neu erstellen müssten.
Für Glide sind im Grunde zwei Dinge erforderlich:
- Die URL des Bildes, das geladen und angezeigt werden soll.
- Ein
ImageView-Objekt zum Anzeigen des Bildes.
In dieser Aufgabe erfahren Sie, wie Sie mit Glide ein einzelnes Bild aus dem Immobilien-Webdienst anzeigen. Sie zeigen das Bild an, das die erste Mars-Property in der Liste der Properties darstellt, die vom Webdienst zurückgegeben werden. Hier sehen Sie die Screenshots vor und nach der Änderung:


Schritt 1: Glide-Abhängigkeit hinzufügen
- Öffnen Sie die MarsRealEstate-App aus dem letzten Codelab. Falls Sie die App noch nicht haben, können Sie MarsRealEstateNetwork hier herunterladen.
- Führen Sie die App aus, um zu sehen, was sie tut. (Es werden Textdetails zu einem Grundstück angezeigt, das hypothetisch auf dem Mars verfügbar ist.)
- Öffnen Sie build.gradle (Module: app).
- Fügen Sie im Abschnitt
dependenciesdiese Zeile für die Glide-Bibliothek hinzu:
implementation "com.github.bumptech.glide:glide:$version_glide"
Die Versionsnummer ist bereits separat in der Gradle-Datei des Projekts definiert.
- Klicken Sie auf Jetzt synchronisieren, um das Projekt mit der neuen Abhängigkeit neu zu erstellen.
Schritt 2: View-Modell aktualisieren
Als Nächstes aktualisieren Sie die Klasse OverviewViewModel, damit sie Livedaten für eine einzelne Mars-Property enthält.
- Öffnen Sie
overview/OverviewViewModel.kt. Fügen Sie direkt unter demLiveDatafür das_responsesowohl interne (veränderliche) als auch externe (unveränderliche) Live-Daten für ein einzelnesMarsProperty-Objekt hinzu.
Importieren Sie die KlasseMarsProperty(com.example.android.marsrealestate.network.MarsProperty), wenn Sie dazu aufgefordert werden.
private val _property = MutableLiveData<MarsProperty>()
val property: LiveData<MarsProperty>
get() = _property- Suchen Sie in der Methode
getMarsRealEstateProperties()die Zeile imtry/catch {}-Block, in der_response.valueauf die Anzahl der Attribute festgelegt wird. Fügen Sie den unten gezeigten Test hinzu. WennMarsProperty-Objekte verfügbar sind, wird mit diesem Test der Wert des_propertyLiveDataauf die erste Eigenschaft in derlistResultfestgelegt.
if (listResult.size > 0) {
_property.value = listResult[0]
}Der vollständige try/catch {}-Block sieht jetzt so aus:
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}"
}- Öffnen Sie die Datei
res/layout/fragment_overview.xml. Ändern Sie im<TextView>-Elementandroid:text, um die Bindung an dieimgSrcUrl-Komponente despropertyLiveDataherzustellen:
android:text="@{viewModel.property.imgSrcUrl}"- Führen Sie die App aus. In
TextViewwird nur die URL des Bildes in der ersten Mars-Property angezeigt. Bisher haben Sie nur das View-Modell und die Live-Daten für diese URL eingerichtet.

Schritt 3: Binding-Adapter erstellen und Glide aufrufen
Sie haben jetzt die URL eines Bildes, das angezeigt werden soll. Nun können Sie mit Glide beginnen, dieses Bild zu laden. In diesem Schritt verwenden Sie einen Binding-Adapter, um die URL aus einem XML-Attribut abzurufen, das mit einem ImageView verknüpft ist. Anschließend verwenden Sie Glide, um das Bild zu laden. Binding-Adapter sind Erweiterungsmethoden, die zwischen einer Ansicht und gebundenen Daten liegen und benutzerdefiniertes Verhalten ermöglichen, wenn sich die Daten ändern. In diesem Fall wird Glide aufgerufen, um ein Bild aus einer URL in ein ImageView zu laden.
- Öffnen Sie
BindingAdapters.kt. Diese Datei enthält die Bindungsadapter, die Sie in der gesamten App verwenden. - Erstellen Sie eine
bindImage()-Funktion, die einImageViewund einStringals Parameter verwendet. Kommentieren Sie die Funktion mit@BindingAdapter. Die Annotation@BindingAdapterweist die Datenbindung an, diesen Bindungsadapter auszuführen, wenn ein XML-Element das AttributimageUrlhat.
Importieren Sieandroidx.databinding.BindingAdapterundandroid.widget.ImageView, wenn Sie dazu aufgefordert werden.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
}- Fügen Sie in der Funktion
bindImage()einenlet {}-Block für das ArgumentimgUrlein:
imgUrl?.let {
}- Fügen Sie im
let {}-Block die unten gezeigte Zeile hinzu, um den URL-String (aus dem XML) in einUri-Objekt zu konvertieren. Importieren Sieandroidx.core.net.toUri, wenn Sie dazu aufgefordert werden.
Das endgültigeUri-Objekt soll das HTTPS-Schema verwenden, da der Server, von dem Sie die Bilder abrufen, dieses Schema erfordert. Wenn Sie das HTTPS-Schema verwenden möchten, hängen SiebuildUpon.scheme("https")an dentoUri-Builder an. Die MethodetoUri()ist eine Kotlin-Erweiterungsfunktion aus der Android KTX-Kernbibliothek. Sie sieht also so aus, als wäre sie Teil der KlasseString.
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()- Rufen Sie in
let {}Glide.with()auf, um das Bild aus demUri-Objekt in dasImageViewzu laden. Importieren Siecom.bumptech.glide.Glide, wenn Sie dazu aufgefordert werden.
Glide.with(imgView.context)
.load(imgUri)
.into(imgView)Schritt 4: Layout und Fragmente aktualisieren
Das Bild wurde zwar von Glide geladen, aber es ist noch nichts zu sehen. Im nächsten Schritt aktualisieren Sie das Layout und die Fragmente mit einem ImageView, um das Bild anzuzeigen.
- Öffnen Sie
res/layout/gridview_item.xml. Dies ist die Layoutressourcendatei, die Sie später im Codelab für jedes Element in derRecyclerViewverwenden. Sie verwenden es hier vorübergehend, um nur das einzelne Bild anzuzeigen. - Fügen Sie über dem
<ImageView>-Element ein<data>-Element für die Datenbindung hinzu und binden Sie es an dieOverviewViewModel-Klasse:
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>- Fügen Sie dem
ImageView-Element einapp:imageUrl-Attribut hinzu, um den neuen Binding-Adapter für das Laden von Bildern zu verwenden:
app:imageUrl="@{viewModel.property.imgSrcUrl}"- Öffnen Sie
overview/OverviewFragment.kt. Kommentieren Sie in der MethodeonCreateView()die Zeile aus, mit der die KlasseFragmentOverviewBindinginstanziiert und der Bindungsvariable zugewiesen wird. Das ist nur vorübergehend. Sie kehren später wieder dorthin zurück.
//val binding = FragmentOverviewBinding.inflate(inflater)- Fügen Sie stattdessen eine Zeile hinzu, um die Klasse
GridViewItemBindingzu erweitern. Importieren Siecom.example.android.marsrealestate. databinding.GridViewItemBinding, wenn Sie dazu aufgefordert werden.
val binding = GridViewItemBinding.inflate(inflater)- Führen Sie die App aus. In der Ergebnisliste sollte jetzt ein Foto des Bildes aus dem ersten
MarsPropertyangezeigt werden.
Schritt 5: Einfache Lade- und Fehlerbilder hinzufügen
Glide kann die Nutzerfreundlichkeit verbessern, indem während des Ladens des Bildes ein Platzhalterbild und bei einem Fehler beim Laden ein Fehlerbild angezeigt wird, z. B. wenn das Bild fehlt oder beschädigt ist. In diesem Schritt fügen Sie diese Funktion dem Binding-Adapter und dem Layout hinzu.
- Öffnen Sie
res/drawable/ic_broken_image.xmlund klicken Sie rechts auf den Tab Vorschau. Für das Fehlerbild verwenden Sie das Symbol für ein defektes Bild, das in der integrierten Symbolbibliothek verfügbar ist. In diesem Vektor-Drawable wird das Attributandroid:tintverwendet, um das Symbol grau zu färben.

- Öffnen Sie
res/drawable/loading_animation.xml. Dieses Drawable ist eine Animation, die mit dem Tag<animate-rotate>definiert wird. Bei der Animation wird ein Bild-Drawable,loading_img.xml, um den Mittelpunkt gedreht. Die Animation wird in der Vorschau nicht angezeigt.

- Kehren Sie zur Datei
BindingAdapters.ktzurück. Aktualisieren Sie in der MethodebindImage()den Aufruf vonGlide.with()so, dass die Funktionapply()zwischenload()undinto()aufgerufen wird.com.bumptech.glide.request.RequestOptionsImportieren, wenn angefordert.
Mit diesem Code wird das Platzhalter-Ladebild festgelegt, das während des Ladens verwendet werden soll (dasloading_animation-Drawable). Der Code legt auch ein Bild fest, das verwendet werden soll, wenn das Laden des Bildes fehlschlägt (dasbroken_image-Drawable). Die vollständigebindImage()-Methode sieht jetzt so aus:
@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)
}
}
- Führen Sie die App aus. Je nach Geschwindigkeit Ihrer Netzwerkverbindung wird möglicherweise kurz das Ladebild angezeigt, während Glide das Immobilienbild herunterlädt und anzeigt. Das Symbol für das fehlerhafte Bild wird jedoch noch nicht angezeigt, auch wenn Sie das Netzwerk deaktivieren. Das beheben Sie im letzten Teil des Codelabs.
Ihre App lädt jetzt Property-Informationen aus dem Internet. Anhand der Daten aus dem ersten MarsProperty-Listenelement haben Sie im Ansichtsmodell eine LiveData-Property erstellt und die Bild-URL aus den Daten dieser Property verwendet, um ein ImageView zu füllen. Da in Ihrer App aber ein Raster mit Bildern angezeigt werden soll, müssen Sie ein RecyclerView mit einem GridLayoutManager verwenden.
Schritt 1: View-Modell aktualisieren
Das View-Modell hat derzeit ein _property LiveData, das ein MarsProperty-Objekt enthält – das erste in der Antwortliste des Webdiensts. In diesem Schritt ändern Sie LiveData so, dass es die gesamte Liste der MarsProperty-Objekte enthält.
- Öffnen Sie
overview/OverviewViewModel.kt. - Ändern Sie die private Variable
_propertyin_properties. Ändern Sie den Typ in eine Liste vonMarsProperty-Objekten.
private val _properties = MutableLiveData<List<MarsProperty>>()- Ersetzen Sie die externen
property-Live-Daten durchproperties. Fügen Sie die Liste auch hier dem TypLiveDatahinzu:
val properties: LiveData<List<MarsProperty>>
get() = _properties- Scrollen Sie nach unten zur Methode
getMarsRealEstateProperties(). Ersetzen Sie imtry {}-Block den gesamten Test, den Sie in der vorherigen Aufgabe hinzugefügt haben, durch die unten gezeigte Zeile. Da die VariablelistResulteine Liste vonMarsProperty-Objekten enthält, können Sie sie einfach_properties.valuezuweisen, anstatt auf eine erfolgreiche Antwort zu testen.
_properties.value = listResultDer gesamte try/catch-Block sieht jetzt so aus:
try {
var listResult = getPropertiesDeferred.await()
_response.value = "Success: ${listResult.size} Mars properties retrieved"
_properties.value = listResult
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}Schritt 2: Layouts und Fragmente aktualisieren
Im nächsten Schritt ändern Sie das Layout und die Fragmente der App, sodass anstelle der einzelnen Bildansicht eine Recycler-Ansicht und ein Rasterlayout verwendet werden.
- Öffnen Sie
res/layout/gridview_item.xml. Ändern Sie die Datenbindung vonOverviewViewModelinMarsPropertyund benennen Sie die Variable in"property"um.
<variable
name="property"
type="com.example.android.marsrealestate.network.MarsProperty" />- Ändern Sie im
<ImageView>das Attributapp:imageUrlso, dass es auf die Bild-URL imMarsProperty-Objekt verweist:
app:imageUrl="@{property.imgSrcUrl}"- Öffnen Sie
overview/OverviewFragment.kt. Entfernen Sie inonCreateview()die Kommentarzeichen aus der Zeile, in derFragmentOverviewBindingaufgebläht wird. Löschen Sie die Zeile, in derGridViewBindingaufgebläht wird, oder kommentieren Sie sie aus. Durch diese Änderungen werden die temporären Änderungen rückgängig gemacht, die Sie in der letzten Aufgabe vorgenommen haben.
val binding = FragmentOverviewBinding.inflate(inflater)
// val binding = GridViewItemBinding.inflate(inflater)- Öffnen Sie
res/layout/fragment_overview.xml. Löschen Sie das gesamte<TextView>-Element. - Fügen Sie stattdessen dieses
<RecyclerView>-Element hinzu, das einGridLayoutManagerund dasgrid_view_item-Layout für ein einzelnes Element verwendet:
<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" />Schritt 3: Fotogrid-Adapter hinzufügen
Das fragment_overview-Layout hat jetzt ein RecyclerView, während das grid_view_item-Layout ein einzelnes ImageView hat. In diesem Schritt binden Sie die Daten über einen RecyclerView-Adapter an RecyclerView.
- Öffnen Sie
overview/PhotoGridAdapter.kt. - Erstellen Sie die
PhotoGridAdapter-Klasse mit den unten gezeigten Konstruktorparametern. Die KlassePhotoGridAdapterwird vonListAdapterabgeleitet. Der Konstruktor vonListAdapterbenötigt den Listenelementtyp, den View-Holder und eineDiffUtil.ItemCallback-Implementierung.
Importieren Sie die Klassenandroidx.recyclerview.widget.ListAdapterundcom.example.android.marsrealestate.network.MarsProperty, wenn Sie dazu aufgefordert werden. In den folgenden Schritten implementieren Sie die anderen fehlenden Teile dieses Konstruktors, die Fehler verursachen.
class PhotoGridAdapter : ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
}- Klicken Sie auf eine beliebige Stelle in der Klasse
PhotoGridAdapterund drücken SieControl+i, um dieListAdapter-MethodenonCreateViewHolder()undonBindViewHolder()zu implementieren.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoGridAdapter.MarsPropertyViewHolder {
TODO("not implemented")
}
override fun onBindViewHolder(holder: PhotoGridAdapter.MarsPropertyViewHolder, position: Int) {
TODO("not implemented")
}- Fügen Sie am Ende der
PhotoGridAdapter-Klassendefinition nach den Methoden, die Sie gerade hinzugefügt haben, eine Companion-Objektdefinition fürDiffCallbackein, wie unten dargestellt.
Importieren Sieandroidx.recyclerview.widget.DiffUtil, wenn Sie dazu aufgefordert werden.
DasDiffCallback-Objekt erweitertDiffUtil.ItemCallbackmit dem Typ des Objekts, das Sie vergleichen möchten:MarsProperty.
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}- Drücken Sie
Control+i, um die Vergleichsmethoden für dieses Objekt zu implementieren, nämlichareItemsTheSame()undareContentsTheSame().
override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented")
}
override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented") }- Entfernen Sie für die Methode
areItemsTheSame()die TODO-Anweisung. Verwenden Sie den referenziellen Gleichheitsoperator von Kotlin (===), dertruezurückgibt, wenn die Objektreferenzen füroldItemundnewItemidentisch sind.
override fun areItemsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem === newItem
}- Verwenden Sie für
areContentsTheSame()den Standardgleichheitsoperator nur für die ID vonoldItemundnewItem.
override fun areContentsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem.id == newItem.id
}- Fügen Sie in der
PhotoGridAdapter-Klasse unter dem Companion-Objekt eine innere Klassendefinition fürMarsPropertyViewHolderhinzu, dieRecyclerView.ViewHoldererweitert.
Importieren Sieandroidx.recyclerview.widget.RecyclerViewundcom.example.android.marsrealestate.databinding.GridViewItemBinding, wenn Sie dazu aufgefordert werden.
Sie benötigen dieGridViewItemBinding-Variable, umMarsPropertyan das Layout zu binden. Übergeben Sie die Variable also anMarsPropertyViewHolder. Da die BasisklasseViewHoldereine Ansicht in ihrem Konstruktor erfordert, übergeben Sie ihr die Bindungs-Root-Ansicht.
class MarsPropertyViewHolder(private var binding:
GridViewItemBinding):
RecyclerView.ViewHolder(binding.root) {
}- Erstellen Sie in
MarsPropertyViewHoldereinebind()-Methode, die einMarsProperty-Objekt als Argument akzeptiert undbinding.propertyauf dieses Objekt festlegt. Rufen SieexecutePendingBindings()auf, nachdem Sie das Attribut festgelegt haben. Dadurch wird das Update sofort ausgeführt.
fun bind(marsProperty: MarsProperty) {
binding.property = marsProperty
binding.executePendingBindings()
}- Entfernen Sie in
onCreateViewHolder()das TODO und fügen Sie die unten gezeigte Zeile hinzu. Importieren Sieandroid.view.LayoutInflater, wenn Sie dazu aufgefordert werden.
Die MethodeonCreateViewHolder()muss ein neuesMarsPropertyViewHolderzurückgeben, das durch Aufblähen desGridViewItemBindingund Verwenden desLayoutInflateraus dem übergeordnetenViewGroup-Kontext erstellt wird.
return MarsPropertyViewHolder(GridViewItemBinding.inflate(
LayoutInflater.from(parent.context)))- Entfernen Sie in der Methode
onBindViewHolder()das TODO und fügen Sie die unten gezeigten Zeilen hinzu. Hier rufen SiegetItem()auf, um dasMarsProperty-Objekt abzurufen, das mit der aktuellenRecyclerView-Position verknüpft ist, und übergeben diese Property dann an diebind()-Methode in derMarsPropertyViewHolder.
val marsProperty = getItem(position)
holder.bind(marsProperty)Schritt 4: Binding-Adapter hinzufügen und Teile verbinden
Verwenden Sie schließlich ein BindingAdapter, um das PhotoGridAdapter mit der Liste der MarsProperty-Objekte zu initialisieren. Wenn Sie ein BindingAdapter verwenden, um die RecyclerView-Daten festzulegen, wird die Datenbindung automatisch für die Liste der MarsProperty-Objekte beobachtet.LiveData Der Binding-Adapter wird dann automatisch aufgerufen, wenn sich die Liste MarsProperty ändert.
- Öffnen Sie
BindingAdapters.kt. - Fügen Sie am Ende der Datei eine
bindRecyclerView()-Methode hinzu, die einRecyclerViewund eine Liste vonMarsProperty-Objekten als Argumente verwendet. Fügen Sie der Methode eine Annotation mit@BindingAdapterhinzu.
Importieren Sieandroidx.recyclerview.widget.RecyclerViewundcom.example.android.marsrealestate.network.MarsProperty, wenn Sie dazu aufgefordert werden.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView,
data: List<MarsProperty>?) {
}- Wandeln Sie
recyclerView.adapterin der FunktionbindRecyclerView()inPhotoGridAdapterum und rufen Sieadapter.submitList()mit den Daten auf. So wirdRecyclerViewdarüber informiert, wenn eine neue Liste verfügbar ist.
Importieren Sie com.example.android.marsrealestate.overview.PhotoGridAdapter, wenn Sie dazu aufgefordert werden.
val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)- Öffnen Sie
res/layout/fragment_overview.xml. Fügen Sie dem ElementRecyclerViewdas Attributapp:listDatahinzu und legen Sie es mithilfe der Datenbindung aufviewmodel.propertiesfest.
app:listData="@{viewModel.properties}"- Öffnen Sie
overview/OverviewFragment.kt. Initialisieren Sie inonCreateView(), kurz vor dem Aufruf vonsetHasOptionsMenu(), denRecyclerView-Adapter inbinding.photosGridmit einem neuenPhotoGridAdapter-Objekt.
binding.photosGrid.adapter = PhotoGridAdapter()- Führen Sie die App aus. Sie sollten ein Raster mit
MarsProperty-Bildern sehen. Wenn Sie scrollen, um neue Bilder zu sehen, wird in der App das Symbol für den Ladestatus angezeigt, bevor das Bild selbst eingeblendet wird. Wenn Sie den Flugmodus aktivieren, werden Bilder, die noch nicht geladen wurden, als Symbol für ein defektes Bild angezeigt.

In der MarsRealEstate-App wird das Symbol für ein beschädigtes Bild angezeigt, wenn ein Bild nicht abgerufen werden kann. Wenn jedoch kein Netzwerk vorhanden ist, wird in der App ein leerer Bildschirm angezeigt.

Das ist nicht gerade nutzerfreundlich. In dieser Aufgabe fügen Sie eine grundlegende Fehlerbehandlung hinzu, damit der Nutzer besser nachvollziehen kann, was passiert. Wenn keine Internetverbindung besteht, wird in der App das Symbol für Verbindungsfehler angezeigt. Während die App die Liste MarsProperty abruft, wird die Ladeanimation angezeigt.
Schritt 1: Status zum Ansichtsmodell hinzufügen
Erstellen Sie zuerst ein LiveData im Ansichtsmodell, um den Status der Webanfrage darzustellen. Es gibt drei Status: „Wird geladen“, „Erfolg“ und „Fehler“. Der Ladestatus wird angezeigt, während Sie auf Daten im Aufruf an await() warten.
- Öffnen Sie
overview/OverviewViewModel.kt. Fügen Sie oben in der Datei (nach den Importen, vor der Klassendefinition) einenumhinzu, um alle verfügbaren Status darzustellen:
enum class MarsApiStatus { LOADING, ERROR, DONE }- Benennen Sie sowohl die internen als auch die externen
_response-Livedatendefinitionen in der gesamtenOverviewViewModel-Klasse in_statusum. Da Sie in diesem Codelab bereits Unterstützung für_propertiesLiveDatahinzugefügt haben, wurde die vollständige Webdienstantwort nicht verwendet. Sie benötigen hier eineLiveData, um den aktuellen Status im Blick zu behalten. Sie können die vorhandenen Variablen also einfach umbenennen.
Ändern Sie außerdem die Typen von String in MarsApiStatus..
private val _status = MutableLiveData<MarsApiStatus>()
val status: LiveData<MarsApiStatus>
get() = _status- Scrollen Sie nach unten zur Methode
getMarsRealEstateProperties()und aktualisieren Sie_responseauch hier auf_status. Ändern Sie den String"Success"in den StatusMarsApiStatus.DONEund den String"Failure"inMarsApiStatus.ERROR. - Fügen Sie oben im
try {}-Block vor dem Aufruf vonawait()den StatusMarsApiStatus.LOADINGhinzu. Dies ist der ursprüngliche Status, während die Coroutine ausgeführt wird und Sie auf Daten warten. Der vollständigetry/catch {}-Block sieht jetzt so aus:
try {
_status.value = MarsApiStatus.LOADING
var listResult = getPropertiesDeferred.await()
_status.value = MarsApiStatus.DONE
_properties.value = listResult
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
}- Legen Sie nach dem Fehlerstatus im Block
catch {}die_propertiesLiveDataauf eine leere Liste fest. Dadurch wird dieRecyclerViewgelöscht.
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
_properties.value = ArrayList()
}Schritt 2: Bindungsadapter für die Status-ImageView hinzufügen
Jetzt haben Sie einen Status im Ansichtsmodell, aber er besteht nur aus einer Reihe von Status. Wie kann ich es in der App selbst anzeigen lassen? In diesem Schritt verwenden Sie ein ImageView, das mit der Datenbindung verbunden ist, um Symbole für die Lade- und Fehlerstatus anzuzeigen. Wenn sich die App im Lade- oder Fehlerstatus befindet, sollte ImageView sichtbar sein. Wenn die App geladen wurde, sollte ImageView nicht mehr zu sehen sein.
- Öffnen Sie
BindingAdapters.kt. Fügen Sie einen neuen Binding-Adapter namensbindStatus()hinzu, der einenImageView- und einenMarsApiStatus-Wert als Argumente akzeptiert. Importieren Siecom.example.android.marsrealestate.overview.MarsApiStatus, wenn Sie dazu aufgefordert werden.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView,
status: MarsApiStatus?) {
}- Fügen Sie in der Methode
bindStatus()einwhen {}hinzu, um zwischen den verschiedenen Status zu wechseln.
when (status) {
}- Fügen Sie im
when {}einen Case für den Ladestatus (MarsApiStatus.LOADING) hinzu. Legen Sie für diesen StatusImageViewauf „Sichtbar“ fest und weisen Sie ihm die Ladeanimation zu. Das ist dasselbe Animations-Drawable, das Sie in der vorherigen Aufgabe für Glide verwendet haben. Importieren Sieandroid.view.View, wenn Sie dazu aufgefordert werden.
when (status) {
MarsApiStatus.LOADING -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.loading_animation)
}
}- Fügen Sie einen Fall für den Fehlerstatus hinzu, der
MarsApiStatus.ERRORist. Ähnlich wie beim StatusLOADINGlegen Sie den StatusImageViewauf „visible“ fest und verwenden Sie das Drawable für Verbindungsfehler wieder.
MarsApiStatus.ERROR -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.ic_connection_error)
}- Fügen Sie einen Fall für den Status „Erledigt“ hinzu, der
MarsApiStatus.DONEist. Hier sehen Sie eine erfolgreiche Antwort. Deaktivieren Sie die Sichtbarkeit des StatusImageView, um ihn auszublenden.
MarsApiStatus.DONE -> {
statusImageView.visibility = View.GONE
}Schritt 3: Status-ImageView dem Layout hinzufügen
- Öffnen Sie
res/layout/fragment_overview.xml. Fügen Sie unter demRecyclerView-Element innerhalb vonConstraintLayoutdas unten gezeigteImageViewein.
DiesesImageViewhat dieselben Einschränkungen wie dasRecyclerView. Bei der Breite und Höhe wird das Bild jedoch mitwrap_contentzentriert, anstatt es so zu strecken, dass es die Ansicht ausfüllt. Beachten Sie auch das Attributapp:marsApiStatus, mit dem die Ansicht IhreBindingAdapteraufruft, wenn sich die Status-Property im Ansichtsmodell ändert.
<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}" />- Aktivieren Sie den Flugmodus in Ihrem Emulator oder auf Ihrem Gerät, um eine fehlende Netzwerkverbindung zu simulieren. Kompilieren Sie die App und führen Sie sie aus. Das Fehlerbild wird angezeigt:

- Tippen Sie auf die Zurück-Schaltfläche, um die App zu schließen, und deaktivieren Sie den Flugmodus. Rufen Sie die App über den Bildschirm „Zuletzt verwendet“ wieder auf. Je nach Geschwindigkeit Ihrer Netzwerkverbindung wird möglicherweise kurz ein Ladesymbol angezeigt, während die App den Webdienst abfragt, bevor die Bilder geladen werden.
Android Studio-Projekt: MarsRealEstateGrid
- Um die Verwaltung von Bildern zu vereinfachen, können Sie die Glide-Bibliothek verwenden, um Bilder in Ihrer App herunterzuladen, zu puffern, zu decodieren und zu cachen.
- Glide benötigt zwei Dinge, um ein Bild aus dem Internet zu laden: die URL eines Bildes und ein
ImageView-Objekt, in das das Bild eingefügt werden soll. Verwenden Sie die Methodenload()undinto()mit Glide, um diese Optionen anzugeben. - Binding-Adapter sind Erweiterungsmethoden, die zwischen einer Ansicht und den gebundenen Daten dieser Ansicht liegen. Bindungsadapter bieten benutzerdefiniertes Verhalten, wenn sich die Daten ändern, z. B. um Glide aufzurufen, damit ein Bild aus einer URL in ein
ImageViewgeladen wird. - Binding-Adapter sind Erweiterungsmethoden, die mit der Annotation
@BindingAdapterannotiert sind. - Verwenden Sie die Methode
apply(), um der Glide-Anfrage Optionen hinzuzufügen. Verwenden Sie beispielsweiseapply()mitplaceholder(), um ein Drawable für das Laden anzugeben, undapply()miterror(), um ein Fehler-Drawable anzugeben. - Um ein Raster aus Bildern zu erstellen, verwenden Sie ein
RecyclerViewmit einemGridLayoutManager. - Verwenden Sie einen Binding-Adapter zwischen
RecyclerViewund dem Layout, um die Liste der Eigenschaften zu aktualisieren, wenn sie sich ändert.
Udacity-Kurs:
Android-Entwicklerdokumentation:
Sonstiges:
In diesem Abschnitt werden mögliche Hausaufgaben für Schüler und Studenten aufgeführt, die dieses Codelab im Rahmen eines von einem Kursleiter geleiteten Kurses durcharbeiten. Es liegt in der Verantwortung des Kursleiters, Folgendes zu tun:
- Weisen Sie bei Bedarf Aufgaben zu.
- Teilen Sie den Schülern/Studenten mit, wie sie Hausaufgaben abgeben können.
- Benoten Sie die Hausaufgaben.
Lehrkräfte können diese Vorschläge nach Belieben nutzen und auch andere Hausaufgaben zuweisen, die sie für angemessen halten.
Wenn Sie dieses Codelab selbst durcharbeiten, können Sie mit diesen Hausaufgaben Ihr Wissen testen.
Beantworten Sie diese Fragen
Frage 1
Mit welcher Glide-Methode geben Sie das ImageView an, das das geladene Bild enthalten soll?
▢ into()
▢ with()
▢ imageview()
▢ apply()
Frage 2
Wie gebe ich ein Platzhalterbild an, das während des Ladens von Glide angezeigt werden soll?
▢ Verwenden Sie die into()-Methode mit einem Drawable.
▢ Verwenden Sie RequestOptions() und rufen Sie die Methode placeholder() mit einem Drawable auf.
▢ Weisen Sie der Eigenschaft Glide.placeholder eine Zeichnung zu.
▢ Verwenden Sie RequestOptions() und rufen Sie die Methode loadingImage() mit einem Drawable auf.
Frage 3
Wie geben Sie an, dass eine Methode ein Binding-Adapter ist?
▢ Rufen Sie die Methode setBindingAdapter() für LiveData auf.
▢ Fügen Sie die Methode in eine Kotlin-Datei namens BindingAdapters.kt ein.
▢ Verwenden Sie das Attribut android:adapter im XML-Layout.
▢ Kommentieren Sie die Methode mit @BindingAdapter.
Nächste Lektion:
Links zu anderen Codelabs in diesem Kurs finden Sie auf der Landingpage für Android Kotlin Fundamentals-Codelabs.