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.
- Öffnen Sie die MarsRealEstate App im letzten Codelab. Sie können MarsRealEstateGrid herunterladen, wenn Sie die App nicht haben.
- Öffnen Sie
network/MarsProperty.kt
. Fügen Sie derMarsProperty
-Klassendefinition einen Text hinzu und fügen Sie einen benutzerdefinierten Getter fürisRental
hinzu, dertrue
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.
- Öffnen Sie
res/layout/grid_view_item.xml
. Dies ist die Layoutdatei für jede einzelne Zelle im Rasterlayout für dieRecyclerView
. Zurzeit enthält die Datei nur das<ImageView>
-Element für das Attributbild. - Fügen Sie im Element
<data>
ein<import>
-Element für die KlasseView
hinzu. Mit Importen können Sie Komponenten einer Klasse innerhalb eines Datenbindungsausdrucks in einer Layoutdatei verwenden. In diesem Fall werden die KonstantenView.GONE
undView.VISIBLE
verwendet, also benötigen Sie Zugriff auf die KlasseView
.
<import type="android.view.View"/>
- 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>
- Ändern Sie bei
ImageView
das Attributandroid:layout_height
inmatch_parent
, damit das neue übergeordnete ElementFrameLayout
ausgefüllt wird.
android:layout_height="match_parent"
- Füge ein zweites
<ImageView>
-Element direkt unter dem ersten Element innerhalb vonFrameLayout
hinzu. Verwenden Sie die unten stehende Definition. Dieses Bild wird rechts unten auf dem Mars-Bild angezeigt und zeigt die Drehleiste, die inres/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"/>
- Fügen Sie der Bildanzeige für
mars_property_type
das Attributandroid:visibility
hinzu. Verwenden Sie einen Bindungsausdruck, um den Property-Typ zu testen, und weisen Sie die Sichtbarkeit entwederView.GONE
(für einen Verleih) oderView.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>
- 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.
- Öffnen Sie
network/MarsApiService.kt
. Erstellen Sie direkt unter den Importen einenum
namensMarsApiFilter
, 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") }
- Ä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 Sieretrofit2.http.Query
, wenn Sie dazu aufgefordert werden.
Die Annotation@Query
weist diegetProperties()
-Methode (und damit Retrofit) an, die Webdienstanfrage mit der Filteroption durchzuführen. Bei jedem Aufruf vongetProperties()
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.
- Öffnen Sie
overview/OverviewViewModel.kt
. In Android Studio werden dir aufgrund von Änderungen, die du im vorherigen Schritt vorgenommen hast, Fehler angezeigt. Fügen SieMarsApiFilter
(die Liste der möglichen Filterwerte) als Parameter in dengetMarsRealEstateProperties()
-Aufruf ein.
Importieren Siecom.example.android.marsrealestate.network.MarsApiFilter
, wenn Sie dazu aufgefordert werden.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
- Ändern Sie den Aufruf im Retrofit-Dienst zu
getProperties()
, um diese Filterabfrage als String weiterzugeben.
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)
- Übergeben Sie im
init {}
-BlockMarsApiFilter.SHOW_ALL
als Argument angetMarsRealEstateProperties()
, um alle Eigenschaften anzuzeigen, wenn die App zum ersten Mal geladen wird.
init {
getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
- Fügen Sie am Ende der Klasse eine
updateFilter()
-Methode hinzu, die das ArgumentMarsApiFilter
verwendet undgetMarsRealEstateProperties()
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.
- Ö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>
- Öffnen Sie
overview/OverviewFragment.kt
. Implementieren Sie am Ende des Kurses die MethodeonOptionsItemSelected()
, mit der Sie Menüoptionen auswählen.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
}
- In
onOptionsItemSelected()
rufen Sie die MethodeupdateFilter()
für das Ansichtsmodell mit dem entsprechenden Filter auf. Mit einem Kotlin-Blockwhen {}
können Sie zwischen den Optionen wechseln. Verwenden SieMarsApiFilter.SHOW_ALL
als Standardfilterwert. Gibttrue
zurück, da du den Menüpunkt verarbeitet hast. Importieren SieMarsApiFilter
(com.example.android.marsrealestate.network.MarsApiFilter
). Die vollständige Methode füronOptionsItemSelected()
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
}
- 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.
- 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.
- 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.
- Öffnen Sie
detail/DetailViewModel.kt
. Genauso wie netzwerkbezogene Kotlin-Dateien im Ordnernetwork
und Übersichtsdateien inoverview
enthalten sind, enthält der Ordnerdetail
die Dateien, die mit der Detailansicht verknüpft sind. DieDetailViewModel
-Klasse (derzeit leer) verwendet im Konstruktor einenmarsProperty
-Parameter.
class DetailViewModel( marsProperty: MarsProperty,
app: Application) : AndroidViewModel(app) {
}
- 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 eineMutableLiveData
zu erstellen, in der dieMarsProperty
selbst gespeichert wird, und dann eine unveränderliche öffentlicheLiveData
-Property verfügbar zu machen.
Importieren Sieandroidx.lifecycle.LiveData
und importieren Sieandroidx.lifecycle.MutableLiveData
, wenn Sie dazu aufgefordert werden.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
get() = _selectedProperty
- Erstelle einen
init {}
-Block und lege den Wert der ausgewählten Mars-Property mit demMarsProperty
-Objekt aus dem Konstruktor fest.
init {
_selectedProperty.value = marsProperty
}
- Ö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 eineImageView
für das große Foto, eineTextView
für den Immobilientyp (Verleih oder Verkauf) und eineTextView
für den Preis. Das Einschränkungslayout ist in einScrollView
-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. - 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>
- Fügen Sie dem Element
ImageView
das Attributapp:imageUrl
hinzu. Du kannst ihn über die ausgewählte Property des Ansichtsmodells aufimgSrcUrl
setzen.
Der Bindungsadapter, mit dem ein Bild mit Glide geladen wird, wird hier ebenfalls automatisch verwendet, da dieser Adapter alleapp: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.
- Öffnen Sie
overview/OverviewViewModel.kt
. Füge eine_navigateToSelectedProperty
-MutableLiveData
-Property hinzu und gib sie mit einem unveränderlichenLiveData
-Element an.
Wenn sich derLiveData
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
- 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
}
- 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
- Öffnen Sie
overview/PhotoGridAdapter.kt
. Erstellen Sie am Ende des Kurses eine benutzerdefinierteOnClickListener
-Klasse, die ein Lambda mit einemmarsProperty
-Parameter anwendet. Definieren Sie innerhalb der Klasse eineonClick()
-Funktion, die auf den Parameter „lambda“ festgelegt ist.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
- Scrolle nach oben zur Klassendefinition für
PhotoGridAdapter
und füge dem Konstruktor eine privateOnClickListener
-Property hinzu.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
- Mache ein Foto anklickbar, indem du
onClickListener
dem Rasterelement in deronBindviewHolder()
-Methode hinzufügst. Definieren Sie den Klick-Listener zwischen den Aufrufen vongetItem() and bind()
.
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
val marsProperty = getItem(position)
holder.itemView.setOnClickListener {
onClickListener.onClick(marsProperty)
}
holder.bind(marsProperty)
}
- Öffnen Sie
overview/OverviewFragment.kt
. Ersetze in deronCreateView()
-Methode die Zeile, die diebinding.photosGrid.adapter
-Property initialisiert, durch die unten gezeigte Zeile.
Dieser Code fügt dasPhotoGridAdapter.onClickListener
-Objekt demPhotoGridAdapter
-Konstruktor hinzu und ruftviewModel.displayPropertyDetails()
mit dem übergebenenMarsProperty
-Objekt auf. Dadurch wirdLiveData
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.
- Öffnen Sie
res/navigation/nav_graph.xml
. Klicken Sie auf den Tab Text, um den XML-Code für die Navigationsgrafik aufzurufen. - Füge das unten stehende Element
<argument>
zum Element<fragment>
im Detailfragment hinzu. Dieses Argument namensselectedProperty
hat den TypMarsProperty
.
<argument
android:name="selectedProperty"
app:argType="com.example.android.marsrealestate.network.MarsProperty"
/>
- Kompilieren Sie die App. Sie erhalten eine Fehlermeldung, weil
MarsProperty
nicht parparierbar ist. Über die SchnittstelleParcelable
können Objekte serialisiert werden, sodass die Daten zwischen Fragmenten oder Aktivitäten weitergegeben werden können. In diesem Fall mussMarsProperty
dieParcelable
-Schnittstelle implementieren, damit die Daten innerhalb desMarsProperty
-Objekts über Safe Args an das Detailfragment übergeben werden. Die gute Nachricht: Kotlin bietet eine einfache Möglichkeit, diese Schnittstelle zu implementieren. - Öffnen Sie
network/MarsProperty.kt
. Fügen Sie der Klassendefinition die Annotation@Parcelize
hinzu.
Importieren Siekotlinx.android.parcel.Parcelize
, wenn Sie dazu aufgefordert werden.
Für die Annotation@Parcelize
werden die Kotlin-Android-Erweiterungen verwendet, um die Methoden in derParcelable
-Schnittstelle für diese Klasse automatisch zu implementieren. Sie müssen nichts weiter tun.
@Parcelize
data class MarsProperty (
- Ändern Sie die Klassendefinition von
MarsProperty
, umParcelable
zu verlängern.
Importieren Sieandroid.os.Parcelable
, wenn Sie dazu aufgefordert werden.
Die KlassendefinitionMarsProperty
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.
- Öffnen Sie
overview/OverviewFragment.kt
. Fügen Sie inonCreateView()
unter den Zeilen, mit denen der Fotorasteradapter initialisiert wird, die unten angezeigten Zeilen hinzu, um dienavigatedToSelectedProperty
aus dem Übersichtsansicht-Modell zu beobachten.
Importieren Sieandroidx.lifecycle.Observer
und importieren Sieandroidx.navigation.fragment.findNavController
, wenn Sie dazu aufgefordert werden.
Der Beobachter testet, obMarsProperty
– derit
in dem Lammda-Tag – nicht null ist und, falls ja, es ruft den Navigations-Controller aus dem Fragment mitfindNavController()
ab. RufedisplayPropertyDetailsComplete()
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 zuOverviewFragment
zurückkehrt.
viewModel.navigateToSelectedProperty.observe(this, Observer {
if ( null != it ) {
this.findNavController().navigate(
OverviewFragmentDirections.actionShowDetail(it))
viewModel.displayPropertyDetailsComplete()
}
})
- Öffnen Sie
detail/DetailFragment.kt
. Fügen Sie diese Zeile direkt unter dem Aufruf vonsetLifecycleOwner()
in deronCreateView()
-Methode hinzu. Diese Zeile ruft das ausgewählteMarsProperty
-Objekt aus dem Safe Args ab.
Sieh dir die Verwendung des Kotlin-Assertion-Operators (!!
) an. WennselectedProperty
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
- Fügen Sie diese Zeile als Nächstes hinzu, um eine neue
DetailViewModelFactory
zu erhalten. Sie verwendenDetailViewModelFactory
, um eine Instanz vonDetailViewModel
zu erhalten. Die Starter-App enthält eine Implementierung vonDetailViewModelFactory
. Sie müssen ihn also nur initialisieren.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
- 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)
- 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.
- Ö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 Ressourcedisplay_price_monthly_rental
oderdisplay_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>
- Öffnen Sie
detail/DetailViewModel.kt
. Geben Sie unten im Kurs den unten angezeigten Code ein.
Importieren Sieandroidx.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 dieproperty.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)
}
- Importieren Sie die generierte
R
-Klasse, um Zugriff auf die Stringressourcen im Projekt zu erhalten.
import com.example.android.marsrealestate.R
- 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
}))
}
- Öffnen Sie
res/layout/fragment_detail.xml
. Es gibt noch etwas zu tun. Das bedeutet, die neuen Strings, die Sie mit denLiveData
-Transformationen erstellt haben, an die Detailansicht zu binden. Dazu setzen Sie den Wert des Textfelds für den Text des Unterkunftstyps aufviewModel.displayPropertyType
und das Textfeld für den Preiswert aufviewModel.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" />
- 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:
- ViewModel-Übersicht
- LiveData – Übersicht
- Bindungsadapter
- Layouts und Bindungsausdrücke
- Navigation
- Erste Schritte mit der Komponente „Navigation“
- Daten zwischen Zielen übergeben (auch „Safe Args“ beschrieben)
Transformations
-KlasseViewModelProvider
-KlasseViewModelProvider.Factory
-Klasse
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:
Links zu anderen Codelabs in diesem Kurs finden Sie auf der Landingpage zu Kotlin-Grundlagen für Android.