Questo codelab fa parte del corso Android Kotlin Fundamentals. Per ottenere il massimo valore da questo corso, ti consigliamo di seguire le codelab in sequenza. Tutti i codelab del corso sono elencati nella pagina di destinazione dei codelab Android Kotlin Fundamentals.
Introduzione
Nei codelab precedenti di questa lezione, hai imparato a recuperare i dati sugli immobili su Marte da un servizio web e a creare un RecyclerView con un layout a griglia per caricare e visualizzare le immagini da questi dati. In questo codelab, completi l'app MarsRealEstate implementando la possibilità di filtrare le proprietà di Marte in base alla disponibilità per l'affitto o l'acquisto. Crei anche una visualizzazione dettagliata in modo che, se l'utente tocca una foto della proprietà nella panoramica, visualizzi una visualizzazione dettagliata con i dettagli della proprietà.
Cosa devi già sapere
- Come creare e utilizzare i frammenti.
- Come spostarsi tra i fragment e utilizzare Safe Args (un plug-in Gradle) per passare dati tra i fragment.
- Come utilizzare i componenti dell'architettura, inclusi view model, fabbriche di view model, trasformazioni e
LiveData. - Come recuperare dati codificati in formato JSON da un servizio web REST e analizzarli in oggetti Kotlin con le librerie Retrofit e Moshi.
Cosa imparerai a fare
- Come utilizzare espressioni di binding complesse nei file di layout.
- Come effettuare richieste Retrofit a un servizio web con opzioni di query.
Attività previste
- Modifica l'app MarsRealEstate per contrassegnare le proprietà di Mars in vendita (anziché quelle in affitto) con un'icona con il simbolo del dollaro.
- Utilizza il menu delle opzioni nella pagina Panoramica per creare una richiesta di servizio web che filtri le proprietà di Mars per tipo.
- Crea un frammento di dettagli per una proprietà di Marte, collegalo alla griglia di panoramica con la navigazione e trasferisci i dati della proprietà nel frammento.
In questo codelab (e in quelli correlati) lavorerai con un'app chiamata MarsRealEstate, che mostra le proprietà in vendita su Marte. Questa app si connette a un server internet per recuperare e visualizzare i dati della proprietà, inclusi dettagli come il prezzo e se la proprietà è disponibile per la vendita o l'affitto. Le immagini che rappresentano ogni proprietà sono foto reali di Marte scattate dai rover marziani della NASA. Nei codelab precedenti, hai creato un RecyclerView con un layout a griglia per tutte le foto della proprietà:

In questa versione dell'app, lavori con il tipo di proprietà (affitto o acquisto) e aggiungi un'icona al layout a griglia per contrassegnare le proprietà in vendita:

Modifica il menu delle opzioni dell'app per filtrare la griglia in modo da mostrare solo le proprietà in affitto o in vendita:

Infine, crei una visualizzazione dettagliata per una singola proprietà e colleghi le icone della griglia di panoramica a questo frammento di dettagli con la navigazione:

Finora, l'unica parte dei dati della proprietà Mars che hai utilizzato è l'URL dell'immagine della proprietà. Tuttavia, i dati della proprietà, che hai definito nella classe MarsProperty, includono anche un ID, un prezzo e un tipo (affitto o vendita). Per rinfrescarti la memoria, ecco un snippet dei dati JSON che ottieni dal servizio web:
{
"price":8000000,
"id":"424908",
"type":"rent",
"img_src": "http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631290305226E03_DXXX.jpg"
},In questa attività, inizi a lavorare con il tipo di proprietà Marte per aggiungere un'immagine con il simbolo del dollaro alle proprietà in vendita nella pagina Panoramica.
Passaggio 1: aggiorna MarsProperty per includere il tipo
La classe MarsProperty definisce la struttura dei dati per ogni proprietà fornita dal servizio web. In un codelab precedente, hai utilizzato la libreria Moshi per analizzare la risposta JSON non elaborata del servizio web Mars in singoli oggetti di dati MarsProperty.
In questo passaggio, aggiungi una logica alla classe MarsProperty per indicare se una proprietà è in affitto o meno (ovvero se il tipo è la stringa "rent" o "buy"). Utilizzerai questa logica in più di un punto, quindi è meglio averla qui nella classe di dati piuttosto che replicarla.
- Apri l'app MarsRealEstate dell'ultimo codelab. Se non hai l'app, puoi scaricare MarsRealEstateGrid.
- Apri
network/MarsProperty.kt. Aggiungi un corpo alla definizione della classeMarsPropertye un getter personalizzato perisRentalche restituiscetruese l'oggetto è di tipo"rent".
data class MarsProperty(
val id: String,
@Json(name = "img_src") val imgSrcUrl: String,
val type: String,
val price: Double) {
val isRental
get() = type == "rent"
}Passaggio 2: aggiorna il layout dell'elemento della griglia
Ora aggiorna il layout dell'elemento per la griglia di immagini in modo da mostrare una risorsa disegnabile con il simbolo del dollaro solo sulle immagini delle proprietà in vendita:

Con le espressioni di data binding puoi eseguire questo test interamente nel layout XML per gli elementi della griglia.
- Apri
res/layout/grid_view_item.xml. Questo è il file di layout per ogni singola cella nel layout a griglia perRecyclerView. Al momento il file contiene solo l'elemento<ImageView>per l'immagine della proprietà. - All'interno dell'elemento
<data>, aggiungi un elemento<import>per la classeView. Utilizzi le importazioni quando vuoi utilizzare i componenti di una classe all'interno di un'espressione di data binding in un file di layout. In questo caso, utilizzerai le costantiView.GONEeView.VISIBLE, quindi devi accedere alla classeView.
<import type="android.view.View"/>- Circonda l'intera visualizzazione dell'immagine con un
FrameLayoutper consentire l'impilamento della risorsa disegnabile del simbolo del dollaro sopra l'immagine della proprietà.
<FrameLayout
android:layout_width="match_parent"
android:layout_height="170dp">
<ImageView
android:id="@+id/mars_image"
...
</FrameLayout>- Per
ImageView, modifica l'attributoandroid:layout_heightinmatch_parentper compilare il nuovo elemento principaleFrameLayout.
android:layout_height="match_parent"- Aggiungi un secondo elemento
<ImageView>appena sotto il primo, all'interno diFrameLayout. Utilizza la definizione mostrata di seguito. Questa immagine viene visualizzata nell'angolo in basso a destra dell'elemento della griglia, sopra l'immagine di Marte, e utilizza la risorsa disegnabile definita inres/drawable/ic_for_sale_outline.xmlper l'icona del simbolo del dollaro.
<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"/>- Aggiungi l'attributo
android:visibilityalla visualizzazione dell'immaginemars_property_type. Utilizza un'espressione di binding per verificare il tipo di proprietà e assegna la visibilità aView.GONE(per un noleggio) oView.VISIBLE(per un acquisto).
android:visibility="@{property.rental ? View.GONE : View.VISIBLE}"Finora hai visto le espressioni di binding solo nei layout che utilizzano singole variabili definite nell'elemento <data>. Le espressioni di binding sono estremamente potenti e ti consentono di eseguire operazioni come test e calcoli matematici interamente all'interno del layout XML. In questo caso, utilizzi l'operatore ternario (?:) per eseguire un test (questo oggetto è un noleggio?). Fornisci un risultato per vero (nascondi l'icona del dollaro con View.GONE) e un altro per falso (mostra l'icona con View.VISIBLE).
Il nuovo file grid_view_item.xml completo è mostrato di seguito:
<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>- Compila ed esegui l'app e tieni presente che le proprietà che non sono affitti hanno l'icona del simbolo del dollaro.

Attualmente la tua app mostra tutte le proprietà di Mars nella griglia di panoramica. Se un utente stesse cercando una proprietà in affitto su Marte, sarebbe utile avere le icone per indicare quali delle proprietà disponibili sono in vendita, ma ci sono ancora molte proprietà da scorrere nella pagina. In questa attività, aggiungi un menu delle opzioni al fragment di panoramica che consente all'utente di mostrare solo gli immobili in affitto, solo quelli in vendita o tutti.

Un modo per svolgere questa attività è testare il tipo per ogni MarsProperty nella griglia di panoramica e visualizzare solo le proprietà corrispondenti. Il servizio web Mars effettivo, tuttavia, ha un parametro o un'opzione di query (chiamato filter) che consente di ottenere solo le proprietà di tipo rent o buy. Puoi utilizzare questa query di filtro con l'URL del servizio web realestate in un browser come questo:
https://android-kotlin-fun-mars-server.appspot.com/realestate?filter=buyIn questa attività, modifichi la classe MarsApiService per aggiungere un'opzione di query alla richiesta del servizio web con Retrofit. Poi collega il menu delle opzioni per scaricare di nuovo tutti i dati della proprietà Mars utilizzando l'opzione di query. Poiché la risposta che ricevi dal servizio web contiene solo le proprietà che ti interessano, non devi modificare la logica di visualizzazione della griglia di panoramica.
Passaggio 1: aggiorna il servizio API Mars
Per modificare la richiesta, devi rivedere la classe MarsApiService che hai implementato nel primo codelab di questa serie. Modifica la classe per fornire un'API di filtraggio.
- Apri
network/MarsApiService.kt. Subito sotto le importazioni, crea unenumchiamatoMarsApiFilterper definire le costanti che corrispondono ai valori della query previsti dal servizio web.
enum class MarsApiFilter(val value: String) {
SHOW_RENT("rent"),
SHOW_BUY("buy"),
SHOW_ALL("all") }- Modifica il metodo
getProperties()per accettare l'input della stringa per la query di filtro e annota l'input con@Query("filter"), come mostrato di seguito.
Importaretrofit2.http.Queryquando richiesto.
L'annotazione@Queryindica al metodogetProperties()(e quindi a Retrofit) di effettuare la richiesta del servizio web con l'opzione di filtro. Ogni volta che viene chiamatogetProperties(), l'URL della richiesta include la parte?filter=type, che indica al servizio web di rispondere con risultati che corrispondono a quella query.
fun getProperties(@Query("filter") type: String): Passaggio 2: aggiorna il modello di visualizzazione della panoramica
Richiedi i dati da MarsApiService nel metodo getMarsRealEstateProperties() in OverviewViewModel. Ora devi aggiornare la richiesta per utilizzare l'argomento del filtro.
- Apri
overview/OverviewViewModel.kt. Visualizzerai errori in Android Studio a causa delle modifiche apportate nel passaggio precedente. AggiungiMarsApiFilter(l'enumerazione dei possibili valori del filtro) come parametro alla chiamatagetMarsRealEstateProperties().
Importacom.example.android.marsrealestate.network.MarsApiFilterquando richiesto.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {- Modifica la chiamata a
getProperties()nel servizio Retrofit per trasmettere la query di filtro come stringa.
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)- Nel blocco
init {}, passaMarsApiFilter.SHOW_ALLcome argomento agetMarsRealEstateProperties()per mostrare tutte le proprietà al primo caricamento dell'app.
init {
getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}- Alla fine della classe, aggiungi un metodo
updateFilter()che accetta un argomentoMarsApiFiltere chiamagetMarsRealEstateProperties()con quell'argomento.
fun updateFilter(filter: MarsApiFilter) {
getMarsRealEstateProperties(filter)
}Passaggio 3: collega il fragment al menu delle opzioni
L'ultimo passaggio consiste nel collegare il menu overflow al fragment per chiamare updateFilter() nel modello di visualizzazione quando l'utente sceglie un'opzione di menu.
- Apri
res/menu/overflow_menu.xml. L'app MarsRealEstate ha un menu overflow esistente che offre le tre opzioni disponibili: mostrare tutte le proprietà, mostrare solo gli affitti e mostrare solo le proprietà in vendita.
<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>- Apri
overview/OverviewFragment.kt. Alla fine della lezione, implementa il metodoonOptionsItemSelected()per gestire le selezioni delle voci di menu.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} - In
onOptionsItemSelected(), chiama il metodoupdateFilter()sul modello di visualizzazione con il filtro appropriato. Utilizza un bloccowhen {}Kotlin per passare da un'opzione all'altra. UtilizzaMarsApiFilter.SHOW_ALLper il valore predefinito del filtro. Restituiscitrueperché hai gestito la voce di menu. ImportaMarsApiFilter(com.example.android.marsrealestate.network.MarsApiFilter) quando richiesto. Il metodoonOptionsItemSelected()completo è mostrato di seguito.
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
}- Compila ed esegui l'app. L'app avvia la prima griglia di panoramica con tutti i tipi di proprietà e le proprietà in vendita contrassegnate dall'icona del dollaro.
- Scegli Noleggia dal menu delle opzioni. Le proprietà vengono ricaricate e nessuna di queste viene visualizzata con l'icona del dollaro. Vengono mostrate solo le proprietà in affitto. Potresti dover attendere qualche istante prima che la visualizzazione venga aggiornata e mostri solo le proprietà filtrate.
- Scegli Acquista dal menu delle opzioni. Le proprietà vengono ricaricate di nuovo e tutte vengono visualizzate con l'icona del dollaro. Vengono mostrate solo le proprietà in vendita.
Ora hai una griglia scorrevole di icone per le proprietà di Mars, ma è il momento di aggiungere maggiori dettagli. In questa attività, aggiungi un frammento di dettagli per visualizzare i dettagli di una proprietà specifica. Il frammento di dettaglio mostrerà un'immagine più grande, il prezzo e il tipo di proprietà, ovvero se è in affitto o in vendita.

Questo frammento viene avviato quando l'utente tocca un'immagine nella griglia della panoramica. Per farlo, devi aggiungere un listener onClick agli elementi della griglia RecyclerView e poi passare al nuovo fragmento. La navigazione avviene attivando una modifica di LiveData in ViewModel, come hai fatto in queste lezioni. Utilizzi anche il plug-in Safe Args del componente Navigation per passare le informazioni MarsProperty selezionate dal fragment di panoramica al fragment di dettagli.
Passaggio 1: crea il modello di visualizzazione dettagliata e aggiorna il layout dei dettagli
Analogamente alla procedura utilizzata per il modello e i fragment della visualizzazione Panoramica, ora devi implementare il modello di visualizzazione e i file di layout per il fragment dei dettagli.
- Apri
detail/DetailViewModel.kt. Proprio come i file Kotlin correlati alla rete sono contenuti nella cartellanetworke i file di panoramica inoverview, la cartelladetailcontiene i file associati alla visualizzazione dettagliata. Tieni presente che la classeDetailViewModel(attualmente vuota) accettamarsPropertycome parametro nel costruttore.
class DetailViewModel( marsProperty: MarsProperty,
app: Application) : AndroidViewModel(app) {
}- All'interno della definizione della classe, aggiungi
LiveDataper la proprietà Mars selezionata, in modo da esporre queste informazioni nella visualizzazione dettagliata. Segui il pattern abituale di creazione di unMutableLiveDataper contenere l'MarsPropertystesso, quindi esponi una proprietàLiveDatapubblica e immutabile.
Importaandroidx.lifecycle.LiveDatae importaandroidx.lifecycle.MutableLiveDataquando richiesto.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
get() = _selectedProperty- Crea un blocco
init {}e imposta il valore della proprietà Mars selezionata con l'oggettoMarsPropertydel costruttore.
init {
_selectedProperty.value = marsProperty
}- Apri
res/layout/fragment_detail.xmle visualizzalo nella visualizzazione Progettazione.
Questo è il file di layout per il frammento di dettagli. Contiene unImageViewper la foto grande, unTextViewper il tipo di proprietà (affitto o vendita) e unTextViewper il prezzo. Tieni presente che il layout dei vincoli è racchiuso in unScrollView, quindi scorrerà automaticamente se la visualizzazione diventa troppo grande per il display, ad esempio quando l'utente la visualizza in modalità Orizzontale. - Vai alla scheda Testo del layout. Nella parte superiore del layout, appena prima dell'elemento
<ScrollView>, aggiungi un elemento<data>per associare il modello della visualizzazione dettagli al layout.
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.detail.DetailViewModel" />
</data>- Aggiungi l'attributo
app:imageUrlall'elementoImageView. Impostalo suimgSrcUrldalla proprietà selezionata del modello di visualizzazione.
Anche l'adattatore di binding che carica un'immagine utilizzando Glide verrà utilizzato automaticamente qui, perché questo adattatore monitora tutti gli attributiapp:imageUrl.
app:imageUrl="@{viewModel.selectedProperty.imgSrcUrl}"Passaggio 2: definisci la navigazione nel modello di visualizzazione della panoramica
Quando l'utente tocca una foto nel modello di panoramica, deve attivarsi la navigazione a un frammento che mostra i dettagli dell'elemento selezionato.
- Apri
overview/OverviewViewModel.kt. Aggiungi una proprietà_navigateToSelectedPropertyMutableLiveDataed esponila con unLiveDataimmutabile.
Quando questoLiveDatacambia in non nullo, viene attivata la navigazione. A breve aggiungerai il codice per osservare questa variabile e attivare la navigazione.
private val _navigateToSelectedProperty = MutableLiveData<MarsProperty>()
val navigateToSelectedProperty: LiveData<MarsProperty>
get() = _navigateToSelectedProperty- Alla fine della classe, aggiungi un metodo
displayPropertyDetails()che imposta _navigateToSelectedPropertysulla proprietà Mars selezionata.
fun displayPropertyDetails(marsProperty: MarsProperty) {
_navigateToSelectedProperty.value = marsProperty
}- Aggiungi un metodo
displayPropertyDetailsComplete()che imposta su null il valore di_navigateToSelectedProperty. Ti serve per contrassegnare lo stato di navigazione come completato ed evitare che la navigazione venga attivata di nuovo quando l'utente torna dalla visualizzazione dei dettagli.
fun displayPropertyDetailsComplete() {
_navigateToSelectedProperty.value = null
}Passaggio 3: configura i listener di clic nell'adattatore della griglia e nel fragmento
- Apri
overview/PhotoGridAdapter.kt. Alla fine della classe, crea una classeOnClickListenerpersonalizzata che accetta una lambda con un parametromarsProperty. All'interno della classe, definisci una funzioneonClick()impostata sul parametro lambda.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}- Scorri verso l'alto fino alla definizione della classe per
PhotoGridAdaptere aggiungi una proprietà privataOnClickListeneral costruttore.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {- Rendi una foto cliccabile aggiungendo
onClickListenerall'elemento della griglia nel metodoonBindviewHolder(). Definisci il listener dei clic tra le chiamate agetItem() and bind().
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
val marsProperty = getItem(position)
holder.itemView.setOnClickListener {
onClickListener.onClick(marsProperty)
}
holder.bind(marsProperty)
}- Apri
overview/OverviewFragment.kt. Nel metodoonCreateView(), sostituisci la riga che inizializza la proprietàbinding.photosGrid.adaptercon la riga mostrata di seguito.
Questo codice aggiunge l'oggettoPhotoGridAdapter.onClickListeneral costruttorePhotoGridAdaptere chiamaviewModel.displayPropertyDetails()con l'oggettoMarsPropertypassato. Questo attivaLiveDatanel modello di visualizzazione per la navigazione.
binding.photosGrid.adapter = PhotoGridAdapter(PhotoGridAdapter.OnClickListener {
viewModel.displayPropertyDetails(it)
})Passaggio 4: modifica il grafico di navigazione e rendi MarsProperty serializzabile
Quando un utente tocca una foto nella griglia della panoramica, l'app deve passare al frammento dei dettagli e trasmettere i dettagli della proprietà di Marte selezionata in modo che la visualizzazione dettagli possa mostrare queste informazioni.

Al momento hai un listener di clic da PhotoGridAdapter per gestire il tocco e un modo per attivare la navigazione dal view model. Tuttavia, non hai ancora un oggetto MarsProperty passato al frammento dei dettagli. A questo scopo, utilizza Safe Args dal componente di navigazione.
- Apri
res/navigation/nav_graph.xml. Fai clic sulla scheda Testo per visualizzare il codice XML del grafico di navigazione. - All'interno dell'elemento
<fragment>per il frammento di dettaglio, aggiungi l'elemento<argument>mostrato di seguito. Questo argomento, chiamatoselectedProperty, è di tipoMarsProperty.
<argument
android:name="selectedProperty"
app:argType="com.example.android.marsrealestate.network.MarsProperty"
/>- Compila l'app. La navigazione restituisce un errore perché
MarsPropertynon è parcelable. L'interfacciaParcelableconsente di serializzare gli oggetti, in modo che i dati degli oggetti possano essere trasferiti tra fragment o attività. In questo caso, affinché i dati all'interno dell'oggettoMarsPropertyvengano passati al fragment di dettagli tramite Safe Args,MarsPropertydeve implementare l'interfacciaParcelable. La buona notizia è che Kotlin fornisce una scorciatoia semplice per implementare questa interfaccia. - Apri
network/MarsProperty.kt. Aggiungi l'annotazione@Parcelizealla definizione della classe.
Importakotlinx.android.parcel.Parcelizequando richiesto.
L'annotazione@Parcelizeutilizza le estensioni Kotlin Android per implementare automaticamente i metodi nell'interfacciaParcelableper questa classe. Non devi fare altro.
@Parcelize
data class MarsProperty (- Modifica la definizione della classe
MarsPropertyper estendereParcelable.
Importaandroid.os.Parcelablequando richiesto.
La definizione della classeMarsPropertyora ha il seguente aspetto:
@Parcelize
data class MarsProperty (
val id: String,
@Json(name = "img_src") val imgSrcUrl: String,
val type: String,
val price: Double) : Parcelable {Passaggio 5: collega i frammenti
Non stai ancora navigando: la navigazione effettiva avviene nei fragment. In questo passaggio, aggiungi gli ultimi bit per implementare la navigazione tra i fragment di panoramica e di dettaglio.
- Apri
overview/OverviewFragment.kt. InonCreateView(), sotto le righe che inizializzano l'adattatore della griglia di foto, aggiungi le righe mostrate di seguito per osservarenavigatedToSelectedPropertydal modello di visualizzazione della panoramica.
Importaandroidx.lifecycle.Observere importaandroidx.navigation.fragment.findNavControllerquando richiesto.
L'osservatore verifica seMarsProperty, ovveroitnella lambda, non è null e, in caso affermativo, recupera il controller di navigazione dal fragment confindNavController(). ChiamadisplayPropertyDetailsComplete()per indicare al modello di visualizzazione di reimpostareLiveDatasullo stato null, in modo da non attivare accidentalmente di nuovo la navigazione quando l'app torna aOverviewFragment.
viewModel.navigateToSelectedProperty.observe(this, Observer {
if ( null != it ) {
this.findNavController().navigate(
OverviewFragmentDirections.actionShowDetail(it))
viewModel.displayPropertyDetailsComplete()
}
})- Apri
detail/DetailFragment.kt. Aggiungi questa riga appena sotto la chiamata asetLifecycleOwner()nel metodoonCreateView(). Questa riga recupera l'oggettoMarsPropertyselezionato da Safe Args.
Nota l'utilizzo dell'operatore di asserzione non null di Kotlin (!!). SeselectedPropertynon è presente, è successo qualcosa di terribile e vuoi che il codice generi un puntatore nullo. Nel codice di produzione, devi gestire l'errore in qualche modo.
val marsProperty = DetailFragmentArgs.fromBundle(arguments!!).selectedProperty- Aggiungi questa riga per ottenere un nuovo
DetailViewModelFactory. UtilizzeraiDetailViewModelFactoryper ottenere un'istanza diDetailViewModel. L'app di base include un'implementazione diDetailViewModelFactory, quindi tutto ciò che devi fare qui è inizializzarla.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)- Infine, aggiungi questa linea per ottenere un
DetailViewModeldalla fabbrica e collegare tutte le parti.
binding.viewModel = ViewModelProviders.of(
this, viewModelFactory).get(DetailViewModel::class.java)- Compila ed esegui l'app e tocca una foto di una proprietà di Marte. Viene visualizzato il frammento dei dettagli della proprietà. Tocca il pulsante Indietro per tornare alla pagina di panoramica e noterai che la schermata dei dettagli è ancora un po' scarna. Completerai l'aggiunta dei dati della proprietà a questa pagina dei dettagli nell'attività successiva.
Al momento, la pagina dei dettagli mostra solo la stessa foto di Marte che vedi nella pagina Panoramica. La classe MarsProperty ha anche un tipo di proprietà (affitto o acquisto) e un prezzo. La schermata dei dettagli deve includere entrambi i valori e sarebbe utile se le proprietà in affitto indicassero che il prezzo è un valore mensile. Utilizzi le trasformazioni LiveData nel modello di visualizzazione per implementare entrambe le operazioni.
- Apri
res/values/strings.xml. Il codice iniziale include risorse stringa, mostrate di seguito, per aiutarti a creare le stringhe per la visualizzazione dettagliata. Per il prezzo, utilizzerai la risorsadisplay_price_monthly_rentalo la risorsadisplay_price, a seconda del tipo di proprietà.
<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>- Apri
detail/DetailViewModel.kt. Nella parte inferiore della classe, aggiungi il codice mostrato di seguito.
Importaandroidx.lifecycle.Transformationsse richiesto.
Questa trasformazione verifica se la proprietà selezionata è in affitto, utilizzando lo stesso test della prima attività. Se la proprietà è un noleggio, la trasformazione sceglie la stringa appropriata dalle risorse con un'istruzione switch Kotlinwhen {}. Entrambe queste stringhe devono avere un numero alla fine, quindi concateniproperty.pricein seguito.
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)
}- Importa la classe
Rgenerata per accedere alle risorse stringa nel progetto.
import com.example.android.marsrealestate.R- Dopo la trasformazione
displayPropertyPrice, aggiungi il codice mostrato di seguito. Questa trasformazione concatena più risorse stringa, a seconda che il tipo di proprietà sia un affitto.
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
}))
}- Apri
res/layout/fragment_detail.xml. Devi solo fare un'altra cosa: associare le nuove stringhe (che hai creato con le trasformazioniLiveData) alla visualizzazione dettagliata. A questo scopo, imposta il valore del campo di testo per il testo del tipo di proprietà suviewModel.displayPropertyTypee il campo di testo per il testo del valore del prezzo suviewModel.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" />- Compila ed esegui l'app. Ora tutti i dati della proprietà vengono visualizzati nella pagina dei dettagli, in un formato ben strutturato.

Progetto Android Studio: MarsRealEstateFinal
Espressioni di binding
- Utilizza le espressioni di binding nei file di layout XML per eseguire semplici operazioni programmatiche, come test matematici o condizionali, sui dati associati.
- Per fare riferimento alle classi all'interno del file di layout, utilizza il tag
<import>all'interno del tag<data>.
Opzioni di query del servizio web
- Le richieste ai servizi web possono includere parametri facoltativi.
- Per specificare i parametri di query nella richiesta, utilizza l'annotazione
@Queryin Retrofit.
Corso Udacity:
Documentazione per sviluppatori Android:
- Panoramica di ViewModel
- Panoramica di LiveData
- Adattatori per attacchi
- Layout ed espressioni di binding
- Navigazione
- Inizia a utilizzare il componente Navigazione
- Passare dati tra le destinazioni (descrive anche Safe Args)
TransformationsViewModelProviderViewModelProvider.Factory
Altro:
Questa sezione elenca i possibili compiti a casa per gli studenti che seguono questo codelab nell'ambito di un corso guidato da un insegnante. Spetta all'insegnante:
- Assegna i compiti, se richiesto.
- Comunica agli studenti come inviare i compiti.
- Valuta i compiti a casa.
Gli insegnanti possono utilizzare questi suggerimenti nella misura che ritengono opportuna e sono liberi di assegnare qualsiasi altro compito a casa che ritengono appropriato.
Se stai seguendo questo codelab in autonomia, sentiti libero di utilizzare questi compiti per casa per mettere alla prova le tue conoscenze.
Rispondi a queste domande
Domanda 1
A cosa serve il tag <import> in un file di layout XML?
▢ Includi un file di layout in un altro.
▢ Incorpora il codice Kotlin all'interno del file di layout.
▢ Fornisci l'accesso alle proprietà associate ai dati.
▢ Consentono di fare riferimento a corsi e membri dei corsi nelle espressioni di binding.
Domanda 2
Come si aggiunge un'opzione di query a una chiamata di servizio web REST in Retrofit?
▢ Aggiungi la query alla fine dell'URL della richiesta.
▢ Aggiungi un parametro per la query alla funzione che effettua la richiesta e annota questo parametro con @Query.
▢ Utilizza la classe Query per creare una richiesta.
▢ Utilizza il metodo addQuery() nel generatore Retrofit.
Inizia la lezione successiva:
Per i link ad altri codelab di questo corso, consulta la pagina di destinazione dei codelab di Android Kotlin Fundamentals.