Grundlagen von Android und Kotlin 08.3: Filtern und Detailansichten mit Internetdaten

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

In den vorherigen Codelabs für diese Lektion haben Sie gelernt, wie Sie Daten zu Immobilien auf dem Mars von einem Webdienst abrufen und wie Sie ein RecyclerView mit einem Rasterlayout erstellen, um Bilder aus diesen Daten zu laden und anzuzeigen. In diesem Codelab vervollständigen Sie die MarsRealEstate-App, indem Sie die Möglichkeit implementieren, die Mars-Immobilien danach zu filtern, ob sie gemietet oder gekauft werden können. Außerdem erstellen Sie eine Detailansicht, damit Nutzer, wenn sie in der Übersicht auf ein Immobilienfoto tippen, eine Detailansicht mit Informationen zu dieser Immobilie sehen.

Was Sie bereits wissen sollten

  • Fragmente erstellen und verwenden
  • Hier erfahren Sie, wie Sie zwischen Fragmenten wechseln und Safe Args (ein Gradle-Plug-in) verwenden, um Daten zwischen Fragmenten zu übergeben.
  • Verwendung von Architekturkomponenten wie Viewmodels, Viewmodelfactories, Transformationen und LiveData.
  • Hier erfahren Sie, wie Sie JSON-codierte Daten von einem REST-Webdienst abrufen und mit den Bibliotheken Retrofit und Moshi in Kotlin-Objekte parsen.

Lerninhalte

  • So verwenden Sie komplexe Bindungsausdrücke in Ihren Layoutdateien.
  • Retrofit-Anfragen mit Abfrageoptionen an einen Webdienst senden

Aufgabe

  • Ändern Sie die MarsRealEstate-App so, dass die zum Verkauf stehenden Mars-Grundstücke (im Gegensatz zu den zu vermietenden) mit einem Dollarzeichen gekennzeichnet werden.
  • Über das Optionsmenü auf der Übersichtsseite können Sie eine Webdienstanfrage erstellen, mit der die Mars-Eigenschaften nach Typ gefiltert werden.
  • Erstellen Sie ein Detailfragment für eine Mars-Property, verknüpfen Sie es über die Navigation mit dem Übersichts-Grid und übergeben Sie die Property-Daten an das Fragment.

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. Diese App stellt eine Verbindung zu einem Internetserver her, um Property-Daten 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. In früheren Codelabs haben Sie ein RecyclerView mit einem Rasterlayout für alle Immobilienfotos erstellt:

In dieser Version der App arbeiten Sie mit dem Typ der Immobilie (Miete oder Kauf) und fügen dem Rasterlayout ein Symbol hinzu, um zum Verkauf stehende Immobilien zu kennzeichnen:

Sie ändern das Optionsmenü der App, um das Raster so zu filtern, dass nur die Immobilien angezeigt werden, die zur Miete oder zum Verkauf stehen:

Schließlich erstellen Sie eine Detailansicht für eine einzelne Property und verbinden die Symbole im Übersichts-Grid über die Navigation mit diesem Detailfragment:

Bisher haben Sie nur die URL für das Bild der Mars-Property verwendet. Die Property-Daten, die Sie in der Klasse MarsProperty definiert haben, enthalten aber auch eine ID, einen Preis und einen Typ (Miete oder Verkauf). Zur Erinnerung hier ein Ausschnitt der JSON-Daten, die Sie vom Webdienst erhalten:

{
   "price":8000000,
   "id":"424908",
   "type":"rent",
   "img_src": "http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631290305226E03_DXXX.jpg"
},

In dieser Aufgabe arbeiten Sie mit dem Property-Typ „Mars“, um den zum Verkauf stehenden Properties auf der Übersichtsseite ein Dollarzeichen hinzuzufügen.

Schritt 1: MarsProperty aktualisieren, um den Typ einzuschließen

Die Klasse MarsProperty definiert die Datenstruktur für jede Property, die vom Webdienst bereitgestellt wird. In einem früheren Codelab haben Sie die Moshi-Bibliothek verwendet, um die Roh-JSON-Antwort des Mars-Webdienstes in einzelne MarsProperty-Datenobjekte zu parsen.

In diesem Schritt fügen Sie der Klasse MarsProperty etwas Logik hinzu, um anzugeben, ob eine Unterkunft vermietet werden kann oder nicht (d. h. ob der Typ der String "rent" oder "buy" ist). Sie verwenden diese Logik an mehreren Stellen. Daher ist es besser, sie hier in der Datenklasse zu haben, als sie zu replizieren.

  1. Öffnen Sie die MarsRealEstate-App aus dem letzten Codelab. Wenn Sie die App nicht haben, können Sie MarsRealEstateGrid herunterladen.
  2. Öffnen Sie network/MarsProperty.kt. Fügen Sie der Klassendefinition MarsProperty einen Textkörper hinzu und fügen Sie einen benutzerdefinierten Getter für isRental hinzu, der true zurückgibt, wenn das Objekt vom Typ "rent" ist.
data class MarsProperty(
       val id: String,
       @Json(name = "img_src") val imgSrcUrl: String,
       val type: String,
       val price: Double)  {
   val isRental
       get() = type == "rent"
}

Schritt 2: Layout des Rasterelements aktualisieren

Aktualisieren Sie nun das Artikellayout für das Raster mit Bildern, damit das Dollarzeichen-Drawable nur bei den Immobilienbildern angezeigt wird, die zum Verkauf stehen:

Mit Data Binding-Ausdrücken können Sie diesen Test vollständig im XML-Layout für die Grid-Elemente durchführen.

  1. Öffnen Sie res/layout/grid_view_item.xml. Dies ist die Layoutdatei für jede einzelne Zelle im Rasterlayout für RecyclerView. Derzeit enthält die Datei nur das <ImageView>-Element für das Property-Bild.
  2. Fügen Sie im <data>-Element ein <import>-Element für die Klasse View hinzu. Sie verwenden Importe, wenn Sie Komponenten einer Klasse in einem Data-Binding-Ausdruck in einer Layoutdatei verwenden möchten. In diesem Fall verwenden Sie die Konstanten View.GONE und View.VISIBLE. Daher benötigen Sie Zugriff auf die Klasse View.
<import type="android.view.View"/>
  1. Umschließen Sie die gesamte Bildansicht mit einem FrameLayout, damit das Dollarzeichen-Drawable über dem Property-Bild gestapelt werden kann.
<FrameLayout
   android:layout_width="match_parent"
   android:layout_height="170dp">
             <ImageView 
                    android:id="@+id/mars_image"
            ...
</FrameLayout>
  1. Ändern Sie für ImageView das Attribut android:layout_height in match_parent, um das neue übergeordnete Element FrameLayout zu füllen.
android:layout_height="match_parent"
  1. Fügen Sie ein zweites <ImageView>-Element direkt unter dem ersten innerhalb des FrameLayout ein. Verwenden Sie die unten gezeigte Definition. Dieses Bild wird rechts unten im Rasterelement über dem Marsbild angezeigt. Für das Dollarzeichen-Symbol wird das in res/drawable/ic_for_sale_outline.xml definierte Drawable verwendet.
<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"/>
  1. Fügen Sie das Attribut android:visibility der Bildansicht mars_property_type hinzu. Verwende einen Bindungsausdruck, um den Attributtyp zu testen, und weise die Sichtbarkeit entweder View.GONE (für eine Leihgebühr) oder View.VISIBLE (für einen Kauf) zu.
 android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"

Bisher haben Sie Bindungsausdrücke nur in Layouts gesehen, in denen einzelne Variablen verwendet werden, die im <data>-Element definiert sind. Bindungsausdrücke sind sehr leistungsstark und ermöglichen es Ihnen, Vorgänge wie Tests und mathematische Berechnungen vollständig in Ihrem XML-Layout auszuführen. In diesem Fall verwenden Sie den ternären Operator (?:), um einen Test durchzuführen (Ist dieses Objekt ein Leihartikel?). Sie geben ein Ergebnis für „true“ (das Dollarzeichensymbol mit View.GONE ausblenden) und ein anderes für „false“ (das Symbol mit View.VISIBLE einblenden) an.

Die neue vollständige grid_view_item.xml-Datei sieht so aus:

<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. Kompilieren Sie die App, führen Sie sie aus und beachten Sie, dass für Properties, die keine Mietobjekte sind, das Dollarzeichen-Symbol angezeigt wird.

Derzeit werden in Ihrer App alle Mars-Produkte im Übersichtsraster angezeigt. Wenn ein Nutzer auf dem Mars nach einer Mietunterkunft sucht, sind die Symbole, die angeben, welche der verfügbaren Unterkünfte zum Verkauf stehen, zwar nützlich, aber es gibt immer noch viele Unterkünfte, durch die er auf der Seite scrollen muss. In dieser Aufgabe fügen Sie dem Übersichtsfragment ein Optionsmenü hinzu, mit dem der Nutzer nur Leihartikel, nur zum Verkauf stehende Artikel oder alle Artikel anzeigen lassen kann.

Eine Möglichkeit, diese Aufgabe zu erledigen, besteht darin, den Typ für jedes MarsProperty im Übersichtsraster zu testen und nur die übereinstimmenden Eigenschaften anzuzeigen. Der eigentliche Mars-Webdienst hat jedoch einen Abfrageparameter oder eine Option (filter), mit der Sie nur Eigenschaften vom Typ rent oder buy abrufen können. Sie können diese Filterabfrage mit der realestate-Webdienst-URL in einem Browser verwenden:

https://android-kotlin-fun-mars-server.appspot.com/realestate?filter=buy

In dieser Aufgabe ändern Sie die Klasse MarsApiService, um der Webdienst-Anfrage mit Retrofit eine Abfrageoption hinzuzufügen. Dann verbinden Sie das Optionsmenü, um alle Daten der Mars-Property mit dieser Abfrageoption noch einmal herunterzuladen. Da die Antwort, die Sie vom Webdienst erhalten, nur die Eigenschaften enthält, die Sie interessieren, müssen Sie die Logik für die Ansichtsdarstellung für das Übersichtsfeld überhaupt nicht ändern.

Schritt 1: Mars API-Dienst aktualisieren

Wenn Sie die Anfrage ändern möchten, müssen Sie die MarsApiService-Klasse aufrufen, die Sie im ersten Codelab dieser Reihe implementiert haben. Sie ändern die Klasse, um eine Filter-API bereitzustellen.

  1. Öffnen Sie network/MarsApiService.kt. Erstellen Sie direkt unter den Importen eine enum namens MarsApiFilter, um Konstanten zu definieren, die den Abfragewerten entsprechen, die der Webdienst erwartet.
enum class MarsApiFilter(val value: String) {
   SHOW_RENT("rent"),
   SHOW_BUY("buy"),
   SHOW_ALL("all") }
  1. Ändern Sie die Methode getProperties() so, dass sie eine String-Eingabe für die Filterabfrage akzeptiert, und fügen Sie dieser Eingabe die Annotation @Query("filter") hinzu, wie unten gezeigt.

    Importieren Sie retrofit2.http.Query, wenn Sie dazu aufgefordert werden.

    Die Annotation @Query weist die Methode getProperties() (und damit Retrofit) an, die Webdienstanfrage mit der Filteroption zu stellen. Jedes Mal, wenn getProperties() aufgerufen wird, enthält die Anfrage-URL den Teil ?filter=type, der den Webdienst anweist, mit Ergebnissen zu antworten, die dieser Anfrage entsprechen.
fun getProperties(@Query("filter") type: String):  

Schritt 2: Modell für die Übersicht aktualisieren

Sie fordern Daten aus MarsApiService in der Methode getMarsRealEstateProperties() in OverviewViewModel an. Jetzt müssen Sie diese Anfrage aktualisieren, um das Filterargument zu berücksichtigen.

  1. Öffnen Sie overview/OverviewViewModel.kt. Aufgrund der Änderungen, die Sie im vorherigen Schritt vorgenommen haben, werden in Android Studio Fehler angezeigt. Fügen Sie MarsApiFilter (die Enumeration der möglichen Filterwerte) als Parameter zum getMarsRealEstateProperties()-Aufruf hinzu.

    Importieren Sie com.example.android.marsrealestate.network.MarsApiFilter, wenn Sie dazu aufgefordert werden.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
  1. Ändern Sie den Aufruf von getProperties() im Retrofit-Dienst, um diese Filterabfrage als String zu übergeben.
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)
  1. Übergeben Sie im init {}-Block MarsApiFilter.SHOW_ALL als Argument an getMarsRealEstateProperties(), damit alle Eigenschaften beim ersten Laden der App angezeigt werden.
init {
   getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
  1. Fügen Sie am Ende der Klasse eine updateFilter()-Methode hinzu, die ein MarsApiFilter-Argument akzeptiert und getMarsRealEstateProperties() mit diesem Argument aufruft.
fun updateFilter(filter: MarsApiFilter) {
   getMarsRealEstateProperties(filter)
}

Schritt 3: Fragment mit dem Optionsmenü verbinden

Im letzten Schritt wird das Überlaufmenü mit dem Fragment verknüpft, um updateFilter() für das ViewModel aufzurufen, wenn der Nutzer eine Menüoption auswählt.

  1. Öffnen Sie res/menu/overflow_menu.xml. Die MarsRealEstate-App hat ein vorhandenes Überlaufmenü mit den drei verfügbaren Optionen: alle Immobilien anzeigen, nur Mietobjekte anzeigen und nur zum Verkauf stehende Immobilien anzeigen.
<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>
  1. Öffnen Sie overview/OverviewFragment.kt. Implementieren Sie am Ende der Klasse die Methode onOptionsItemSelected(), um die Auswahl von Menüelementen zu verarbeiten.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} 
  1. Rufen Sie in onOptionsItemSelected() die Methode updateFilter() für das Ansichtsmodell mit dem entsprechenden Filter auf. Verwenden Sie einen Kotlin-when {}-Block, um zwischen den Optionen zu wechseln. Verwenden Sie MarsApiFilter.SHOW_ALL für den Standardfilterwert. Geben Sie true zurück, da Sie das Menüelement verarbeitet haben. Importieren Sie MarsApiFilter (com.example.android.marsrealestate.network.MarsApiFilter), wenn Sie dazu aufgefordert werden. Die vollständige onOptionsItemSelected()-Methode sehen Sie unten.
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
}
  1. Kompilieren und starten Sie die App. Die App startet das erste Übersichts-Grid mit allen Immobilientypen und den zum Verkauf stehenden Immobilien, die mit dem Dollarzeichen gekennzeichnet sind.
  2. Wählen Sie im Optionsmenü Ausleihen aus. Die Properties werden neu geladen und keines der Symbole wird mit dem Dollarzeichen angezeigt. Es werden nur Mietunterkünfte angezeigt. Es kann einige Momente dauern, bis die Anzeige aktualisiert wird und nur die gefilterten Properties angezeigt werden.
  3. Wählen Sie im Optionsmenü Kaufen aus. Die Properties werden neu geladen und alle werden mit dem Dollarzeichen angezeigt. Es werden nur zum Verkauf stehende Immobilien angezeigt.

Sie sehen jetzt ein scrollbares Raster mit Symbolen für Mars-Properties. Es ist an der Zeit, sich die Details anzusehen. In dieser Aufgabe fügen Sie ein Detailfragment hinzu, um die Details einer bestimmten Property anzuzeigen. Im Detailfragment werden ein größeres Bild, der Preis und der Immobilientyp angezeigt, also ob es sich um ein Mietobjekt oder ein Verkaufsobjekt handelt.

Dieses Fragment wird gestartet, wenn der Nutzer auf ein Bild im Übersichtsraster tippt. Dazu müssen Sie den onClick-Listener den RecyclerView-Grid-Elementen hinzufügen und dann zum neuen Fragment navigieren. Sie navigieren, indem Sie eine LiveData-Änderung in der ViewModel auslösen, wie Sie es in diesen Lektionen bereits getan haben. Außerdem verwenden Sie das Safe Args-Plug-in der Navigationskomponente, um die ausgewählten MarsProperty-Informationen vom Übersichts- zum Detailfragment zu übergeben.

Schritt 1: Detailansichtsmodell erstellen und Detaillayout aktualisieren

Ähnlich wie beim Modell und den Fragmenten für die Übersichtsansicht müssen Sie jetzt das Ansichtsmodell und die Layoutdateien für das Detailfragment implementieren.

  1. Öffnen Sie detail/DetailViewModel.kt. So wie netzwerkbezogene Kotlin-Dateien im Ordner network und Übersichtsdateien in overview enthalten sind, enthält der Ordner detail die Dateien, die mit der Detailansicht verknüpft sind. Beachten Sie, dass die Klasse DetailViewModel (derzeit leer) ein marsProperty als Parameter im Konstruktor akzeptiert.
class DetailViewModel( marsProperty: MarsProperty,
                     app: Application) : AndroidViewModel(app) {
}
  1. Fügen Sie in der Klassendefinition LiveData für die ausgewählte Mars-Property hinzu, um diese Informationen in der Detailansicht verfügbar zu machen. Folgen Sie dem üblichen Muster, um eine MutableLiveData zu erstellen, die die MarsProperty enthält, und stellen Sie dann eine unveränderliche öffentliche LiveData-Property bereit.

    Importieren Sie androidx.lifecycle.LiveData und androidx.lifecycle.MutableLiveData, wenn Sie dazu aufgefordert werden.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
   get() = _selectedProperty
  1. Erstellen Sie einen init {}-Block und legen Sie den Wert der ausgewählten Mars-Property mit dem MarsProperty-Objekt aus dem Konstruktor fest.
    init {
        _selectedProperty.value = marsProperty
    }
  1. Öffnen Sie res/layout/fragment_detail.xml und sehen Sie sich die Designansicht an.

    Dies ist die Layoutdatei für das Detailfragment. Es enthält ein ImageView für das große Foto, ein TextView für den Immobilientyp (Vermietung oder Verkauf) und ein TextView für den Preis. Das ConstraintLayout ist in ein ScrollView eingebettet, sodass es automatisch gescrollt wird, wenn die Ansicht zu groß für das Display ist, z. B. wenn der Nutzer sie im Querformat aufruft.
  2. Rufen Sie den Tab Text für das Layout auf. Fügen Sie oben im Layout, direkt vor dem <ScrollView>-Element, ein <data>-Element hinzu, um das Detailansichtsmodell mit dem Layout zu verknüpfen.
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>
  1. Fügen Sie dem Element ImageView das Attribut app:imageUrl hinzu. Legen Sie sie auf den imgSrcUrl der ausgewählten Eigenschaft des Ansichtsmodells fest.

    Der Binding-Adapter, der ein Bild mit Glide lädt, wird hier automatisch verwendet, da dieser Adapter alle app:imageUrl-Attribute überwacht.
 app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"

Schritt 2: Navigation im Übersichtsansichtsmodell definieren

Wenn der Nutzer in der Übersicht auf ein Foto tippt, sollte die Navigation zu einem Fragment ausgelöst werden, in dem Details zum angeklickten Element angezeigt werden.

  1. Öffnen Sie overview/OverviewViewModel.kt. Fügen Sie ein _navigateToSelectedProperty-MutableLiveData-Attribut hinzu und machen Sie es mit einem unveränderlichen LiveData verfügbar.

    Wenn sich dieser LiveData in einen Wert ändert, der nicht null ist, wird die Navigation ausgelöst. (Bald fügen Sie den Code hinzu, um diese Variable zu beobachten und die Navigation auszulösen.)
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
   get() = _navigateToSelectedProperty
  1. Fügen Sie am Ende der Klasse eine displayPropertyDetails()-Methode hinzu, mit der _navigateToSelectedProperty auf die ausgewählte Mars-Property festgelegt wird.
fun displayPropertyDetails(marsProperty: MarsProperty) {
   _navigateToSelectedProperty.value = marsProperty
}
  1. Fügen Sie eine displayPropertyDetailsComplete()-Methode hinzu, die den Wert von _navigateToSelectedProperty auf null setzt. Sie benötigen diesen Wert, um den Navigationsstatus als abgeschlossen zu markieren und zu verhindern, dass die Navigation noch einmal ausgelöst wird, wenn der Nutzer von der Detailansicht zurückkehrt.
fun displayPropertyDetailsComplete() {
   _navigateToSelectedProperty.value = null
}

Schritt 3: Klick-Listener im Grid-Adapter und ‑Fragment einrichten

  1. Öffnen Sie overview/PhotoGridAdapter.kt. Erstellen Sie am Ende des Kurses eine benutzerdefinierte OnClickListener-Klasse, die ein Lambda mit einem marsProperty-Parameter akzeptiert. Definieren Sie in der Klasse eine onClick()-Funktion, die auf den Lambda-Parameter festgelegt ist.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
     fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
  1. Scrollen Sie nach oben zur Klassendefinition für PhotoGridAdapter und fügen Sie dem Konstruktor eine private OnClickListener-Property hinzu.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
       ListAdapter<MarsProperty,              
           PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
  1. Sie können ein Foto anklickbar machen, indem Sie dem Rasterelement in der Methode onBindviewHolder() die onClickListener hinzufügen. Definieren Sie den Klick-Listener zwischen den Aufrufen von getItem() and bind().
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
   val marsProperty = getItem(position)
   holder.itemView.setOnClickListener {
       onClickListener.onClick(marsProperty)
   }
   holder.bind(marsProperty)
}
  1. Öffnen Sie overview/OverviewFragment.kt. Ersetzen Sie in der Methode onCreateView() die Zeile, mit der die Eigenschaft binding.photosGrid.adapter initialisiert wird, durch die unten gezeigte Zeile.

    Mit diesem Code wird das PhotoGridAdapter.onClickListener-Objekt dem PhotoGridAdapter-Konstruktor hinzugefügt und viewModel.displayPropertyDetails() mit dem übergebenen MarsProperty-Objekt aufgerufen. Dadurch wird das LiveData im Ansichtsmodell für die Navigation ausgelöst.
binding.photosGrid.adapter = PhotoGridAdapter(PhotoGridAdapter.OnClickListener {
   viewModel.displayPropertyDetails(it)
})

Schritt 4: Navigationsdiagramm ändern und MarsProperty parcelable machen

Wenn ein Nutzer im Übersichts-Grid auf ein Foto tippt, sollte die App zum Detail-Fragment wechseln und die Details der ausgewählten Mars-Eigenschaft übergeben, damit diese Informationen in der Detailansicht angezeigt werden können.

Derzeit haben Sie einen Klick-Listener von PhotoGridAdapter, um das Tippen zu verarbeiten, und eine Möglichkeit, die Navigation über das View-Modell auszulösen. Es wird aber noch kein MarsProperty-Objekt an das Detailfragment übergeben. Dazu verwenden Sie Safe Args aus der Navigationskomponente.

  1. Öffnen Sie res/navigation/nav_graph.xml. Klicken Sie auf den Tab Text, um den XML-Code für das Navigationsdiagramm aufzurufen.
  2. Fügen Sie dem <fragment>-Element für das Detailfragment das unten gezeigte <argument>-Element hinzu. Dieses Argument mit dem Namen selectedProperty hat den Typ MarsProperty.
<argument
   android:name="selectedProperty"
   app:argType="com.example.android.marsrealestate.network.MarsProperty"
   />
  1. Kompilieren Sie die App. Die Navigation gibt einen Fehler zurück, da MarsProperty nicht parcelable ist. Über die Parcelable-Schnittstelle können Objekte serialisiert werden, sodass die Daten der Objekte zwischen Fragmenten oder Aktivitäten übergeben werden können. Damit die Daten im MarsProperty-Objekt über Safe Args an das Detailfragment übergeben werden können, muss MarsProperty die Parcelable-Schnittstelle implementieren. Die gute Nachricht ist, dass Kotlin eine einfache Abkürzung für die Implementierung dieser Schnittstelle bietet.
  2. Öffnen Sie network/MarsProperty.kt. Fügen Sie der Klassendefinition die Annotation @Parcelize hinzu.

    Importieren Sie kotlinx.android.parcel.Parcelize, wenn Sie dazu aufgefordert werden.

    Die Annotation @Parcelize verwendet die Kotlin-Android-Erweiterungen, um die Methoden in der Schnittstelle Parcelable für diese Klasse automatisch zu implementieren. Sie müssen nichts weiter tun.
@Parcelize
data class MarsProperty (
  1. Ändern Sie die Klassendefinition von MarsProperty, um Parcelable zu erweitern.

    Importieren Sie android.os.Parcelable, wenn Sie dazu aufgefordert werden.

    Die Klassendefinition für MarsProperty sieht jetzt so aus:
@Parcelize
data class MarsProperty (
       val id: String,
       @Json(name = "img_src") val imgSrcUrl: String,
       val type: String,
       val price: Double) : Parcelable {

Schritt 5: Fragmente verbinden

Sie navigieren immer noch nicht – die eigentliche Navigation findet in den Fragmenten statt. In diesem Schritt fügen Sie die letzten Teile für die Implementierung der Navigation zwischen den Übersichts- und Detailfragmenten hinzu.

  1. Öffnen Sie overview/OverviewFragment.kt. Fügen Sie in onCreateView() unter den Zeilen, mit denen der Adapter für das Fotoraster initialisiert wird, die unten gezeigten Zeilen hinzu, um navigatedToSelectedProperty aus dem Übersichtsansichtsmodell zu beobachten.

    Importieren Sie androidx.lifecycle.Observer und androidx.navigation.fragment.findNavController, wenn Sie dazu aufgefordert werden.

    Der Observer prüft, ob MarsProperty – das it im Lambda-Ausdruck – nicht null ist. Wenn dies der Fall ist, ruft er den Navigationscontroller über das Fragment mit findNavController() ab. Rufen Sie displayPropertyDetailsComplete() auf, um das View-Modell anzuweisen, LiveData auf den Nullstatus zurückzusetzen. So wird nicht versehentlich noch einmal die Navigation ausgelöst, wenn die App zur OverviewFragment zurückkehrt.
viewModel.navigateToSelectedProperty.observe(this, Observer {
   if ( null != it ) {   
      this.findNavController().navigate(
              OverviewFragmentDirections.actionShowDetail(it))             
      viewModel.displayPropertyDetailsComplete()
   }
})
  1. Öffnen Sie detail/DetailFragment.kt. Fügen Sie diese Zeile direkt unter dem Aufruf von setLifecycleOwner() in der Methode onCreateView() ein. In dieser Zeile wird das ausgewählte MarsProperty-Objekt aus den Safe Args abgerufen.

    Beachten Sie die Verwendung des Kotlin-Operators für die Zusicherung, dass der Wert nicht null ist (!!). Wenn selectedProperty nicht vorhanden ist, ist etwas Schlimmes passiert und Sie möchten, dass der Code einen Nullzeiger ausgibt. Im Produktionscode sollten Sie diesen Fehler auf irgendeine Weise behandeln.
 val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty
  1. Fügen Sie als Nächstes diese Zeile hinzu, um ein neues DetailViewModelFactory zu erhalten. Mit DetailViewModelFactory erhalten Sie eine Instanz von DetailViewModel. Die Starter-App enthält eine Implementierung von DetailViewModelFactory. Sie müssen sie also nur initialisieren.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
  1. Fügen Sie schließlich diese Linie hinzu, um ein DetailViewModel zu erhalten und alle Teile zu verbinden.
      binding.viewModel = ViewModelProviders.of(
                this, viewModelFactory).get(DetailViewModel::class.java)
  1. Kompilieren Sie die App, führen Sie sie aus und tippen Sie auf ein beliebiges Foto der Mars-Property. Das Detailfragment für die Details der Property wird angezeigt. Tippen Sie auf die Schaltfläche „Zurück“, um zur Übersichtsseite zurückzukehren. Der Detailbildschirm ist immer noch recht spärlich. Im nächsten Schritt fügen Sie dieser Detailseite die Property-Daten hinzu.

Derzeit wird auf der Detailseite nur dasselbe Marsfoto angezeigt, das Sie von der Übersichtsseite kennen. Die Klasse MarsProperty hat auch einen Immobilientyp (Miete oder Kauf) und einen Immobilienpreis. Auf dem Detailbildschirm sollten beide Werte angegeben sein. Außerdem wäre es hilfreich, wenn bei den Mietobjekten angegeben wäre, dass der Preis ein monatlicher Wert ist. Sie verwenden LiveData-Transformationen im Ansichtsmodell, um beides zu implementieren.

  1. Öffnen Sie res/values/strings.xml. Der Startercode enthält die unten gezeigten String-Ressourcen, die Ihnen beim Erstellen der Strings für die Detailansicht helfen. Für den Preis verwenden Sie je nach Attributtyp entweder die Ressource display_price_monthly_rental oder die Ressource 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>
  1. Öffnen Sie detail/DetailViewModel.kt. Fügen Sie am Ende der Klasse den unten gezeigten Code hinzu.

    Importieren Sie androidx.lifecycle.Transformations, falls erforderlich.

    Bei dieser Transformation wird geprüft, ob die ausgewählte Property ein Mietobjekt ist. Dabei wird derselbe Test wie in der ersten Aufgabe verwendet. Wenn es sich bei der Property um eine Mietunterkunft handelt, wird mit der Transformation der entsprechende String aus den Ressourcen mit einem Kotlin-when {}-Switch ausgewählt. Beide Strings benötigen eine Zahl am Ende. Daher wird property.price angehängt.
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)
}
  1. Importieren Sie die generierte R-Klasse, um auf die String-Ressourcen im Projekt zuzugreifen.
import com.example.android.marsrealestate.R
  1. Fügen Sie nach der displayPropertyPrice-Transformation den unten gezeigten Code hinzu. Bei dieser Transformation werden mehrere String-Ressourcen verkettet, je nachdem, ob es sich beim Attributtyp um eine Mietunterkunft handelt.
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
                   }))
}
  1. Öffnen Sie res/layout/fragment_detail.xml. Sie müssen nur noch die neuen Strings, die Sie mit den LiveData-Transformationen erstellt haben, an die Detailansicht binden. Dazu legen Sie den Wert des Textfelds für den Property-Typ-Text auf viewModel.displayPropertyType und den Wert des Textfelds für den Preiswert-Text auf viewModel.displayPropertyPrice fest.
<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" />
  1. Kompilieren und führen Sie die App aus. Alle Property-Daten werden jetzt auf der Detailseite angezeigt, schön formatiert.

Android Studio-Projekt: MarsRealEstateFinal

Bindungsausdrücke

  • Verwenden Sie Bindungsausdrücke in XML-Layoutdateien, um einfache programmatische Operationen wie mathematische Operationen oder bedingte Tests für gebundene Daten auszuführen.
  • Wenn Sie in Ihrer Layoutdatei auf Klassen verweisen möchten, verwenden Sie das <import>-Tag innerhalb des <data>-Tags.

Optionen für Webdienstabfragen

  • Anfragen an Webdienste können optionale Parameter enthalten.
  • Wenn Sie Abfrageparameter in der Anfrage angeben möchten, verwenden Sie die Annotation @Query in Retrofit.

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

Welche Funktion hat das <import>-Tag in einer XML-Layoutdatei?

▢ Eine Layoutdatei in eine andere einfügen

▢ Kotlin-Code in die Layoutdatei einbetten

▢ Zugriff auf datengebundene Eigenschaften gewähren.

▢ Sie können in Bindungsausdrücken auf Klassen und Klassenmitglieder verweisen.

Frage 2

Wie füge ich einem REST-Webdienstaufruf in Retrofit eine Abfrageoption hinzu?

▢ Hängen Sie die Abfrage an das Ende der Anfrage-URL an.

▢ Fügen Sie der Funktion, die die Anfrage stellt, einen Parameter für die Abfrage hinzu und annotieren Sie diesen Parameter mit @Query.

▢ Verwenden Sie die Klasse Query, um eine Anfrage zu erstellen.

▢ Verwenden Sie die Methode addQuery() im Retrofit-Builder.

Nächste Lektion starten: 9.1: Repository

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