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 classeMarsProperty
e un getter personalizzato perisRental
che restituiscetrue
se 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.GONE
eView.VISIBLE
, quindi devi accedere alla classeView
.
<import type="android.view.View"/>
- Circonda l'intera visualizzazione dell'immagine con un
FrameLayout
per 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_height
inmatch_parent
per 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.xml
per 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:visibility
alla 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=buy
In 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 unenum
chiamatoMarsApiFilter
per 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.Query
quando richiesto.
L'annotazione@Query
indica 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.MarsApiFilter
quando 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_ALL
come 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 argomentoMarsApiFilter
e 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_ALL
per il valore predefinito del filtro. Restituiscitrue
perché 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 cartellanetwork
e i file di panoramica inoverview
, la cartelladetail
contiene i file associati alla visualizzazione dettagliata. Tieni presente che la classeDetailViewModel
(attualmente vuota) accettamarsProperty
come parametro nel costruttore.
class DetailViewModel( marsProperty: MarsProperty,
app: Application) : AndroidViewModel(app) {
}
- All'interno della definizione della classe, aggiungi
LiveData
per la proprietà Mars selezionata, in modo da esporre queste informazioni nella visualizzazione dettagliata. Segui il pattern abituale di creazione di unMutableLiveData
per contenere l'MarsProperty
stesso, quindi esponi una proprietàLiveData
pubblica e immutabile.
Importaandroidx.lifecycle.LiveData
e importaandroidx.lifecycle.MutableLiveData
quando 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'oggettoMarsProperty
del costruttore.
init {
_selectedProperty.value = marsProperty
}
- Apri
res/layout/fragment_detail.xml
e visualizzalo nella visualizzazione Progettazione.
Questo è il file di layout per il frammento di dettagli. Contiene unImageView
per la foto grande, unTextView
per il tipo di proprietà (affitto o vendita) e unTextView
per 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:imageUrl
all'elementoImageView
. Impostalo suimgSrcUrl
dalla 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à_navigateToSelectedProperty
MutableLiveData
ed esponila con unLiveData
immutabile.
Quando questoLiveData
cambia 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 _navigateToSelectedProperty
sulla 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 classeOnClickListener
personalizzata 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
PhotoGridAdapter
e aggiungi una proprietà privataOnClickListener
al costruttore.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
- Rendi una foto cliccabile aggiungendo
onClickListener
all'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.adapter
con la riga mostrata di seguito.
Questo codice aggiunge l'oggettoPhotoGridAdapter.onClickListener
al costruttorePhotoGridAdapter
e chiamaviewModel.displayPropertyDetails()
con l'oggettoMarsProperty
passato. Questo attivaLiveData
nel 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é
MarsProperty
non è parcelable. L'interfacciaParcelable
consente 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'oggettoMarsProperty
vengano passati al fragment di dettagli tramite Safe Args,MarsProperty
deve implementare l'interfacciaParcelable
. La buona notizia è che Kotlin fornisce una scorciatoia semplice per implementare questa interfaccia. - Apri
network/MarsProperty.kt
. Aggiungi l'annotazione@Parcelize
alla definizione della classe.
Importakotlinx.android.parcel.Parcelize
quando richiesto.
L'annotazione@Parcelize
utilizza le estensioni Kotlin Android per implementare automaticamente i metodi nell'interfacciaParcelable
per questa classe. Non devi fare altro.
@Parcelize
data class MarsProperty (
- Modifica la definizione della classe
MarsProperty
per estendereParcelable
.
Importaandroid.os.Parcelable
quando richiesto.
La definizione della classeMarsProperty
ora 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 osservarenavigatedToSelectedProperty
dal modello di visualizzazione della panoramica.
Importaandroidx.lifecycle.Observer
e importaandroidx.navigation.fragment.findNavController
quando richiesto.
L'osservatore verifica seMarsProperty
, ovveroit
nella lambda, non è null e, in caso affermativo, recupera il controller di navigazione dal fragment confindNavController()
. ChiamadisplayPropertyDetailsComplete()
per indicare al modello di visualizzazione di reimpostareLiveData
sullo 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'oggettoMarsProperty
selezionato da Safe Args.
Nota l'utilizzo dell'operatore di asserzione non null di Kotlin (!!
). SeselectedProperty
non è 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
. UtilizzeraiDetailViewModelFactory
per 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
DetailViewModel
dalla 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_rental
o 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.Transformations
se 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.price
in 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
R
generata 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.displayPropertyType
e 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
@Query
in 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)
Transformations
ViewModelProvider
ViewModelProvider.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.