Android Kotlin Fundamentals 0.3: Filtern und Detailansichten mit Internetdaten

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

In den vorherigen Codelabs für diese Lektion haben Sie gelernt, wie Sie Immobiliendaten auf dem Mars von einem Webdienst abrufen und wie Sie eine RecyclerView mit einem Rasterlayout erstellen, um Bilder aus diesen Daten zu laden und anzuzeigen. In diesem Codelab kannst du die MarsRealEstate-App fertigstellen, indem du die Mars-Properties danach filtern kannst, ob sie zum Ausleihen oder zum Kauf verfügbar sind. Wenn Sie auf das Property-Foto in der Übersicht tippen, sehen Sie eine Detailansicht mit Details zur entsprechenden Property.

Wichtige Informationen

  • Fragmente erstellen und verwenden
  • Navigation zwischen Fragmenten und Safe Arg (ein Gradle-Plug-in) zur Weitergabe von Daten zwischen Fragmenten
  • Architekturkomponenten verwenden, einschließlich Modellen ansehen, Modellfabriken ansehen, Transformationen und LiveData ansehen.
  • JSON-codierte Daten aus einem REST-Webdienst abrufen und mit den Retrofit- und Moshi-Bibliotheken in Kotlin-Objekte parsen.

Lerninhalte

  • Komplexe Bindungsausdrücke in Layoutdateien verwenden
  • Hier erfahren Sie, wie Sie Anfragen zur Nachrüstung an einen Webdienst mit Abfrageoptionen senden.

Aufgabe

  • Ändern Sie die MarsRealEstate-App, um die zum Verkauf stehenden Mars-Properties (im Gegensatz zu den zum Mieten angebotenen) mit einem Dollarzeichen zu kennzeichnen.
  • Erstellen Sie über das Menü „Optionen“ auf der Übersichtsseite eine Webdienstanfrage, mit der die Mars-Properties nach Typ gefiltert werden.
  • Erstelle ein Detailfragment für eine Mars-Property, füge dieses Fragment mit der Navigationsleiste zum Übersichtsraster hinzu und übergib die Property-Daten an dieses Fragment.

In diesem Codelab und den zugehörigen Codelabs arbeiten Sie mit einer App namens MarsRealEstate, die Eigenschaften zum Verkauf auf dem Mars zeigt. Diese App stellt eine Verbindung zu einem Internetserver her, um Unterkunftsdaten abzurufen und anzuzeigen, einschließlich Details wie den 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. In früheren Codelabs haben Sie ein RecyclerView mit einem Rasterlayout für alle Property-Fotos erstellt:

In dieser Version der App arbeiten Sie mit dem Typ der Property (Ausleihen oder Kauf) und fügen dem Rasterlayout ein Symbol hinzu, mit dem zum Verkauf stehenden Properties gekennzeichnet werden:

Sie können das Raster so ändern, dass nur die Eigenschaften angezeigt werden, die Sie zur Vermietung oder zum Verkauf anbieten:

Schließlich erstellen Sie eine Detailansicht für eine einzelne Property und verbinden die Symbole im Übersichtsraster mit dem entsprechenden Fragment über die Navigation:

Bis jetzt war der einzige Teil der Mars-Property-Daten, die Sie verwendet haben, die URL für das Property-Bild. Die Property-Daten, die Sie in der MarsProperty-Klasse definiert haben, enthalten jedoch auch eine ID, einen Preis und einen Typ (Verleih oder Verkauf). Um Ihren Arbeitsspeicher zu aktualisieren, hier ein Snippet 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 beginnen Sie mit dem Property-Typ „Mars“, um den Immobilien auf der Übersichtsseite, die zum Verkauf stehen, ein Dollarzeichen hinzuzufügen.

Schritt 1: MarsProperty so aktualisieren, dass der Typ enthalten ist

Die Klasse MarsProperty definiert die Datenstruktur für jede Property, die vom Webdienst bereitgestellt wird. In einem vorherigen Codelab hast du die Moshi-Bibliothek verwendet, um die JSON-Rohdaten des JSON-Webdiensts in einzelne MarsProperty-Datenobjekte zu parsen.

In diesem Schritt fügen Sie der Klasse MarsProperty eine Logik hinzu, um anzugeben, ob eine Property gemietet werden soll oder nicht. Das heißt, ob der Typ der Zeichenfolge "rent" oder "buy" ist. Sie verwenden diese Logik an mehreren Orten, sodass es besser ist, sie in der Datenklasse zu replizieren, als sie zu replizieren.

  1. Öffnen Sie die MarsRealEstate App im letzten Codelab. Sie können MarsRealEstateGrid herunterladen, wenn Sie die App nicht haben.
  2. Öffnen Sie network/MarsProperty.kt. Fügen Sie der MarsProperty-Klassendefinition einen Text 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: Rasterelement aktualisieren

Jetzt wird das Elementlayout für das Raster von Bildern aktualisiert, sodass ein Dollarzeichen nur für die Bilder der Unterkunft angezeigt wird, die im Angebot sind:

Mit Datenbindungsausdrücken können Sie diesen Test vollständig im XML-Layout für die Rasterelemente ausführen.

  1. Öffnen Sie res/layout/grid_view_item.xml. Dies ist die Layoutdatei für jede einzelne Zelle im Rasterlayout für die RecyclerView. Zurzeit enthält die Datei nur das <ImageView>-Element für das Attributbild.
  2. Fügen Sie im Element <data> ein <import>-Element für die Klasse View hinzu. Mit Importen können Sie Komponenten einer Klasse innerhalb eines Datenbindungsausdrucks in einer Layoutdatei verwenden. In diesem Fall werden die Konstanten View.GONE und View.VISIBLE verwendet, also benötigen Sie Zugriff auf die Klasse View.
<import type="android.view.View"/>
  1. Umgibt die gesamte Bildansicht mit einer FrameLayout, damit das Dollarzeichen ü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 bei ImageView das Attribut android:layout_height in match_parent, damit das neue übergeordnete Element FrameLayout ausgefüllt wird.
android:layout_height="match_parent"
  1. Füge ein zweites <ImageView>-Element direkt unter dem ersten Element innerhalb von FrameLayout hinzu. Verwenden Sie die unten stehende Definition. Dieses Bild wird rechts unten auf dem Mars-Bild angezeigt und zeigt die Drehleiste, die in res/drawable/ic_for_sale_outline.xml für das Dollarzeichen-Symbol definiert ist.
<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 der Bildanzeige für mars_property_type das Attribut android:visibility hinzu. Verwenden Sie einen Bindungsausdruck, um den Property-Typ zu testen, und weisen Sie die Sichtbarkeit entweder View.GONE (für einen Verleih) oder View.VISIBLE (für einen Kauf) zu.
 android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"

Bis jetzt hast du nur Bindungsausdrücke in Layouts gesehen, die einzelne Variablen verwenden, die im <data>-Element definiert sind. Bindungsausdrücke sind äußerst leistungsstark und ermöglichen es Ihnen, Vorgänge wie Tests und mathematische Berechnungen komplett innerhalb Ihres XML-Layouts auszuführen. In diesem Fall verwendest du den ternären Operator (?:), um einen Test durchzuführen. Ist das Objekt ein Verleih? Sie geben ein Ergebnis für „wahr“ zurück (das Dollarzeichen-Symbol mit View.GONE ausblenden) und ein anderes für „falsch“ (dieses Symbol mit View.VISIBLE anzeigen).

Hier sehen Sie die neue vollständige grid_view_item.xml-Datei:

<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 und führen Sie die App aus. Beachten Sie, dass Eigenschaften, die nicht zum Verleih gehören, das Dollarzeichen haben.

Derzeit werden in Ihrer App alle Mars-Eigenschaften im Übersichtsraster angezeigt. Angenommen, ein Nutzer hat auf dem Mars eine Miete gesucht. Es wäre nützlich, die Symbole für die verfügbaren Unterkünfte zu verwenden, aber es gibt noch viele weitere Eigenschaften, durch die man auf der Seite scrollen kann. In dieser Aufgabe fügen Sie dem Übersichtsfragment ein Optionsmenü hinzu, über das der Nutzer entweder nur Mietobjekte, nur Immobilien oder alle anzeigen kann.

Eine Möglichkeit, dies zu erreichen, besteht darin, den Typ für jedes MarsProperty im Übersichtsraster zu testen und nur die übereinstimmenden Eigenschaften anzuzeigen. Der tatsächliche Mars-Webdienst hat jedoch einen Suchparameter oder eine Option namens filter, mit dem Sie nur Attribute des Typs rent oder des Typs buy abrufen können. Sie können diese Filterabfrage mit der Webdienst-URL realestate in einem Browser wie diesem verwenden:

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

In dieser Aufgabe ändern Sie die Klasse MarsApiService, um der Webdienstanfrage mit Retrofit eine Abfrageoption hinzuzufügen. Anschließend verknüpfen Sie das Optionsmenü, um alle Mars-Property-Daten mit dieser Abfrageoption noch einmal herunterzuladen. Die Antwort, die Sie vom Webdienst erhalten, enthält nur die Eigenschaften, für die Sie sich interessieren. Daher ist es nicht erforderlich, die Anzeigelogik für die Übersichtsansicht zu ändern.

Schritt 1: Mars API-Dienst aktualisieren

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

  1. Öffnen Sie network/MarsApiService.kt. Erstellen Sie direkt unter den Importen ein enum namens MarsApiFilter, um Konstanten zu definieren, die mit den vom Webdienst erwarteten Werten übereinstimmen.
enum class MarsApiFilter(val value: String) {
   SHOW_RENT("rent"),
   SHOW_BUY("buy"),
   SHOW_ALL("all") }
  1. Ändern Sie die getProperties()-Methode, um die Stringeingabe für die Filterabfrage zu verwenden, und versehen Sie diese Eingabe mit @Query("filter"), wie unten dargestellt.

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

    Die Annotation @Query weist die getProperties()-Methode (und damit Retrofit) an, die Webdienstanfrage mit der Filteroption durchzuführen. Bei jedem Aufruf von getProperties() enthält die Anfrage-URL den ?filter=type-Teil, der den Webdienst anweist, mit übereinstimmenden Ergebnissen zu antworten.
fun getProperties(@Query("filter") type: String):  

Schritt 2: Übersichtsansicht aktualisieren

Sie fordern Daten aus dem MarsApiService in der getMarsRealEstateProperties()-Methode in OverviewViewModel an. Jetzt müssen Sie die Anfrage aktualisieren, damit das Filterargument verwendet wird.

  1. Öffnen Sie overview/OverviewViewModel.kt. In Android Studio werden dir aufgrund von Änderungen, die du im vorherigen Schritt vorgenommen hast, Fehler angezeigt. Fügen Sie MarsApiFilter (die Liste der möglichen Filterwerte) als Parameter in den getMarsRealEstateProperties()-Aufruf ein.

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

Schritt 3: Fragment mit dem Optionsmenü verbinden

Der letzte Schritt besteht darin, das Dreipunkt-Menü mit dem Fragment zu verknüpfen und updateFilter() im Ansichtsmodell aufzurufen, wenn der Nutzer eine Menüoption auswählt.

  1. Öffnen Sie res/menu/overflow_menu.xml. Für die MarsRealEstate-App gibt es ein Dreipunkt-Menü mit drei Optionen: alle Unterkünfte, nur Mietobjekte oder nur für den Verkauf genutzte Unterkünfte.
<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 des Kurses die Methode onOptionsItemSelected(), mit der Sie Menüoptionen auswählen.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} 
  1. In onOptionsItemSelected() rufen Sie die Methode updateFilter() für das Ansichtsmodell mit dem entsprechenden Filter auf. Mit einem Kotlin-Block when {} können Sie zwischen den Optionen wechseln. Verwenden Sie MarsApiFilter.SHOW_ALL als Standardfilterwert. Gibt true zurück, da du den Menüpunkt verarbeitet hast. Importieren Sie MarsApiFilter (com.example.android.marsrealestate.network.MarsApiFilter). Die vollständige Methode für onOptionsItemSelected() wird unten dargestellt.
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 führen Sie die App aus. Die App öffnet das erste Übersichtsraster mit allen Property-Typen und den zum Verkauf bereitgestellten Properties, die mit dem Dollarsymbol gekennzeichnet sind.
  2. Wählen Sie das Menü Ausleihen aus. Die Eigenschaften werden neu geladen und keine sind mit dem Dollarsymbol gekennzeichnet. (Hier werden nur Mietobjekte angezeigt.) Möglicherweise müssen Sie einen Moment warten, bis die Anzeige aktualisiert wird und nur die gefilterten Eigenschaften angezeigt werden.
  3. Wählen Sie im Optionsmenü den Eintrag Kaufen aus. Die Eigenschaften werden erneut geladen, jeweils mit dem Dollarsymbol. (Nur zum Verkauf stehende Immobilien werden angezeigt.)

Jetzt habt ihr ein scrollbares Raster mit Symbolen für Mars-Objekte, aber es ist Zeit, mehr zu erfahren. In dieser Aufgabe fügen Sie ein Detailfragment hinzu, damit die Details einer bestimmten Eigenschaft angezeigt werden. Dort werden ein größeres Bild, der Preis und die Art der Unterkunft angezeigt – unabhängig davon, ob es sich um eine Miete oder zum Verkauf handelt.

Dieses Fragment wird gestartet, wenn der Nutzer auf ein Bild im Übersichtsraster tippt. Dazu musst du den RecyclerView-Rasterelementen einen onClick-Listener hinzufügen und dann zum neuen Fragment gehen. Zum Navigieren gelangen Sie, indem Sie eine LiveDataÄnderung im ViewModel auslösen, wie Sie es in diesen Lektionen geschafft haben. Mit dem Plug-in „Navigation Arg“ werden außerdem die ausgewählten MarsProperty-Informationen aus dem Übersichtsfragment an das Detailfragment übergeben.

Schritt 1: Detailansichtmodell erstellen und Layout der Detailansicht aktualisieren

Ähnlich wie beim Verfahren „Übersichtsansicht“ und „Fragmente“ müssen nun auch das Modell für die Ansichtsdaten und die Layoutdateien für das Detailfragment implementiert werden.

  1. Öffnen Sie detail/DetailViewModel.kt. Genauso 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. Die DetailViewModel-Klasse (derzeit leer) verwendet im Konstruktor einen marsProperty-Parameter.
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 für die Detailansicht freizugeben. Gehen Sie wie gewohnt vor, um eine MutableLiveData zu erstellen, in der die MarsProperty selbst gespeichert wird, und dann eine unveränderliche öffentliche LiveData-Property verfügbar zu machen.

    Importieren Sie androidx.lifecycle.LiveData und importieren Sie androidx.lifecycle.MutableLiveData, wenn Sie dazu aufgefordert werden.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
   get() = _selectedProperty
  1. Erstelle einen init {}-Block und lege 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 das Design in der Designansicht an.

    Das ist die Layoutdatei für das Detailfragment. Sie enthält eine ImageView für das große Foto, eine TextView für den Immobilientyp (Verleih oder Verkauf) und eine TextView für den Preis. Das Einschränkungslayout ist in ein ScrollView-Element eingeschlossen. Dadurch wird automatisch gescrollt, wenn die Ansicht für die Anzeige zu groß wird. Das ist z. B. der Fall, wenn der Nutzer sie im Querformat betrachtet.
  2. Gehen Sie zum Tab Text des Layouts. Fügen Sie oben im Layout direkt vor dem Element <ScrollView> ein <data>-Element hinzu, um das Modell der Detailansicht 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. Du kannst ihn über die ausgewählte Property des Ansichtsmodells auf imgSrcUrl setzen.

    Der Bindungsadapter, mit dem ein Bild mit Glide geladen wird, wird hier ebenfalls automatisch verwendet, da dieser Adapter alle app:imageUrl-Attribute ansieht.
 app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"

Schritt 2: Navigation im Modell „Übersichtsansicht“ definieren

Wenn der Nutzer im Übersichtsmodell auf ein Foto tippt, sollte eine Navigation zu einem Fragment ausgelöst werden, das Details zum angeklickten Element zeigt.

  1. Öffnen Sie overview/OverviewViewModel.kt. Füge eine _navigateToSelectedProperty-MutableLiveData-Property hinzu und gib sie mit einem unveränderlichen LiveData-Element an.

    Wenn sich der LiveData in einen anderen Wert als null ändert, wird die Navigation ausgelöst. (Demnächst 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üge eine displayPropertyDetailsComplete()-Methode hinzu, in der der Wert von _navigateToSelectedProperty zurückgesetzt wird. Damit können Sie den Navigationsstatus als abgeschlossen kennzeichnen und verhindern, dass die Navigation wieder ausgelöst wird, wenn der Nutzer von der Detailansicht zurückkehrt.
fun displayPropertyDetailsComplete() {
   _navigateToSelectedProperty.value = null
}

Schritt 3: Klick-Listener im Rasteradapter 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 anwendet. Definieren Sie innerhalb der Klasse eine onClick()-Funktion, die auf den Parameter „lambda“ festgelegt ist.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
     fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
  1. Scrolle nach oben zur Klassendefinition für PhotoGridAdapter und füge dem Konstruktor eine private OnClickListener-Property hinzu.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
       ListAdapter<MarsProperty,              
           PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
  1. Mache ein Foto anklickbar, indem du onClickListener dem Rasterelement in der onBindviewHolder()-Methode hinzufügst. 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. Ersetze in der onCreateView()-Methode die Zeile, die die binding.photosGrid.adapter-Property initialisiert, durch die unten gezeigte Zeile.

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

Schritt 4: Navigationsgrafik ändern und MarsProperty parabel machen

Wenn ein Nutzer auf ein Foto im Übersichtsgitter tippt, sollte die App zum Detailfragment gehen und die Details der ausgewählten Mars-Property durchlaufen, damit die Detailansicht diese Informationen anzeigen kann.

Derzeit hast du einen Klick-Listener von PhotoGridAdapter, mit dem du durch Antippen der Schaltfläche die Navigation aufrufen kannst. Aber du hast noch ein MarsProperty-Objekt, das an das Detailfragment übergeben wird. 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 die Navigationsgrafik aufzurufen.
  2. Füge das unten stehende Element <argument> zum Element <fragment> im Detailfragment hinzu. Dieses Argument namens selectedProperty hat den Typ MarsProperty.
<argument
   android:name="selectedProperty"
   app:argType="com.example.android.marsrealestate.network.MarsProperty"
   />
  1. Kompilieren Sie die App. Sie erhalten eine Fehlermeldung, weil MarsProperty nicht parparierbar ist. Über die Schnittstelle Parcelable können Objekte serialisiert werden, sodass die Daten zwischen Fragmenten oder Aktivitäten weitergegeben werden können. In diesem Fall muss MarsProperty die Parcelable-Schnittstelle implementieren, damit die Daten innerhalb des MarsProperty-Objekts über Safe Args an das Detailfragment übergeben werden. Die gute Nachricht: Kotlin bietet eine einfache Möglichkeit, diese Schnittstelle zu implementieren.
  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.

    Für die Annotation @Parcelize werden die Kotlin-Android-Erweiterungen verwendet, um die Methoden in der Parcelable-Schnittstelle 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 verlängern.

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

    Die Klassendefinition MarsProperty sieht 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

Du navigierst immer noch nicht – die eigentliche Navigation erfolgt in den Fragmenten. In diesem Schritt fügen Sie die letzten Bits für die Implementierung der Navigation zwischen der Übersicht und den Detailfragmenten hinzu.

  1. Öffnen Sie overview/OverviewFragment.kt. Fügen Sie in onCreateView() unter den Zeilen, mit denen der Fotorasteradapter initialisiert wird, die unten angezeigten Zeilen hinzu, um die navigatedToSelectedProperty aus dem Übersichtsansicht-Modell zu beobachten.

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

    Der Beobachter testet, ob MarsProperty – der it in dem Lammda-Tag – nicht null ist und, falls ja, es ruft den Navigations-Controller aus dem Fragment mit findNavController() ab. Rufe displayPropertyDetailsComplete() auf, um das Ansichtsmodell anzuweisen, LiveData auf den Nullstatus zurückzusetzen. So kannst du versehentlich die Navigation nicht noch einmal auslösen, wenn die App zu 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 onCreateView()-Methode hinzu. Diese Zeile ruft das ausgewählte MarsProperty-Objekt aus dem Safe Args ab.

    Sieh dir die Verwendung des Kotlin-Assertion-Operators (!!) an. Wenn selectedProperty nicht vorhanden ist, ist ein Fehler aufgetreten und du möchtest, dass der Code einen Nullzeiger auslöst. Im Produktionscode sollte dieser Fehler in irgendeiner Weise behoben werden.
 val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty
  1. Fügen Sie diese Zeile als Nächstes hinzu, um eine neue DetailViewModelFactory zu erhalten. Sie verwenden DetailViewModelFactory, um eine Instanz von DetailViewModel zu erhalten. Die Starter-App enthält eine Implementierung von DetailViewModelFactory. Sie müssen ihn also nur initialisieren.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
  1. Fügen Sie schließlich diese Zeile hinzu, um einen DetailViewModel vom Werk zu erhalten und alle Teile zu verbinden.
      binding.viewModel = ViewModelProviders.of(
                this, viewModelFactory).get(DetailViewModel::class.java)
  1. Kompilieren und führen Sie die App aus und tippen Sie auf ein Foto der Mars-Property. Das Detailfragment wird für diese Property angezeigt. Tippen Sie auf die Schaltfläche „Zurück“, um zur Übersichtsseite zurückzukehren. Die Details sind immer noch dünn. Sie fügen die Property-Daten der nächsten Seite hinzu.

Aktuell werden auf der Detailseite nur die gleichen Mars-Fotos angezeigt wie auf der Übersichtsseite. Die MarsProperty-Klasse hat auch eine Unterkunft (Verleih oder Kauf) und einen Unterkunftspreis. Der Detailbildschirm sollte beide Werte enthalten und es wäre hilfreich, wenn bei den Mietobjekten angegeben wurde, dass der Preis pro Monat galt. Sie verwenden LiveData-Transformationen im Ansichtsmodell, um beides zu implementieren.

  1. Öffnen Sie res/values/strings.xml. Der Startcode enthält die unten aufgeführten String-Ressourcen, die Ihnen beim Erstellen der Strings für die Detailansicht helfen. Für den Preis verwendest du, je nach Property-Typ, entweder die Ressource display_price_monthly_rental oder 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. Geben Sie unten im Kurs den unten angezeigten Code ein.

    Importieren Sie androidx.lifecycle.Transformations, wenn Sie dazu aufgefordert werden.

    Bei dieser Transformation wird anhand des Tests aus der ersten Aufgabe getestet, ob die ausgewählte Property ein Verleih ist. Wenn die Property ein Leihobjekt ist, wird bei der Transformation der entsprechende String aus den Ressourcen mit einem Kotlin-when {}-Schalter ausgewählt. Beide Strings müssen am Ende mit einer Zahl versehen werden, sodass Sie anschließend die property.price verketten.
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 Zugriff auf die Stringressourcen im Projekt zu erhalten.
import com.example.android.marsrealestate.R
  1. Füge nach der displayPropertyPrice-Transformation den unten gezeigten Code hinzu. Bei dieser Transformation werden mehrere String-Ressourcen verkettet, je nachdem, ob es sich bei dem Property-Typ um einen Verleih 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. Es gibt noch etwas zu tun. Das bedeutet, die neuen Strings, die Sie mit den LiveData-Transformationen erstellt haben, an die Detailansicht zu binden. Dazu setzen Sie den Wert des Textfelds für den Text des Unterkunftstyps auf viewModel.displayPropertyType und das Textfeld für den Preiswert auf viewModel.displayPropertyPrice.
<TextView
   android:id="@+id/property_type_text"
...
android:text="@{viewModel.displayPropertyType}"
...
   tools:text="To Rent" />

<TextView
   android:id="@+id/price_value_text"
...
android:text="@{viewModel.displayPropertyPrice}"
...
   tools:text="$100,000" />
  1. Kompilieren und führen Sie die App aus. Jetzt werden alle Unterkunftsdaten in einem ansprechenden Format auf der Detailseite angezeigt.

Android Studio-Projekt: MarsRealEstateFinal

Bindungsausdrücke

  • Mit Bindungsausdrücken in XML-Layoutdateien können Sie einfache programmatische Vorgänge wie mathematische oder bedingte Tests an gebundenen Daten ausführen.
  • Für Verweise auf Klassen in der Layoutdatei verwenden Sie das <import>-Tag im <data>-Tag.

Webdienstoptionen

  • 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 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

Was bewirkt das <import>-Tag in einer XML-Layoutdatei?

▢ Fügen Sie eine Layoutdatei in eine andere ein.

▢ Kotlin-Code in die Layoutdatei einbetten

▢ Sie ermöglichen den Zugriff auf datengebundene Properties.

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

Frage 2

Wie fügst du 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 versehen Sie diesen Parameter mit @Query.

▢ Erstelle eine Anfrage mit der Klasse Query.

▢ Sie verwenden 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 zu Kotlin-Grundlagen für Android.