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.
- Öffnen Sie die MarsRealEstate-App aus dem letzten Codelab. Wenn Sie die App nicht haben, können Sie MarsRealEstateGrid herunterladen.
- Öffnen Sie
network/MarsProperty.kt
. Fügen Sie der KlassendefinitionMarsProperty
einen Textkörper 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: 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.
- Öffnen Sie
res/layout/grid_view_item.xml
. Dies ist die Layoutdatei für jede einzelne Zelle im Rasterlayout fürRecyclerView
. Derzeit enthält die Datei nur das<ImageView>
-Element für das Property-Bild. - Fügen Sie im
<data>
-Element ein<import>
-Element für die KlasseView
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 KonstantenView.GONE
undView.VISIBLE
. Daher benötigen Sie Zugriff auf die KlasseView
.
<import type="android.view.View"/>
- 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>
- Ändern Sie für
ImageView
das Attributandroid:layout_height
inmatch_parent
, um das neue übergeordnete ElementFrameLayout
zu füllen.
android:layout_height="match_parent"
- Fügen Sie ein zweites
<ImageView>
-Element direkt unter dem ersten innerhalb desFrameLayout
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 inres/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"/>
- Fügen Sie das Attribut
android:visibility
der Bildansichtmars_property_type
hinzu. Verwende einen Bindungsausdruck, um den Attributtyp zu testen, und weise die Sichtbarkeit entwederView.GONE
(für eine Leihgebühr) oderView.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>
- 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.
- Öffnen Sie
network/MarsApiService.kt
. Erstellen Sie direkt unter den Importen eineenum
namensMarsApiFilter
, 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") }
- Ä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 Sieretrofit2.http.Query
, wenn Sie dazu aufgefordert werden.
Die Annotation@Query
weist die MethodegetProperties()
(und damit Retrofit) an, die Webdienstanfrage mit der Filteroption zu stellen. Jedes Mal, wenngetProperties()
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.
- Öffnen Sie
overview/OverviewViewModel.kt
. Aufgrund der Änderungen, die Sie im vorherigen Schritt vorgenommen haben, werden in Android Studio Fehler angezeigt. Fügen SieMarsApiFilter
(die Enumeration der möglichen Filterwerte) als Parameter zumgetMarsRealEstateProperties()
-Aufruf hinzu.
Importieren Siecom.example.android.marsrealestate.network.MarsApiFilter
, wenn Sie dazu aufgefordert werden.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
- Ändern Sie den Aufruf von
getProperties()
im Retrofit-Dienst, um diese Filterabfrage als String zu übergeben.
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)
- Übergeben Sie im
init {}
-BlockMarsApiFilter.SHOW_ALL
als Argument angetMarsRealEstateProperties()
, damit alle Eigenschaften beim ersten Laden der App angezeigt werden.
init {
getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
- Fügen Sie am Ende der Klasse eine
updateFilter()
-Methode hinzu, die einMarsApiFilter
-Argument akzeptiert undgetMarsRealEstateProperties()
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.
- Ö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>
- Öffnen Sie
overview/OverviewFragment.kt
. Implementieren Sie am Ende der Klasse die MethodeonOptionsItemSelected()
, um die Auswahl von Menüelementen zu verarbeiten.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
}
- Rufen Sie in
onOptionsItemSelected()
die MethodeupdateFilter()
für das Ansichtsmodell mit dem entsprechenden Filter auf. Verwenden Sie einen Kotlin-when {}
-Block, um zwischen den Optionen zu wechseln. Verwenden SieMarsApiFilter.SHOW_ALL
für den Standardfilterwert. Geben Sietrue
zurück, da Sie das Menüelement verarbeitet haben. Importieren SieMarsApiFilter
(com.example.android.marsrealestate.network.MarsApiFilter
), wenn Sie dazu aufgefordert werden. Die vollständigeonOptionsItemSelected()
-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
}
- 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.
- 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.
- 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.
- Öffnen Sie
detail/DetailViewModel.kt
. So wie netzwerkbezogene Kotlin-Dateien im Ordnernetwork
und Übersichtsdateien inoverview
enthalten sind, enthält der Ordnerdetail
die Dateien, die mit der Detailansicht verknüpft sind. Beachten Sie, dass die KlasseDetailViewModel
(derzeit leer) einmarsProperty
als Parameter im Konstruktor akzeptiert.
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 in der Detailansicht verfügbar zu machen. Folgen Sie dem üblichen Muster, um eineMutableLiveData
zu erstellen, die dieMarsProperty
enthält, und stellen Sie dann eine unveränderliche öffentlicheLiveData
-Property bereit.
Importieren Sieandroidx.lifecycle.LiveData
undandroidx.lifecycle.MutableLiveData
, wenn Sie dazu aufgefordert werden.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
get() = _selectedProperty
- Erstellen Sie einen
init {}
-Block und legen Sie 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 die Designansicht an.
Dies ist die Layoutdatei für das Detailfragment. Es enthält einImageView
für das große Foto, einTextView
für den Immobilientyp (Vermietung oder Verkauf) und einTextView
für den Preis. Das ConstraintLayout ist in einScrollView
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. - 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>
- Fügen Sie dem Element
ImageView
das Attributapp:imageUrl
hinzu. Legen Sie sie auf denimgSrcUrl
der ausgewählten Eigenschaft des Ansichtsmodells fest.
Der Binding-Adapter, der ein Bild mit Glide lädt, wird hier automatisch verwendet, da dieser Adapter alleapp: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.
- Öffnen Sie
overview/OverviewViewModel.kt
. Fügen Sie ein_navigateToSelectedProperty
-MutableLiveData
-Attribut hinzu und machen Sie es mit einem unveränderlichenLiveData
verfügbar.
Wenn sich dieserLiveData
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
- 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ü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
- Öffnen Sie
overview/PhotoGridAdapter.kt
. Erstellen Sie am Ende des Kurses eine benutzerdefinierteOnClickListener
-Klasse, die ein Lambda mit einemmarsProperty
-Parameter akzeptiert. Definieren Sie in der Klasse eineonClick()
-Funktion, die auf den Lambda-Parameter festgelegt ist.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
- Scrollen Sie nach oben zur Klassendefinition für
PhotoGridAdapter
und fügen Sie dem Konstruktor eine privateOnClickListener
-Property hinzu.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
- Sie können ein Foto anklickbar machen, indem Sie dem Rasterelement in der Methode
onBindviewHolder()
dieonClickListener
hinzufügen. 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
. Ersetzen Sie in der MethodeonCreateView()
die Zeile, mit der die Eigenschaftbinding.photosGrid.adapter
initialisiert wird, durch die unten gezeigte Zeile.
Mit diesem Code wird dasPhotoGridAdapter.onClickListener
-Objekt demPhotoGridAdapter
-Konstruktor hinzugefügt undviewModel.displayPropertyDetails()
mit dem übergebenenMarsProperty
-Objekt aufgerufen. Dadurch wird dasLiveData
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.
- Öffnen Sie
res/navigation/nav_graph.xml
. Klicken Sie auf den Tab Text, um den XML-Code für das Navigationsdiagramm aufzurufen. - Fügen Sie dem
<fragment>
-Element für das Detailfragment das unten gezeigte<argument>
-Element hinzu. Dieses Argument mit dem NamenselectedProperty
hat den TypMarsProperty
.
<argument
android:name="selectedProperty"
app:argType="com.example.android.marsrealestate.network.MarsProperty"
/>
- Kompilieren Sie die App. Die Navigation gibt einen Fehler zurück, da
MarsProperty
nicht parcelable ist. Über dieParcelable
-Schnittstelle können Objekte serialisiert werden, sodass die Daten der Objekte zwischen Fragmenten oder Aktivitäten übergeben werden können. Damit die Daten imMarsProperty
-Objekt über Safe Args an das Detailfragment übergeben werden können, mussMarsProperty
dieParcelable
-Schnittstelle implementieren. Die gute Nachricht ist, dass Kotlin eine einfache Abkürzung für die Implementierung dieser Schnittstelle bietet. - Öffnen Sie
network/MarsProperty.kt
. Fügen Sie der Klassendefinition die Annotation@Parcelize
hinzu.
Importieren Siekotlinx.android.parcel.Parcelize
, wenn Sie dazu aufgefordert werden.
Die Annotation@Parcelize
verwendet die Kotlin-Android-Erweiterungen, um die Methoden in der SchnittstelleParcelable
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 erweitern.
Importieren Sieandroid.os.Parcelable
, wenn Sie dazu aufgefordert werden.
Die Klassendefinition fürMarsProperty
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.
- Öffnen Sie
overview/OverviewFragment.kt
. Fügen Sie inonCreateView()
unter den Zeilen, mit denen der Adapter für das Fotoraster initialisiert wird, die unten gezeigten Zeilen hinzu, umnavigatedToSelectedProperty
aus dem Übersichtsansichtsmodell zu beobachten.
Importieren Sieandroidx.lifecycle.Observer
undandroidx.navigation.fragment.findNavController
, wenn Sie dazu aufgefordert werden.
Der Observer prüft, obMarsProperty
– dasit
im Lambda-Ausdruck – nicht null ist. Wenn dies der Fall ist, ruft er den Navigationscontroller über das Fragment mitfindNavController()
ab. Rufen SiedisplayPropertyDetailsComplete()
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 zurOverviewFragment
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 der MethodeonCreateView()
ein. In dieser Zeile wird das ausgewählteMarsProperty
-Objekt aus den Safe Args abgerufen.
Beachten Sie die Verwendung des Kotlin-Operators für die Zusicherung, dass der Wert nicht null ist (!!
). WennselectedProperty
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
- Fügen Sie als Nächstes diese Zeile hinzu, um ein neues
DetailViewModelFactory
zu erhalten. MitDetailViewModelFactory
erhalten Sie eine Instanz vonDetailViewModel
. Die Starter-App enthält eine Implementierung vonDetailViewModelFactory
. Sie müssen sie also nur initialisieren.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
- 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)
- 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.
- Ö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 Ressourcedisplay_price_monthly_rental
oder die Ressourcedisplay_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
. Fügen Sie am Ende der Klasse den unten gezeigten Code hinzu.
Importieren Sieandroidx.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 wirdproperty.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)
}
- Importieren Sie die generierte
R
-Klasse, um auf die String-Ressourcen im Projekt zuzugreifen.
import com.example.android.marsrealestate.R
- 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
}))
}
- Öffnen Sie
res/layout/fragment_detail.xml
. Sie müssen nur noch die neuen Strings, die Sie mit denLiveData
-Transformationen erstellt haben, an die Detailansicht binden. Dazu legen Sie den Wert des Textfelds für den Property-Typ-Text aufviewModel.displayPropertyType
und den Wert des Textfelds für den Preiswert-Text aufviewModel.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" />
- 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:
- ViewModel – Übersicht
- LiveData – Übersicht
- Bindungsadapter
- Layouts und Bindungsausdrücke
- Navigation
- Erste Schritte mit der Navigation-Komponente
- Daten zwischen Zielen übergeben (Safe Args wird ebenfalls beschrieben)
Transformations
-KlasseViewModelProvider
-KlasseViewModelProvider.Factory
-Klasse
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:
Links zu anderen Codelabs in diesem Kurs finden Sie auf der Landingpage für Android Kotlin Fundamentals-Codelabs.