Android Kotlin Fundamentals 08.2: Bilder aus dem Internet laden und anzeigen

Dieses Codelab ist Teil des Android Kotlin Fundamentals-Kurss. Sie profitieren von diesem Kurs, wenn Sie nacheinander die Codelabs durcharbeiten. Alle Kurs-Codelabs finden Sie auf der Landingpage für Kotlin-Grundlagen für Android-Entwickler.

Einführung

Im vorherigen Codelab haben Sie gelernt, wie Sie Daten aus einem Webdienst abrufen und die Antwort in ein Datenobjekt parsen. In diesem Codelab baust du auf diesem Wissen auf, um Fotos über eine Web-URL zu laden und anzuzeigen. Sie sehen sich außerdem an, wie Sie einen RecyclerView erstellen und damit ein Raster mit Bildern auf der Übersichtsseite aufrufen.

Wichtige Informationen

  • Fragmente erstellen und verwenden
  • Architekturkomponenten verwenden, einschließlich Modellen ansehen, Modellfabriken ansehen, Transformationen und LiveData ansehen.
  • JSON- Inhalt aus einem REST-Webdienst abrufen und mit den Retrofit- und Moshi-Bibliotheken in Kotlin-Objekte parsen.
  • So erstellst du ein Rasterlayout mit einem RecyclerView.
  • Funktionsweise von Adapter, ViewHolder und DiffUtil.

Lerninhalte

  • Mit der Glide-Bibliothek ein Bild aus einer Web-URL laden und anzeigen
  • RecyclerView- und Rasteradapter für Rasterbilder verwenden
  • Umgang mit möglichen Fehlern beim Herunterladen und Anzeigen von Bildern

Aufgabe

  • Ändern Sie die MarsRealEstate-App, um die Bild-URL aus den Mars-Property-Daten zu erhalten. Mit Glide können Sie das Bild laden und anzeigen.
  • Ladesymbol und Fehlersymbol zur App hinzufügen
  • Verwenden Sie einen RecyclerView, um ein Raster der Mars-Property-Bilder anzuzeigen.
  • Fügen Sie der RecyclerView Status- und Fehlerbehandlung hinzu.

In diesem Codelab und den zugehörigen Codelabs arbeiten Sie mit einer App namens MarsRealEstate, die Eigenschaften zum Verkauf auf Mars zeigt. Die App stellt eine Verbindung zu einem Internetserver her, um Unterkunftsdaten abzurufen und anzuzeigen, einschließlich Details wie dem Preis und ob die Immobilie zum Verkauf oder zur Vermietung verfügbar ist. Die Bilder, die die einzelnen Gebäude repräsentieren, sind echte Fotos des Mars, die von den Mars-Rovern der NASA aufgenommen wurden.

Die Version der Anwendung, die Sie in diesem Codelab erstellen, füllt die Übersichtsseite aus, auf der ein Raster mit Bildern angezeigt wird. Die Bilder sind Teil der Property-Daten, die Ihre App vom Webdienst für Mars-Immobilien erhält. Ihre App verwendet die Glide-Bibliothek zum Laden und Anzeigen der Bilder und eine RecyclerView zum Erstellen des Raster-Layouts für die Bilder. Außerdem können Netzwerkfehler in Ihrer App problemlos behoben werden.

Das Anzeigen eines Fotos über eine Web-URL kann auf den ersten Blick verständlich aussehen. Trotzdem gibt es einige technische Änderungen, die erforderlich sind, um das Foto anzuzeigen. Es muss aus einem komprimierten Format heruntergeladen, zwischengespeichert und decodiert werden. Dann kann es das Image verwenden, das Android verwenden kann. Das Image sollte in einem In-Memory-Cache oder in einem speicherbasierten Cache gespeichert werden. All das muss in Hintergrundthreads mit niedriger Priorität passieren, damit die Benutzeroberfläche schnell reagiert. Für eine optimale Netzwerk- und CPU-Leistung sollten Sie auch mehrere Images gleichzeitig abrufen und decodieren. Das Laden von Bildern aus dem Netzwerk kann ein Codelab sein.

Sie können aber auch eine von der Community entwickelte Bibliothek namens Glide verwenden, um Bilder herunterzuladen, zu puffern, zu decodieren und im Cache zu speichern. Mit Glide musst du viel weniger Arbeit machen, als wenn du alles von Grund auf selbst machen müsstest.

Glide benötigt im Wesentlichen zwei Dinge:

  • Die URL des Bildes, das Sie laden und anzeigen möchten.
  • Ein ImageView-Objekt zum Anzeigen dieses Bildes

In dieser Aufgabe erfahren Sie, wie Sie mit Glide ein einzelnes Bild des Immobiliendiensts präsentieren. Sie sehen das Bild, das die erste Mars-Property darstellt, in der Liste der Properties, die der Webdienst zurückgibt. Hier sind die Screenshots vor und nach der Änderung:

Schritt 1: Glide-Abhängigkeit hinzufügen

  1. Öffnen Sie die MarsRealEstate App im letzten Codelab. (Du kannst MarsRealEstateNetworkhier herunterladen, wenn du die App nicht hast.)
  2. Führen Sie die App aus, um ihre Funktion zu sehen. (Hier sehen Sie Textdetails einer Unterkunft, die auf dem Mars hypothetisch verfügbar ist.)
  3. Öffnen Sie build.gradle (Modul: App).
  4. Fügen Sie im Abschnitt dependencies die folgende Zeile für die Glide-Bibliothek hinzu:
implementation "com.github.bumptech.glide:glide:$version_glide"


Die Versionsnummer ist in der Gradle-Datei des Projekts bereits separat definiert.

  1. Klicken Sie auf Jetzt synchronisieren, um das Projekt mit der neuen Abhängigkeit neu zu erstellen.

Schritt 2: Ansichtsmodell aktualisieren

Als Nächstes aktualisiere die Klasse OverviewViewModel, um Live-Daten für eine einzelne Mars-Property einzuschließen.

  1. Öffnen Sie overview/OverviewViewModel.kt. Fügen Sie unterhalb des LiveData für die _response sowohl interne (änderbare) als auch externe (unveränderbare) Live-Daten für ein einzelnes MarsProperty-Objekt hinzu.

    Importieren Sie die Klasse MarsProperty (com.example.android.marsrealestate.network.MarsProperty), wenn Sie dazu aufgefordert werden.
private val _property = MutableLiveData<MarsProperty>()

val property: LiveData<MarsProperty>
   get() = _property
  1. Suchen Sie mit der getMarsRealEstateProperties()-Methode nach der Zeile innerhalb des try/catch {}-Blocks, mit der _response.value auf die Anzahl der Unterkünfte festgelegt wird. Fügen Sie den unten angezeigten Test hinzu. Wenn MarsProperty-Objekte verfügbar sind, wird bei diesem Test der Wert von _property LiveData auf die erste Property im listResult festgelegt.
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}"
 }
  1. Öffnen Sie die Datei res/layout/fragment_overview.xml. Ändern Sie im <TextView>-Element android:text so, dass es an die Komponente imgSrcUrl der property-LiveData gebunden ist:
android:text="@{viewModel.property.imgSrcUrl}"
  1. Führen Sie die App aus. Mit TextView wird nur die URL des Bildes in der ersten Mars-Property angezeigt. Sie haben bereits das Ansichtsmodell und die Live-Daten für diese URL eingerichtet.

Schritt 3: Bindungsadapter erstellen und Glide aufrufen

Sie haben jetzt die URL eines Bilds, das Sie anzeigen können. Mit der Glide-Ladefunktion können Sie dieses Bild jetzt laden. In diesem Schritt verwenden Sie einen Bindungsadapter, um die URL aus einem XML-Attribut abzurufen, das mit einem ImageView verknüpft ist. Und Sie verwenden Glide, um das Bild zu laden. Bindungsadapter sind Erweiterungsmethoden, die zwischen einer Ansicht und gebundenen Daten liegen, um ein benutzerdefiniertes Verhalten bereitzustellen, wenn sich die Daten ändern. In diesem Fall ist das benutzerdefinierte Verhalten, Glide aufzurufen, damit ein Bild von einer URL in eine ImageView geladen wird.

  1. Öffnen Sie BindingAdapters.kt. Diese Datei enthält die Bindungsadapter, die Sie in der gesamten App verwenden.
  2. Erstelle eine bindImage()-Funktion, die eine ImageView und eine String als Parameter verwendet. Funktion mit @BindingAdapter annotieren Die Annotation @BindingAdapter teilt der Datenbindung mit, dass dieser Bindungsadapter ausgeführt werden soll, wenn ein XML-Element das Attribut imageUrl hat.

    Importieren Sie androidx.databinding.BindingAdapter und android.widget.ImageView, wenn Sie dazu aufgefordert werden.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {

}
  1. Fügen Sie in der Funktion bindImage() einen Block let {} für das Argument imgUrl hinzu:
imgUrl?.let { 
}
  1. Fügen Sie im Block let {} die unten gezeigte Zeile ein, um den URL-String (aus dem XML-Code) in ein Uri-Objekt zu konvertieren. Importieren Sie androidx.core.net.toUri auf Anforderung.

    Das endgültige Uri-Objekt sollte das HTTPS-Schema verwenden, weil der Server, von dem Sie die Bilder abrufen, dieses Schema benötigt. Wenn Sie das HTTPS-Schema verwenden möchten, hängen Sie buildUpon.scheme("https") an den toUri-Builder an. Die toUri()-Methode ist eine Kotlin-Erweiterungsfunktion aus der Android KTX-Kernbibliothek. Sie sieht also so aus, als wäre sie zur String-Klasse.
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
  1. Rufen Sie noch innerhalb von let {} Glide.with() auf, um das Bild aus dem Uri-Objekt in ImageView zu laden. Importieren Sie com.bumptech.glide.Glide, wenn Sie dazu aufgefordert werden.
Glide.with(imgView.context)
       .load(imgUri)
       .into(imgView)

Schritt 4: Layout und Fragmente aktualisieren

Obwohl Glide das Bild geladen hat, ist es noch nichts zu sehen. Als Nächstes musst du das Layout und die Fragmente mit einem ImageView aktualisieren, um das Bild anzeigen zu lassen.

  1. Öffnen Sie res/layout/gridview_item.xml. Dies ist die Layoutressourcendatei, die du für jedes Element in der RecyclerView später im Codelab verwendest. Hier wird vorübergehend nur das eine Bild angezeigt.
  2. Fügen Sie über dem Element <ImageView> ein <data>-Element für die Datenbindung hinzu und binden Sie es an die Klasse OverviewViewModel:
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>
  1. Fügen Sie dem Element ImageView ein app:imageUrl-Attribut hinzu, um den neuen Bindungsadapter für das Laden von Bildern zu verwenden:
app:imageUrl="@{viewModel.property.imgSrcUrl}"
  1. Öffnen Sie overview/OverviewFragment.kt. Kommentieren Sie in der Methode onCreateView() die Zeile aus, in der die Klasse FragmentOverviewBinding aufgebläht wird, und weisen Sie sie der Bindungsvariablen zu. Dies ist nur vorübergehend. Du kannst später noch einmal darauf zugreifen.
//val binding = FragmentOverviewBinding.inflate(inflater)
  1. Fügen Sie eine Zeile hinzu, um stattdessen die Klasse GridViewItemBinding zu erhöhen. Importieren Sie com.example.android.marsrealestate. databinding.GridViewItemBinding, wenn Sie dazu aufgefordert werden.
val binding = GridViewItemBinding.inflate(inflater)
  1. Führe die App aus. Nun sollte das Foto des ersten MarsProperty in der Ergebnisliste zu sehen sein.

Schritt 5: Einfache Lade- und Fehlerbilder hinzufügen

Glide verbessert die Nutzererfahrung, da beim Laden des Bildes ein Platzhalterbild und bei einem Fehler das Fehlerbild angezeigt wird, etwa wenn das Bild fehlt oder beschädigt ist. In diesem Schritt fügen Sie diese Funktion dem Bindungsadapter und dem Layout hinzu.

  1. Öffnen Sie res/drawable/ic_broken_image.xml und klicken Sie rechts auf den Tab Vorschau. Für das Fehlerbild verwenden Sie das Symbol für das fehlerhafte Bild, das in der integrierten Symbolbibliothek verfügbar ist. Diese Vektorgrafik kann mit dem Attribut „android:tint“ grau dargestellt werden.

  1. Öffnen Sie res/drawable/loading_animation.xml. Diese Auszeichnung ist eine Animation, die mit dem <animate-rotate>-Tag definiert wird. Die Animation dreht das Bild, d. h., loading_img.xml um den Mittelpunkt. Die Animation wird nicht in der Vorschau angezeigt.

  1. Kehren Sie zur Datei BindingAdapters.kt zurück. Aktualisieren Sie in der bindImage()-Methode den Aufruf von Glide.with(), um die Funktion apply() zwischen load() und into() aufzurufen. Importieren Sie com.bumptech.glide.request.RequestOptions, wenn Sie dazu aufgefordert werden.

    Mit diesem Code wird das Platzhalterbild für das Laden festgelegt, das beim Laden verwendet wird (die loading_animation-Leiste). Mit dem Code wird auch ein Bild festgelegt, das verwendet werden soll, wenn beim Laden des Bildes ein Fehler auftritt (die broken_image-Leiste). Die vollständige Methode bindImage() 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)
    }
}
  1. Führen Sie die App aus. Abhängig von der Geschwindigkeit Ihrer Netzwerkverbindung kann das Ladebild kurz angezeigt werden, während Glide die Property herunterlädt und anzeigt. Allerdings sehen Sie das Symbol für das defekte Bild noch nicht, selbst wenn Sie das Netzwerk deaktivieren. Sie korrigieren das 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 eine LiveData-Property im Darstellungsmodell erstellt und die Bild-URL aus diesen Property-Daten verwendet, um eine ImageView zu füllen. Da Ihre App jedoch ein Raster von Bildern enthalten soll, verwenden Sie RecyclerView mit GridLayoutManager.

Schritt 1: Modell der Datenansicht aktualisieren

Momentan hat das Ansichtsmodell ein _property-LiveData, das ein MarsProperty-Objekt enthält – das erste in der Antwortliste des Webdiensts. In diesem Schritt ändern Sie LiveData, sodass die gesamte Liste der MarsProperty-Objekte enthalten ist.

  1. Öffnen Sie overview/OverviewViewModel.kt.
  2. Ändern Sie die private _property-Variable zu _properties. Ändern Sie den Typ in eine Liste von MarsProperty-Objekten.
private val _properties = MutableLiveData<List<MarsProperty>>()
  1. Ersetze die externen Live-Daten property durch properties. Fügen Sie die Liste hier auch dem Typ LiveData hinzu:
 val properties: LiveData<List<MarsProperty>>
        get() = _properties
  1. Scrollen Sie nach unten zur Methode getMarsRealEstateProperties(). Ersetzen Sie im try {}-Block den gesamten Test, den Sie in der vorherigen Aufgabe hinzugefügt haben, durch die unten gezeigte Zeile. Weil die Variable listResult eine Liste von MarsProperty-Objekten enthält, können Sie sie einfach _properties.value zuweisen, anstatt eine erfolgreiche Antwort zu testen.
_properties.value = listResult

Der 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

Als Nächstes ändern Sie das Layout und die Fragmente der App, um eine Recycler- und eine Rasteransicht anstelle der einzelnen Bildansicht zu verwenden.

  1. Öffnen Sie res/layout/gridview_item.xml. Ändern Sie die Datenbindung von OverviewViewModel zu MarsProperty und benennen Sie die Variable in "property" um.
<variable
   name="property"
   type="com.example.android.marsrealestate.network.MarsProperty" />
  1. Ändern Sie im <ImageView> das Attribut app:imageUrl, um auf die Bild-URL im Objekt MarsProperty zu verweisen:
app:imageUrl="@{property.imgSrcUrl}"
  1. Öffnen Sie overview/OverviewFragment.kt. Heben Sie in onCreateview() die Kommentarzeile auf, die FragmentOverviewBinding aufgebläht. Löschen oder kommentieren Sie die Zeile, die GridViewBinding aufgebläht. Dadurch werden die in der letzten Aufgabe vorgenommenen Änderungen rückgängig gemacht.
val binding = FragmentOverviewBinding.inflate(inflater)
 // val binding = GridViewItemBinding.inflate(inflater)
  1. Öffnen Sie res/layout/fragment_overview.xml. Das gesamte <TextView>-Element löschen.
  2. Füge stattdessen dieses <RecyclerView>-Element hinzu, das ein GridLayoutManager- und das grid_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: Fotorasteradapter hinzufügen

Jetzt hat das fragment_overview-Layout ein RecyclerView, das grid_view_item-Layout einen einzelnen ImageView. In diesem Schritt binden Sie die Daten über einen RecyclerView-Adapter an die RecyclerView.

  1. Öffnen Sie overview/PhotoGridAdapter.kt.
  2. Erstelle die PhotoGridAdapter-Klasse mit den unten aufgeführten Konstruktorparametern. Die Klasse PhotoGridAdapter erweitert ListAdapter, dessen Konstruktor den Listenelementtyp, den Ansichtsinhaber und eine DiffUtil.ItemCallback-Implementierung benötigt.

    Importieren Sie die Klassen androidx.recyclerview.widget.ListAdapter und com.example.android.marsrealestate.network.MarsProperty, wenn Sie dazu aufgefordert werden. Mit den folgenden Schritten implementierst du die anderen fehlenden Teile dieses Konstruktors, die Fehler verursachen.
class PhotoGridAdapter : ListAdapter<MarsProperty,
        PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {

}
  1. Klicken Sie auf eine beliebige Stelle in der Klasse PhotoGridAdapter und drücken Sie Control+i, um die ListAdapter-Methoden zu implementieren. Dies sind onCreateViewHolder() und onBindViewHolder().
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoGridAdapter.MarsPropertyViewHolder {
   TODO("not implemented") 
}

override fun onBindViewHolder(holder: PhotoGridAdapter.MarsPropertyViewHolder, position: Int) {
   TODO("not implemented") 
}
  1. Fügen Sie am Ende der Klassendefinition PhotoGridAdapter nach den gerade hinzugefügten Methoden eine Companion-Objektdefinition für DiffCallback hinzu (siehe unten).

    Importieren Sie androidx.recyclerview.widget.DiffUtil, wenn Sie dazu aufgefordert werden.

    Das Objekt DiffCallback erweitert DiffUtil.ItemCallback um den zu vergleichenden Objekttyp: MarsProperty.
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}
  1. Drücke Control+i, um die Vergleichsmethoden für dieses Objekt zu implementieren. Dies sind areItemsTheSame() und areContentsTheSame().
override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
   TODO("not implemented") 
}

override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
   TODO("not implemented") }
  1. Entferne die TODO-Methode für die areItemsTheSame()-Methode. Verwenden Sie den Kotlin-Operator „<reference equality“ (===), der true zurückgibt, wenn die Objekte, auf die oldItem verweist, und newItem identisch sind.
override fun areItemsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem === newItem
}
  1. Für areContentsTheSame() verwenden Sie den standardmäßigen Gleichheitsoperator nur für die ID von oldItem und newItem.
override fun areContentsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem.id == newItem.id
}
  1. Fügen Sie außerdem innerhalb der Klasse PhotoGridAdapter unter dem Companion-Objekt eine innere Klassendefinition für MarsPropertyViewHolder hinzu, die RecyclerView.ViewHolder verlängert.

    Importieren Sie androidx.recyclerview.widget.RecyclerView und com.example.android.marsrealestate.databinding.GridViewItemBinding, wenn Sie dazu aufgefordert werden.

    Sie benötigen die Variable GridViewItemBinding, um die MarsProperty an das Layout zu binden. Übergeben Sie daher die Variable an MarsPropertyViewHolder. Weil die Basisklasse ViewHolder eine Ansicht in ihrem Konstruktor erfordert, wird sie an die Bindungs-Stammansicht übergeben.
class MarsPropertyViewHolder(private var binding: 
                   GridViewItemBinding):
       RecyclerView.ViewHolder(binding.root) {

}
  1. Erstelle in MarsPropertyViewHolder eine bind()-Methode, mit der ein MarsProperty-Objekt als Argument verwendet wird und binding.property auf dieses Objekt festgelegt wird. Rufen Sie executePendingBindings() auf, nachdem Sie die Eigenschaft festgelegt haben. Dadurch wird die Aktualisierung sofort ausgeführt.
fun bind(marsProperty: MarsProperty) {
   binding.property = marsProperty
   binding.executePendingBindings()
}
  1. Entfernen Sie in onCreateViewHolder() die TODO-Aufgabe und fügen Sie die unten gezeigte Zeile hinzu. Importieren Sie android.view.LayoutInflater, wenn Sie dazu aufgefordert werden.

    Die onCreateViewHolder()-Methode muss ein neues MarsPropertyViewHolder zurückgeben, das erstellt wurde, indem der GridViewItemBinding aufgebläht und der LayoutInflater aus dem übergeordneten ViewGroup-Kontext verwendet wurde.
   return MarsPropertyViewHolder(GridViewItemBinding.inflate(
      LayoutInflater.from(parent.context)))
  1. Entfernen Sie in der onBindViewHolder()-Methode die TODO-Eingabe und fügen Sie die unten angezeigten Zeilen hinzu. Hier rufen Sie getItem() auf, um das MarsProperty-Objekt abzurufen, das mit der aktuellen RecyclerView-Position verknüpft ist, und diese Property dann an die bind()-Methode in der MarsPropertyViewHolder zu übergeben.
val marsProperty = getItem(position)
holder.bind(marsProperty)

Schritt 4: Bindungsadapter hinzufügen und Teile verbinden

Abschließend verwenden Sie einen BindingAdapter, um den PhotoGridAdapter mit der Liste der MarsProperty-Objekte zu initialisieren. Mit einem BindingAdapter, um die RecyclerView Daten festzulegen, führt die Datenbindung dazu, dass LiveData automatisch die Liste der MarsProperty Objekte überwacht. Der Bindungsadapter wird automatisch aufgerufen, wenn sich die Liste MarsProperty ändert.

  1. Öffnen Sie BindingAdapters.kt.
  2. Fügen Sie am Ende der Datei eine bindRecyclerView()-Methode hinzu, in der ein RecyclerView-Objekt und eine Liste von MarsProperty-Objekten als Argumente verwendet werden. Annotieren Sie diese Methode mit einer @BindingAdapter.

    Importieren Sie androidx.recyclerview.widget.RecyclerView und com.example.android.marsrealestate.network.MarsProperty, wenn Sie dazu aufgefordert werden.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, 
    data: List<MarsProperty>?) {
}
  1. Führen Sie in der Funktion bindRecyclerView() die Option recyclerView.adapter in PhotoGridAdapter aus und rufen Sie adapter.submitList() mit den Daten auf. Dadurch wird RecyclerView mitgeteilt, 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)
  1. Öffnen Sie res/layout/fragment_overview.xml. Fügen Sie dem Element RecyclerView das Attribut app:listData hinzu und setzen Sie es mit Datenbindung auf viewmodel.properties.
app:listData="@{viewModel.properties}"
  1. Öffnen Sie overview/OverviewFragment.kt. In onCreateView(), kurz vor dem Aufruf von setHasOptionsMenu(), initialisieren Sie den RecyclerView-Adapter in binding.photosGrid mit einem neuen PhotoGridAdapter-Objekt.
binding.photosGrid.adapter = PhotoGridAdapter()
  1. Führen Sie die App aus. Sie sollten ein Raster mit MarsProperty Bildern sehen. Während Sie scrollen, um neue Bilder zu sehen, zeigt die App das Ladesymbol an, bevor das Bild selbst angezeigt wird. Wenn Sie den Flugmodus aktivieren, werden Bilder, die noch nicht geladen wurden, als Symbole für fehlerhafte Bilder angezeigt.

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

Das ist ein tolles Erlebnis. In dieser Aufgabe fügen Sie eine grundlegende Fehlerbehandlung hinzu, um dem Nutzer einen besseren Überblick über das Problem zu geben. Wenn das Internet nicht verfügbar ist, wird in der App das Symbol für den Verbindungsfehler angezeigt. Während die App die Liste MarsProperty abruft, zeigt die App die Ladeanimation an.

Schritt 1: Status zum Ansichtsmodell hinzufügen

Zuerst erstellen Sie eine LiveData im Ansichtsmodell, um den Status der Webanfrage darzustellen. Es gibt drei Status: Laden, Erfolg und Fehler. Der Ladestatus wird durchgeführt, während du beim Anruf an await() auf Daten wartest.

  1. Öffnen Sie overview/OverviewViewModel.kt. Fügen Sie oben in der Datei (nach den Importen vor der Klassendefinition) ein enum hinzu, das alle verfügbaren Status darstellt:
enum class MarsApiStatus { LOADING, ERROR, DONE }
  1. Benennen Sie sowohl die interne als auch die externe Live-Definition des _response in der Klasse OverviewViewModel in _status um. Da du bereits die Unterstützung für _properties LiveData in diesem Codelab hinzugefügt hast, wurde die vollständige Webdienstantwort nicht verwendet. Sie benötigen hier ein LiveData-Element, um den aktuellen Status zu verfolgen und einfach die vorhandenen Variablen umzubenennen.

Auch die Typen von String in MarsApiStatus. ändern

private val _status = MutableLiveData<MarsApiStatus>()

val status: LiveData<MarsApiStatus>
   get() = _status
  1. Scrollen Sie nach unten zur getMarsRealEstateProperties()-Methode und aktualisieren Sie _response auch in _status. Ändern Sie den String "Success" in den Status MarsApiStatus.DONE und den String "Failure" in MarsApiStatus.ERROR.
  2. Fügen Sie oben im try {}-Block einen MarsApiStatus.LOADING-Status vor dem Aufruf von await() hinzu. Dies ist der ursprüngliche Status, während die Koroutine ausgeführt wird und du auf Daten wartet. Der vollständige try/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
}
  1. Nach dem Fehlerstatus im catch {}Block setzen Sie _properties LiveData auf eine leere Liste. Dadurch wird der RecyclerView gelöscht.
} catch (e: Exception) {
   _status.value = MarsApiStatus.ERROR
   _properties.value = ArrayList()
}

Schritt 2: Bindungsadapter für den Status „ImageView“ hinzufügen

Sie haben jetzt einen Status im Ansichtsmodell, doch es ist nur eine Reihe von Zuständen. Wie sieht es in der App selbst aus? In diesem Schritt verwenden Sie eine ImageView, die mit Datenbindung verbunden ist, um Symbole für die Lade- und Fehlerstatus anzuzeigen. Wenn sich die App im Lade- oder Fehlerstatus befindet, sollte ImageView angezeigt werden. Wenn die Anwendung vollständig geladen wurde, sollte ImageView unsichtbar sein.

  1. Öffnen Sie BindingAdapters.kt. Fügen Sie einen neuen Bindungsadapter mit dem Namen bindStatus() hinzu, der einen ImageView- und einen MarsApiStatus-Wert als Argumente verwendet. Importieren Sie com.example.android.marsrealestate.overview.MarsApiStatus, wenn Sie dazu aufgefordert werden.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView, 
          status: MarsApiStatus?) {
}
  1. Fügen Sie innerhalb der Methode bindStatus() ein when {} hinzu, um zwischen den verschiedenen Status zu wechseln.
when (status) {

}
  1. Fügen Sie innerhalb von when {} eine Anfrage für den Ladestatus (MarsApiStatus.LOADING) hinzu. Setzen Sie für diesen Status den Wert ImageView auf „sichtbar“ und weisen Sie ihn der Ladeanimation zu. Dies ist dieselbe Animations-Draming, die Sie in der vorherigen Aufgabe für Glide verwendet haben. Importieren Sie android.view.View, wenn Sie dazu aufgefordert werden.
when (status) {
   MarsApiStatus.LOADING -> {
      statusImageView.visibility = View.VISIBLE
      statusImageView.setImageResource(R.drawable.loading_animation)
   }
}
  1. Fügen Sie einen Fall für den Fehlerstatus hinzu: MarsApiStatus.ERROR. Ähnlich wie beim Status LOADING kannst du den Status ImageView so festlegen, dass der Verbindungsfehler sichtbar und wieder sichtbar wird.
MarsApiStatus.ERROR -> {
   statusImageView.visibility = View.VISIBLE
   statusImageView.setImageResource(R.drawable.ic_connection_error)
}
  1. Fügen Sie einen Fall für den Status „Fertig“ hinzu. Er lautet MarsApiStatus.DONE. Hier haben Sie eine erfolgreiche Antwort. Deaktivieren Sie daher die Sichtbarkeit des Status ImageView, um sie auszublenden.
MarsApiStatus.DONE -> {
   statusImageView.visibility = View.GONE
}

Schritt 3: Status „ImageView“ zum Layout hinzufügen

  1. Öffnen Sie res/layout/fragment_overview.xml. Füge unter dem RecyclerView Element innerhalb von ConstraintLayout die unten angezeigten ImageViewhinzu.

    Diese ImageView hat dieselben Beschränkungen wie die RecyclerView. Breite und Höhe nutzen jedoch wrap_content, um das Bild zu zentrieren anstatt es zu strecken. Beachten Sie auch das app:marsApiStatus-Attribut, das den Aufruf Ihrer BindingAdapter aufzeichnet, wenn sich die Status-Property im Datenansichtsmodell ä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}" />
  1. Aktivieren Sie im Emulator oder Gerät den Flugmodus, um eine fehlende Netzwerkverbindung zu simulieren. Wenn Sie die App kompilieren und ausführen, sehen Sie das Fehlerbild:

  1. Tippen Sie auf die Schaltfläche „Zurück“, um die App zu schließen, und deaktivieren Sie den Flugmodus. Verwenden Sie das Fenster, wenn Sie die App aufrufen möchten. Je nachdem, wie schnell Ihre Netzwerkverbindung ist, wird möglicherweise ein extrem kurzer Ladesymbol angezeigt.

Android Studio-Projekt: MarsRealEstateGrid

  • Mit der Glide-Bibliothek können Sie Bilder in Ihrer App herunterladen, zwischenspeichern, decodieren und im Cache speichern, um die Verwaltung von Bildern zu vereinfachen.
  • Glide benötigt zwei Dinge, um ein Bild aus dem Internet zu laden: die URL eines Bilds und ein ImageView-Objekt, in das das Bild eingefügt werden soll. Verwende zum Festlegen dieser Optionen die Methoden load() und into() mit Glide.
  • Bindungsadapter sind Erweiterungsmethoden zwischen einer Ansicht und den gebundenen Daten. Bindungsadapter bieten ein benutzerdefiniertes Verhalten, wenn sich die Daten ändern, z. B. um Glide aufzurufen, um ein Bild von einer URL in eine ImageView zu laden.
  • Bindungsadapter sind Erweiterungsmethoden, die mit der Annotation @BindingAdapter versehen sind.
  • Zum Hinzufügen von Optionen zur Glide-Anfrage verwenden Sie die Methode apply(). Beispiel: Sie können apply() zusammen mit placeholder() verwenden, um eine Ladedatei anzugeben. Wenn Sie apply() verwenden, geben Sie eine error()-Zeichenleiste an.
  • Wenn du ein Raster aus Bildern erstellen möchtest, verwende ein RecyclerView mit einem GridLayoutManager.
  • Wenn Sie die Liste der Unterkünfte ändern möchten, verwenden Sie einen Bindungsadapter zwischen RecyclerView und dem Layout.

Udacity-Kurs:

Android-Entwicklerdokumentation:

Sonstiges:

In diesem Abschnitt werden mögliche Hausaufgaben für Schüler oder Studenten aufgeführt, die an diesem von einem Kursleiter geleiteten Codelab arbeiten. Die Lehrkraft kann Folgendes tun:

  • Bei Bedarf können Sie die entsprechenden Aufgaben zuweisen.
  • Schülern mitteilen, wie sie Aufgaben für die Aufgabe abgeben
  • Benoten Sie die Hausaufgaben.

Lehrkräfte können diese Vorschläge so oft oder so oft verwenden, wie sie möchten. anderen Aufgaben können sie nach Belieben zugewiesen werden.

Wenn Sie alleine an diesem Codelab arbeiten, können Sie Ihr Wissen mit diesen Hausaufgaben testen.

Diese Fragen beantworten

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 angezeigt wird, wenn Glide geladen wird?

▢ Nutze die Methode into() mit einer Schublade.

▢ Verwende RequestOptions() und rufe die placeholder()-Methode mit einer Schublade auf.

▢ Ziehe die Property Glide.placeholder einer Drawable zu.

▢ Verwende RequestOptions() und rufe die loadingImage()-Methode mit einer Schublade auf.

Frage 3

Wie geben Sie an, dass eine Methode ein Bindungsadapter ist?

▢ Rufe die Methode setBindingAdapter() in LiveData auf.

▢ Lege die Methode in eine Kotlin-Datei namens BindingAdapters.kt ein.

▢ Sie verwenden das Attribut android:adapter im XML-Layout.

▢ Führt die Methode mit @BindingAdapter an.

Beginnen Sie mit der nächsten Lektion: 8.3 Filtern und Detailansichten mit Internetdaten

Links zu anderen Codelabs in diesem Kurs finden Sie auf der Landingpage zu Kotlin-Grundlagen für Android.