Android Kotlin Fundamentals 08.3 Filtering and detail views with internet data

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.

  1. Apri l'app MarsRealEstate dell'ultimo codelab. Se non hai l'app, puoi scaricare MarsRealEstateGrid.
  2. Apri network/MarsProperty.kt. Aggiungi un corpo alla definizione della classe MarsProperty e un getter personalizzato per isRental che restituisce true 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.

  1. Apri res/layout/grid_view_item.xml. Questo è il file di layout per ogni singola cella nel layout a griglia per RecyclerView. Al momento il file contiene solo l'elemento <ImageView> per l'immagine della proprietà.
  2. All'interno dell'elemento <data>, aggiungi un elemento <import> per la classe View. 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 costanti View.GONE e View.VISIBLE, quindi devi accedere alla classe View.
<import type="android.view.View"/>
  1. 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>
  1. Per ImageView, modifica l'attributo android:layout_height in match_parent per compilare il nuovo elemento principale FrameLayout.
android:layout_height="match_parent"
  1. Aggiungi un secondo elemento <ImageView> appena sotto il primo, all'interno di FrameLayout. 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 in res/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"/>
  1. Aggiungi l'attributo android:visibility alla visualizzazione dell'immagine mars_property_type. Utilizza un'espressione di binding per verificare il tipo di proprietà e assegna la visibilità a View.GONE (per un noleggio) o View.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>
  1. 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.

  1. Apri network/MarsApiService.kt. Subito sotto le importazioni, crea un enum chiamato MarsApiFilter 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") }
  1. 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.

    Importa retrofit2.http.Query quando richiesto.

    L'annotazione @Query indica al metodo getProperties() (e quindi a Retrofit) di effettuare la richiesta del servizio web con l'opzione di filtro. Ogni volta che viene chiamato getProperties(), 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.

  1. Apri overview/OverviewViewModel.kt. Visualizzerai errori in Android Studio a causa delle modifiche apportate nel passaggio precedente. Aggiungi MarsApiFilter (l'enumerazione dei possibili valori del filtro) come parametro alla chiamata getMarsRealEstateProperties().

    Importa com.example.android.marsrealestate.network.MarsApiFilter quando richiesto.
private fun getMarsRealEstateProperties(filter: MarsApiFilter) {
  1. Modifica la chiamata a getProperties() nel servizio Retrofit per trasmettere la query di filtro come stringa.
var getPropertiesDeferred = MarsApi.retrofitService.getProperties(filter.value)
  1. Nel blocco init {}, passa MarsApiFilter.SHOW_ALL come argomento a getMarsRealEstateProperties() per mostrare tutte le proprietà al primo caricamento dell'app.
init {
   getMarsRealEstateProperties(MarsApiFilter.SHOW_ALL)
}
  1. Alla fine della classe, aggiungi un metodo updateFilter() che accetta un argomento MarsApiFilter e chiama getMarsRealEstateProperties() 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.

  1. 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>
  1. Apri overview/OverviewFragment.kt. Alla fine della lezione, implementa il metodo onOptionsItemSelected() per gestire le selezioni delle voci di menu.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
} 
  1. In onOptionsItemSelected(), chiama il metodo updateFilter() sul modello di visualizzazione con il filtro appropriato. Utilizza un blocco when {} Kotlin per passare da un'opzione all'altra. Utilizza MarsApiFilter.SHOW_ALL per il valore predefinito del filtro. Restituisci true perché hai gestito la voce di menu. Importa MarsApiFilter (com.example.android.marsrealestate.network.MarsApiFilter) quando richiesto. Il metodo onOptionsItemSelected() 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
}
  1. 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.
  2. 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.
  3. 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.

  1. Apri detail/DetailViewModel.kt. Proprio come i file Kotlin correlati alla rete sono contenuti nella cartella network e i file di panoramica in overview, la cartella detail contiene i file associati alla visualizzazione dettagliata. Tieni presente che la classe DetailViewModel (attualmente vuota) accetta marsProperty come parametro nel costruttore.
class DetailViewModel( marsProperty: MarsProperty,
                     app: Application) : AndroidViewModel(app) {
}
  1. 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 un MutableLiveData per contenere l'MarsProperty stesso, quindi esponi una proprietà LiveData pubblica e immutabile.

    Importa androidx.lifecycle.LiveData e importa androidx.lifecycle.MutableLiveData quando richiesto.
private val _selectedProperty = MutableLiveData<MarsProperty>()
val selectedProperty: LiveData<MarsProperty>
   get() = _selectedProperty
  1. Crea un blocco init {} e imposta il valore della proprietà Mars selezionata con l'oggetto MarsProperty del costruttore.
    init {
        _selectedProperty.value = marsProperty
    }
  1. Apri res/layout/fragment_detail.xml e visualizzalo nella visualizzazione Progettazione.

    Questo è il file di layout per il frammento di dettagli. Contiene un ImageView per la foto grande, un TextView per il tipo di proprietà (affitto o vendita) e un TextView per il prezzo. Tieni presente che il layout dei vincoli è racchiuso in un ScrollView, quindi scorrerà automaticamente se la visualizzazione diventa troppo grande per il display, ad esempio quando l'utente la visualizza in modalità Orizzontale.
  2. 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>
  1. Aggiungi l'attributo app:imageUrl all'elemento ImageView. Impostalo su imgSrcUrl 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 attributi app: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.

  1. Apri overview/OverviewViewModel.kt. Aggiungi una proprietà _navigateToSelectedProperty MutableLiveData ed esponila con un LiveData immutabile.

    Quando questo LiveData 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
  1. Alla fine della classe, aggiungi un metodo displayPropertyDetails() che imposta _navigateToSelectedProperty sulla proprietà Mars selezionata.
fun displayPropertyDetails(marsProperty: MarsProperty) {
   _navigateToSelectedProperty.value = marsProperty
}
  1. 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

  1. Apri overview/PhotoGridAdapter.kt. Alla fine della classe, crea una classe OnClickListener personalizzata che accetta una lambda con un parametro marsProperty. All'interno della classe, definisci una funzione onClick() impostata sul parametro lambda.
class OnClickListener(val clickListener: (marsProperty:MarsProperty) -> Unit) {
     fun onClick(marsProperty:MarsProperty) = clickListener(marsProperty)
}
  1. Scorri verso l'alto fino alla definizione della classe per PhotoGridAdapter e aggiungi una proprietà privata OnClickListener al costruttore.
class PhotoGridAdapter( private val onClickListener: OnClickListener ) :
       ListAdapter<MarsProperty,              
           PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
  1. Rendi una foto cliccabile aggiungendo onClickListener all'elemento della griglia nel metodo onBindviewHolder(). Definisci il listener dei clic tra le chiamate a getItem() and bind().
override fun onBindViewHolder(holder: MarsPropertyViewHolder, position: Int) {
   val marsProperty = getItem(position)
   holder.itemView.setOnClickListener {
       onClickListener.onClick(marsProperty)
   }
   holder.bind(marsProperty)
}
  1. Apri overview/OverviewFragment.kt. Nel metodo onCreateView(), sostituisci la riga che inizializza la proprietà binding.photosGrid.adapter con la riga mostrata di seguito.

    Questo codice aggiunge l'oggetto PhotoGridAdapter.onClickListener al costruttore PhotoGridAdapter e chiama viewModel.displayPropertyDetails() con l'oggetto MarsProperty passato. Questo attiva LiveData 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.

  1. Apri res/navigation/nav_graph.xml. Fai clic sulla scheda Testo per visualizzare il codice XML del grafico di navigazione.
  2. All'interno dell'elemento <fragment> per il frammento di dettaglio, aggiungi l'elemento <argument> mostrato di seguito. Questo argomento, chiamato selectedProperty, è di tipo MarsProperty.
<argument
   android:name="selectedProperty"
   app:argType="com.example.android.marsrealestate.network.MarsProperty"
   />
  1. Compila l'app. La navigazione restituisce un errore perché MarsProperty non è parcelable. L'interfaccia Parcelable 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'oggetto MarsProperty vengano passati al fragment di dettagli tramite Safe Args, MarsProperty deve implementare l'interfaccia Parcelable. La buona notizia è che Kotlin fornisce una scorciatoia semplice per implementare questa interfaccia.
  2. Apri network/MarsProperty.kt. Aggiungi l'annotazione @Parcelize alla definizione della classe.

    Importa kotlinx.android.parcel.Parcelize quando richiesto.

    L'annotazione @Parcelize utilizza le estensioni Kotlin Android per implementare automaticamente i metodi nell'interfaccia Parcelable per questa classe. Non devi fare altro.
@Parcelize
data class MarsProperty (
  1. Modifica la definizione della classe MarsProperty per estendere Parcelable.

    Importa android.os.Parcelable quando richiesto.

    La definizione della classe MarsProperty 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.

  1. Apri overview/OverviewFragment.kt. In onCreateView(), sotto le righe che inizializzano l'adattatore della griglia di foto, aggiungi le righe mostrate di seguito per osservare navigatedToSelectedProperty dal modello di visualizzazione della panoramica.

    Importa androidx.lifecycle.Observer e importa androidx.navigation.fragment.findNavController quando richiesto.

    L'osservatore verifica se MarsProperty, ovvero it nella lambda, non è null e, in caso affermativo, recupera il controller di navigazione dal fragment con findNavController(). Chiama displayPropertyDetailsComplete() per indicare al modello di visualizzazione di reimpostare LiveData sullo stato null, in modo da non attivare accidentalmente di nuovo la navigazione quando l'app torna a OverviewFragment.
viewModel.navigateToSelectedProperty.observe(this, Observer {
   if ( null != it ) {   
      this.findNavController().navigate(
              OverviewFragmentDirections.actionShowDetail(it))             
      viewModel.displayPropertyDetailsComplete()
   }
})
  1. Apri detail/DetailFragment.kt. Aggiungi questa riga appena sotto la chiamata a setLifecycleOwner() nel metodo onCreateView(). Questa riga recupera l'oggetto MarsProperty selezionato da Safe Args.

    Nota l'utilizzo dell'operatore di asserzione non null di Kotlin (!!). Se selectedProperty 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
  1. Aggiungi questa riga per ottenere un nuovo DetailViewModelFactory. Utilizzerai DetailViewModelFactory per ottenere un'istanza di DetailViewModel. L'app di base include un'implementazione di DetailViewModelFactory, quindi tutto ciò che devi fare qui è inizializzarla.
val viewModelFactory = DetailViewModelFactory(marsProperty, application)
  1. 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)
  1. 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.

  1. 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 risorsa display_price_monthly_rental o la risorsa display_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>
  1. Apri detail/DetailViewModel.kt. Nella parte inferiore della classe, aggiungi il codice mostrato di seguito.

    Importa androidx.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 Kotlin when {}. Entrambe queste stringhe devono avere un numero alla fine, quindi concateni property.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)
}
  1. Importa la classe R generata per accedere alle risorse stringa nel progetto.
import com.example.android.marsrealestate.R
  1. 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
                   }))
}
  1. Apri res/layout/fragment_detail.xml. Devi solo fare un'altra cosa: associare le nuove stringhe (che hai creato con le trasformazioni LiveData) alla visualizzazione dettagliata. A questo scopo, imposta il valore del campo di testo per il testo del tipo di proprietà su viewModel.displayPropertyType e il campo di testo per il testo del valore del prezzo su viewModel.displayPropertyPrice.
<TextView
   android:id="@+id/property_type_text"
...
android:text="@{viewModel.displayPropertyType}"
...
   tools:text="To Rent" />

<TextView
   android:id="@+id/price_value_text"
...
android:text="@{viewModel.displayPropertyPrice}"
...
   tools:text="$100,000" />
  1. 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:

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: 9.1: Repository

Per i link ad altri codelab di questo corso, consulta la pagina di destinazione dei codelab di Android Kotlin Fundamentals.