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
Nel codelab precedente hai imparato a recuperare i dati da un servizio web e ad analizzare la risposta in un oggetto dati. In questo codelab, amplierai queste conoscenze per caricare e visualizzare foto da un URL web. Inoltre, rivedrai come creare un RecyclerView
e utilizzarlo per visualizzare una griglia di immagini nella pagina di panoramica.
Cosa devi già sapere
- Come creare e utilizzare i frammenti.
- Come utilizzare i componenti dell'architettura, inclusi view model, fabbriche di view model, trasformazioni e
LiveData
. - Come recuperare JSON da un servizio web REST e analizzare i dati in oggetti Kotlin utilizzando le librerie Retrofit e Moshi.
- Come creare un layout a griglia con un
RecyclerView
. - Come funzionano
Adapter
,ViewHolder
eDiffUtil
.
Cosa imparerai a fare
- Come utilizzare la libreria Glide per caricare e visualizzare un'immagine da un URL web.
- Come utilizzare un
RecyclerView
e un adattatore a griglia per visualizzare una griglia di immagini. - Come gestire i potenziali errori durante il download e la visualizzazione delle immagini.
Attività previste
- Modifica l'app MarsRealEstate per ottenere l'URL dell'immagine dai dati della proprietà Mars e utilizza Glide per caricare e visualizzare l'immagine.
- Aggiungi un'animazione di caricamento e un'icona di errore all'app.
- Utilizza un
RecyclerView
per visualizzare una griglia di immagini di proprietà di Marte. - Aggiungi la gestione dello stato e degli errori a
RecyclerView
.
In questo codelab (e in quelli correlati), lavorerai con un'app chiamata MarsRealEstate, che mostra le proprietà in vendita su Marte. L'app si connette a un server internet per recuperare e visualizzare i dati delle 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.
La versione dell'app che crei in questo codelab riempie la pagina della panoramica, che mostra una griglia di immagini. Le immagini fanno parte dei dati della proprietà che la tua app riceve dal servizio web immobiliare Mars. La tua app utilizzerà la libreria Glide per caricare e visualizzare le immagini e un RecyclerView
per creare il layout a griglia per le immagini. La tua app gestirà anche gli errori di rete in modo controllato.
Visualizzare una foto da un URL web può sembrare semplice, ma è necessario un lavoro di progettazione piuttosto complesso per farla funzionare correttamente. L'immagine deve essere scaricata, memorizzata nel buffer e decodificata dal formato compresso a un formato utilizzabile da Android. L'immagine deve essere memorizzata nella cache in una cache in memoria, in una cache basata sull'archiviazione o in entrambe. Tutto questo deve avvenire in thread in background a bassa priorità, in modo che l'interfaccia utente rimanga reattiva. Inoltre, per ottenere le migliori prestazioni di rete e della CPU, ti consigliamo di recuperare e decodificare più immagini contemporaneamente. Imparare a caricare in modo efficace le immagini dalla rete potrebbe essere un codelab a sé stante.
Fortunatamente, puoi utilizzare una libreria sviluppata dalla community chiamata Glide per scaricare, memorizzare nel buffer, decodificare e memorizzare nella cache le tue immagini. Glide ti fa risparmiare molto lavoro rispetto a se dovessi fare tutto da zero.
Glide ha bisogno di due cose:
- L'URL dell'immagine che vuoi caricare e mostrare.
- Un oggetto
ImageView
per visualizzare l'immagine.
In questa attività, imparerai a utilizzare Glide per visualizzare una singola immagine dal servizio web immobiliare. Visualizzi l'immagine che rappresenta la prima proprietà di Marte nell'elenco delle proprietà restituite dal servizio web. Ecco gli screenshot prima e dopo:
Passaggio 1: aggiungi la dipendenza Glide
- Apri l'app MarsRealEstate dell'ultimo codelab. Se non hai l'app, puoi scaricare MarsRealEstateNetwork qui.
- Esegui l'app per vedere cosa fa. (Mostra i dettagli di testo di una proprietà ipoteticamente disponibile su Marte.)
- Apri build.gradle (Module: app).
- Nella sezione
dependencies
, aggiungi questa riga per la libreria Glide:
implementation "com.github.bumptech.glide:glide:$version_glide"
Tieni presente che il numero di versione è già definito separatamente nel file Gradle del progetto.
- Fai clic su Sincronizza ora per ricompilare il progetto con la nuova dipendenza.
Passaggio 2: aggiorna il modello di visualizzazione
Poi aggiorna la classe OverviewViewModel
per includere i dati in tempo reale per una singola proprietà di Marte.
- Apri
overview/OverviewViewModel.kt
. Appena sottoLiveData
per_response
, aggiungi dati live interni (modificabili) ed esterni (immutabili) per un singolo oggettoMarsProperty
.
Importa il corsoMarsProperty
(com.example.android.marsrealestate.network.MarsProperty
) quando richiesto.
private val _property = MutableLiveData<MarsProperty>()
val property: LiveData<MarsProperty>
get() = _property
- Nel metodo
getMarsRealEstateProperties()
, trova la riga all'interno del bloccotry/catch {}
che imposta_response.value
sul numero di proprietà. Aggiungi il test mostrato di seguito. Se sono disponibili oggettiMarsProperty
, questo test imposta il valore di_property
LiveData
sulla prima proprietà inlistResult
.
if (listResult.size > 0) {
_property.value = listResult[0]
}
Il blocco try/catch {}
completo ora ha il seguente aspetto:
try {
var listResult = getPropertiesDeferred.await()
_response.value = "Success: ${listResult.size} Mars properties retrieved"
if (listResult.size > 0) {
_property.value = listResult[0]
}
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}
- Apri il file
res/layout/fragment_overview.xml
. Nell'elemento<TextView>
, modificaandroid:text
in modo che venga associato al componenteimgSrcUrl
diproperty
LiveData
:
android:text="@{viewModel.property.imgSrcUrl}"
- Esegui l'app.
TextView
mostra solo l'URL dell'immagine nella prima proprietà di Marte. Finora hai configurato solo il modello di visualizzazione e i dati in tempo reale per l'URL.
Passaggio 3: crea un adattatore di binding e chiama Glide
Ora hai l'URL di un'immagine da visualizzare ed è il momento di iniziare a lavorare con Glide per caricarla. In questo passaggio, utilizzi un adattatore di binding per estrarre l'URL da un attributo XML associato a un ImageView
e utilizzi Glide per caricare l'immagine. Gli adattatori di binding sono metodi di estensione che si trovano tra una visualizzazione e i dati associati per fornire un comportamento personalizzato quando i dati cambiano. In questo caso, il comportamento personalizzato consiste nel chiamare Glide per caricare un'immagine da un URL in un ImageView
.
- Apri
BindingAdapters.kt
. Questo file conterrà gli adattatori di binding che utilizzi in tutta l'app. - Crea una funzione
bindImage()
che accetti unImageView
e unString
come parametri. Annota la funzione con@BindingAdapter
. L'annotazione@BindingAdapter
indica all'associazione di dati che vuoi che questo adattatore di associazione venga eseguito quando un elemento XML ha l'attributoimageUrl
.
Importaandroidx.databinding.BindingAdapter
eandroid.widget.ImageView
quando richiesto.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
}
- All'interno della funzione
bindImage()
, aggiungi un bloccolet {}
per l'argomentoimgUrl
:
imgUrl?.let {
}
- All'interno del blocco
let {}
, aggiungi la riga mostrata di seguito per convertire la stringa URL (dal file XML) in un oggettoUri
. Importaandroidx.core.net.toUri
quando richiesto.
Vuoi che l'oggettoUri
finale utilizzi lo schema HTTPS, perché il server da cui estrai le immagini lo richiede. Per utilizzare lo schema HTTPS, aggiungibuildUpon.scheme("https")
al generatoretoUri
. Il metodotoUri()
è una funzione di estensione Kotlin della libreria principale Android KTX, quindi sembra solo far parte della classeString
.
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
- All'interno di
let {}
, chiamaGlide.with()
per caricare l'immagine dall'oggettoUri
inImageView
. Importacom.bumptech.glide.Glide
quando richiesto.
Glide.with(imgView.context)
.load(imgUri)
.into(imgView)
Passaggio 4: aggiorna il layout e i frammenti
Anche se Glide ha caricato l'immagine, non c'è ancora nulla da vedere. Il passaggio successivo consiste nell'aggiornamento del layout e dei fragment con un ImageView
per visualizzare l'immagine.
- Apri
res/layout/gridview_item.xml
. Questo è il file di risorse di layout che utilizzerai per ogni elemento diRecyclerView
più avanti nel codelab. Lo utilizzi temporaneamente qui per mostrare solo la singola immagine. - Sopra l'elemento
<ImageView>
, aggiungi un elemento<data>
per l'associazione di dati e associa la classeOverviewViewModel
:
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>
- Aggiungi un attributo
app:imageUrl
all'elementoImageView
per utilizzare il nuovo adattatore di binding per il caricamento delle immagini:
app:imageUrl="@{viewModel.property.imgSrcUrl}"
- Apri
overview/OverviewFragment.kt
. Nel metodoonCreateView()
, commenta la riga che gonfia la classeFragmentOverviewBinding
e la assegna alla variabile di binding. Questa modifica è temporanea e potrai tornare a questa impostazione in un secondo momento.
//val binding = FragmentOverviewBinding.inflate(inflater)
- Aggiungi una riga per gonfiare la classe
GridViewItemBinding
. Importacom.example.android.marsrealestate. databinding.GridViewItemBinding
quando richiesto.
val binding = GridViewItemBinding.inflate(inflater)
- Esegui l'app. Ora dovresti vedere la foto dell'immagine del primo
MarsProperty
nell'elenco dei risultati.
Passaggio 5: aggiungi immagini di caricamento ed errore semplici
Glide può migliorare l'esperienza utente mostrando un'immagine segnaposto durante il caricamento dell'immagine e un'immagine di errore se il caricamento non va a buon fine, ad esempio se l'immagine è mancante o danneggiata. In questo passaggio, aggiungi questa funzionalità all'adattatore di binding e al layout.
- Apri
res/drawable/ic_broken_image.xml
e fai clic sulla scheda Anteprima a destra. Per l'immagine di errore, utilizzi l'icona di immagine non disponibile che è disponibile nella libreria di icone integrata. Questa risorsa drawable vettoriale utilizza l'attributoandroid:tint
per colorare l'icona di grigio.
- Apri
res/drawable/loading_animation.xml
. Questa risorsa disegnabile è un'animazione definita con il tag<animate-rotate>
. L'animazione ruota un elemento disegnabile dell'immagine,loading_img.xml
, attorno al punto centrale. Non vedi l'animazione nell'anteprima.
- Torna al file
BindingAdapters.kt
. Nel metodobindImage()
, aggiorna la chiamata aGlide.with()
per chiamare la funzioneapply()
traload()
einto()
. Importacom.bumptech.glide.request.RequestOptions
quando richiesto.
Questo codice imposta l'immagine di caricamento del segnaposto da utilizzare durante il caricamento (la risorsa disegnabileloading_animation
). Il codice imposta anche un'immagine da utilizzare se il caricamento dell'immagine non va a buon fine (la risorsa disegnabilebroken_image
). Il metodobindImage()
completo ora è il seguente:
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
imgUrl?.let {
val imgUri =
imgUrl.toUri().buildUpon().scheme("https").build()
Glide.with(imgView.context)
.load(imgUri)
.apply(RequestOptions()
.placeholder(R.drawable.loading_animation)
.error(R.drawable.ic_broken_image))
.into(imgView)
}
}
- Esegui l'app. A seconda della velocità della connessione di rete, potresti vedere brevemente l'immagine di caricamento mentre Glide scarica e visualizza l'immagine della proprietà. Tuttavia, non vedrai ancora l'icona dell'immagine non funzionante, anche se disattivi la rete. Risolverai il problema nell'ultima parte del codelab.
Ora l'app carica le informazioni sulla proprietà da internet. Utilizzando i dati del primo elemento dell'elenco MarsProperty
, hai creato una proprietà LiveData
nel modello di visualizzazione e hai utilizzato l'URL dell'immagine dei dati della proprietà per compilare un ImageView
. Tuttavia, l'obiettivo è che la tua app mostri una griglia di immagini, quindi devi utilizzare un RecyclerView
con un GridLayoutManager
.
Passaggio 1: aggiorna il modello di visualizzazione
Al momento, il modello di visualizzazione ha un _property
LiveData
che contiene un oggetto MarsProperty
, il primo nell'elenco delle risposte del servizio web. In questo passaggio, modifichi LiveData
in modo che contenga l'intero elenco di oggetti MarsProperty
.
- Apri
overview/OverviewViewModel.kt
. - Modifica la variabile privata
_property
in_properties
. Modifica il tipo in un elenco di oggettiMarsProperty
.
private val _properties = MutableLiveData<List<MarsProperty>>()
- Sostituisci i dati in tempo reale esterni
property
conproperties
. Aggiungi l'elenco anche al tipoLiveData
qui:
val properties: LiveData<List<MarsProperty>>
get() = _properties
- Scorri verso il basso fino al metodo
getMarsRealEstateProperties()
. All'interno del bloccotry {}
, sostituisci l'intero test che hai aggiunto nell'attività precedente con la riga mostrata di seguito. Poiché la variabilelistResult
contiene un elenco di oggettiMarsProperty
, puoi assegnarla a_properties.value
anziché verificare se la risposta è andata a buon fine.
_properties.value = listResult
L'intero blocco try/catch
ora ha il seguente aspetto:
try {
var listResult = getPropertiesDeferred.await()
_response.value = "Success: ${listResult.size} Mars properties retrieved"
_properties.value = listResult
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}
Passaggio 2: aggiorna i layout e i fragment
Il passaggio successivo consiste nel modificare il layout e i frammenti dell'app per utilizzare una visualizzazione elenco e un layout a griglia, anziché la visualizzazione di una singola immagine.
- Apri
res/layout/gridview_item.xml
. Modifica il binding dei dati daOverviewViewModel
aMarsProperty
e rinomina la variabile in"property"
.
<variable
name="property"
type="com.example.android.marsrealestate.network.MarsProperty" />
- In
<ImageView>
, modifica l'attributoapp:imageUrl
in modo che faccia riferimento all'URL dell'immagine nell'oggettoMarsProperty
:
app:imageUrl="@{property.imgSrcUrl}"
- Apri
overview/OverviewFragment.kt
. InonCreateview()
, rimuovi il commento dalla riga che gonfiaFragmentOverviewBinding
. Elimina o commenta la riga che gonfiaGridViewBinding
. Queste modifiche annullano le modifiche temporanee apportate nell'ultima attività.
val binding = FragmentOverviewBinding.inflate(inflater)
// val binding = GridViewItemBinding.inflate(inflater)
- Apri
res/layout/fragment_overview.xml
. Elimina l'intero elemento<TextView>
. - Aggiungi invece questo elemento
<RecyclerView>
, che utilizza un layoutGridLayoutManager
egrid_view_item
per un singolo elemento:
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/photos_grid"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="6dp"
android:clipToPadding="false"
app:layoutManager=
"androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:spanCount="2"
tools:itemCount="16"
tools:listitem="@layout/grid_view_item" />
Passaggio 3: aggiungi l'adattatore per griglia di foto
Ora il layout fragment_overview
ha un RecyclerView
, mentre il layout grid_view_item
ha un solo ImageView
. In questo passaggio, colleghi i dati a RecyclerView
tramite un adattatore RecyclerView
.
- Apri
overview/PhotoGridAdapter.kt
. - Crea la classe
PhotoGridAdapter
con i parametri del costruttore mostrati di seguito. La classePhotoGridAdapter
estendeListAdapter
, il cui costruttore richiede il tipo di elemento dell'elenco, il view holder e un'implementazioneDiffUtil.ItemCallback
.
Importa i corsiandroidx.recyclerview.widget.ListAdapter
ecom.example.android.marsrealestate.network.MarsProperty
quando richiesto. Nei passaggi successivi, implementa le altre parti mancanti di questo costruttore che generano errori.
class PhotoGridAdapter : ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
}
- Fai clic in un punto qualsiasi della classe
PhotoGridAdapter
e premiControl+i
per implementare i metodiListAdapter
, ovveroonCreateViewHolder()
eonBindViewHolder()
.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoGridAdapter.MarsPropertyViewHolder {
TODO("not implemented")
}
override fun onBindViewHolder(holder: PhotoGridAdapter.MarsPropertyViewHolder, position: Int) {
TODO("not implemented")
}
- Alla fine della definizione della classe
PhotoGridAdapter
, dopo i metodi appena aggiunti, aggiungi una definizione di oggetto companion perDiffCallback
, come mostrato di seguito.
Importaandroidx.recyclerview.widget.DiffUtil
quando richiesto.
L'oggettoDiffCallback
estendeDiffUtil.ItemCallback
con il tipo di oggetto che vuoi confrontare:MarsProperty
.
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}
- Premi
Control+i
per implementare i metodi di confronto per questo oggetto, ovveroareItemsTheSame()
eareContentsTheSame()
.
override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented")
}
override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
TODO("not implemented") }
- Per il metodo
areItemsTheSame()
, rimuovi TODO. Utilizza l'operatore di uguaglianza referenziale di Kotlin (===
), che restituiscetrue
se i riferimenti agli oggetti peroldItem
enewItem
sono gli stessi.
override fun areItemsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem === newItem
}
- Per
areContentsTheSame()
, utilizza l'operatore di uguaglianza standard solo sull'ID dioldItem
enewItem
.
override fun areContentsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem.id == newItem.id
}
- Sempre all'interno della classe
PhotoGridAdapter
, sotto l'oggetto complementare, aggiungi una definizione di classe interna perMarsPropertyViewHolder
, che estendeRecyclerView.ViewHolder
.
Importaandroidx.recyclerview.widget.RecyclerView
ecom.example.android.marsrealestate.databinding.GridViewItemBinding
quando richiesto.
Hai bisogno della variabileGridViewItemBinding
per associareMarsProperty
al layout, quindi passala aMarsPropertyViewHolder
. Poiché la classe baseViewHolder
richiede una vista nel suo costruttore, le passi la vista radice del binding.
class MarsPropertyViewHolder(private var binding:
GridViewItemBinding):
RecyclerView.ViewHolder(binding.root) {
}
- In
MarsPropertyViewHolder
, crea un metodobind()
che accetta un oggettoMarsProperty
come argomento e impostabinding.property
su quell'oggetto. ChiamaexecutePendingBindings()
dopo aver impostato la proprietà, in modo che l'aggiornamento venga eseguito immediatamente.
fun bind(marsProperty: MarsProperty) {
binding.property = marsProperty
binding.executePendingBindings()
}
- In
onCreateViewHolder()
, rimuovi TODO e aggiungi la riga mostrata di seguito. Importaandroid.view.LayoutInflater
quando richiesto.
Il metodoonCreateViewHolder()
deve restituire un nuovoMarsPropertyViewHolder
, creato gonfiandoGridViewItemBinding
e utilizzandoLayoutInflater
dal contestoViewGroup
principale.
return MarsPropertyViewHolder(GridViewItemBinding.inflate(
LayoutInflater.from(parent.context)))
- Nel metodo
onBindViewHolder()
, rimuovi TODO e aggiungi le righe mostrate di seguito. Qui chiamigetItem()
per ottenere l'oggettoMarsProperty
associato alla posizioneRecyclerView
corrente e poi passi questa proprietà al metodobind()
inMarsPropertyViewHolder
.
val marsProperty = getItem(position)
holder.bind(marsProperty)
Passaggio 4: aggiungi l'adattatore di rilegatura e collega le parti
Infine, utilizza un BindingAdapter
per inizializzare PhotoGridAdapter
con l'elenco di oggetti MarsProperty
. L'utilizzo di un BindingAdapter
per impostare i dati RecyclerView
fa sì che il binding dei dati osservi automaticamente LiveData
per l'elenco degli oggetti MarsProperty
. L'adattatore di binding viene chiamato automaticamente quando l'elenco MarsProperty
cambia.
- Apri
BindingAdapters.kt
. - Alla fine del file, aggiungi un metodo
bindRecyclerView()
che accetta unRecyclerView
e un elenco di oggettiMarsProperty
come argomenti. Aggiungi un'annotazione a questo metodo con@BindingAdapter
.
Importaandroidx.recyclerview.widget.RecyclerView
ecom.example.android.marsrealestate.network.MarsProperty
quando richiesto.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView,
data: List<MarsProperty>?) {
}
- All'interno della funzione
bindRecyclerView()
, esegui il cast direcyclerView.adapter
inPhotoGridAdapter
e chiamaadapter.submitList()
con i dati. Indica aRecyclerView
quando è disponibile un nuovo elenco.
Importa com.example.android.marsrealestate.overview.PhotoGridAdapter
quando richiesto.
val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)
- Apri
res/layout/fragment_overview.xml
. Aggiungi l'attributoapp:listData
all'elementoRecyclerView
e impostalo suviewmodel.properties
utilizzando l'associazione dei dati.
app:listData="@{viewModel.properties}"
- Apri
overview/OverviewFragment.kt
. InonCreateView()
, appena prima della chiamata asetHasOptionsMenu()
, inizializza l'adattatoreRecyclerView
inbinding.photosGrid
a un nuovo oggettoPhotoGridAdapter
.
binding.photosGrid.adapter = PhotoGridAdapter()
- Esegui l'app. Dovresti vedere una griglia di immagini di
MarsProperty
. Mentre scorri per visualizzare le nuove immagini, l'app mostra l'icona di avanzamento del caricamento prima di visualizzare l'immagine stessa. Se attivi la modalità aereo, le immagini che non sono ancora state caricate vengono visualizzate come icone di immagini non funzionanti.
L'app MarsRealEstate mostra l'icona di immagine non disponibile quando non è possibile recuperare un'immagine. Tuttavia, in assenza di rete, l'app mostra una schermata vuota.
Questa non è un'esperienza utente ottimale. In questa attività, aggiungi la gestione di base degli errori per dare all'utente un'idea migliore di ciò che sta succedendo. Se internet non è disponibile, l'app mostrerà l'icona di errore di connessione. Mentre l'app recupera l'elenco MarsProperty
, viene visualizzata l'animazione di caricamento.
Passaggio 1: aggiungi lo stato al modello di visualizzazione
Per iniziare, crea un LiveData
nel modello di visualizzazione per rappresentare lo stato della richiesta web. Esistono tre stati da considerare: caricamento, riuscito e non riuscito. Lo stato di caricamento si verifica mentre attendi i dati nella chiamata a await()
.
- Apri
overview/OverviewViewModel.kt
. Nella parte superiore del file (dopo le importazioni, prima della definizione della classe), aggiungi unenum
per rappresentare tutti gli stati disponibili:
enum class MarsApiStatus { LOADING, ERROR, DONE }
- Rinomina le definizioni dei dati live interni ed esterni
_response
in tutta la classeOverviewViewModel
in_status
. Poiché in precedenza in questo codelab hai aggiunto il supporto per_properties
LiveData
, la risposta completa del servizio web non è stata utilizzata. Qui hai bisogno di unLiveData
per tenere traccia dello stato attuale, quindi puoi semplicemente rinominare le variabili esistenti.
Inoltre, modifica i tipi da String
a MarsApiStatus.
private val _status = MutableLiveData<MarsApiStatus>()
val status: LiveData<MarsApiStatus>
get() = _status
- Scorri verso il basso fino al metodo
getMarsRealEstateProperties()
e aggiorna_response
a_status
anche qui. Modifica la stringa"Success"
in modo che corrisponda allo statoMarsApiStatus.DONE
e la stringa"Failure"
in modo che corrisponda aMarsApiStatus.ERROR
. - Aggiungi uno stato
MarsApiStatus.LOADING
all'inizio del bloccotry {}
, prima della chiamata aawait()
. Questo è lo stato iniziale mentre la coroutine è in esecuzione e stai aspettando i dati. Il bloccotry/catch {}
completo ora ha il seguente aspetto:
try {
_status.value = MarsApiStatus.LOADING
var listResult = getPropertiesDeferred.await()
_status.value = MarsApiStatus.DONE
_properties.value = listResult
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
}
- Dopo lo stato di errore nel blocco
catch {}
, imposta_properties
LiveData
su un elenco vuoto. In questo modo,RecyclerView
.
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
_properties.value = ArrayList()
}
Passaggio 2: aggiungi un adattatore di binding per ImageView di stato
Ora hai uno stato nel modello di visualizzazione, ma si tratta solo di un insieme di stati. Come si fa a farlo apparire nell'app stessa? In questo passaggio, utilizzi un ImageView
, collegato al data binding, per visualizzare le icone per gli stati di caricamento ed errore. Quando l'app è in stato di caricamento o di errore, ImageView
dovrebbe essere visibile. Al termine del caricamento dell'app, ImageView
dovrebbe essere invisibile.
- Apri
BindingAdapters.kt
. Aggiungi un nuovo adattatore di binding chiamatobindStatus()
che accetta un valoreImageView
e un valoreMarsApiStatus
come argomenti. Importacom.example.android.marsrealestate.overview.MarsApiStatus
quando richiesto.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView,
status: MarsApiStatus?) {
}
- Aggiungi un
when {}
all'interno del metodobindStatus()
per passare da uno stato all'altro.
when (status) {
}
- All'interno di
when {}
, aggiungi un caso per lo stato di caricamento (MarsApiStatus.LOADING
). Per questo stato, impostaImageView
su visibile e assegna l'animazione di caricamento. Si tratta dello stesso drawable di animazione che hai utilizzato per Glide nell'attività precedente. Importaandroid.view.View
quando richiesto.
when (status) {
MarsApiStatus.LOADING -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.loading_animation)
}
}
- Aggiungi un caso per lo stato di errore, ovvero
MarsApiStatus.ERROR
. Analogamente a quanto fatto per lo statoLOADING
, imposta lo statoImageView
su visibile e riutilizza la risorsa disegnabile di errore di connessione.
MarsApiStatus.ERROR -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.ic_connection_error)
}
- Aggiungi un caso per lo stato completato, ovvero
MarsApiStatus.DONE
. In questo caso la risposta ha esito positivo, quindi disattiva la visibilità dello statoImageView
per nasconderlo.
MarsApiStatus.DONE -> {
statusImageView.visibility = View.GONE
}
Passaggio 3: aggiungi ImageView di stato al layout
- Apri
res/layout/fragment_overview.xml
. Sotto l'elementoRecyclerView
, all'interno diConstraintLayout
, aggiungiImageView
mostrato di seguito.
QuestoImageView
ha gli stessi vincoli diRecyclerView
. Tuttavia, la larghezza e l'altezza utilizzanowrap_content
per centrare l'immagine anziché allungarla per riempire la visualizzazione. Nota anche l'attributoapp:marsApiStatus
, che chiama la visualizzazioneBindingAdapter
quando la proprietà di stato nel modello di visualizzazione cambia.
<ImageView
android:id="@+id/status_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:marsApiStatus="@{viewModel.status}" />
- Attiva la modalità aereo nell'emulatore o sul dispositivo per simulare una connessione di rete mancante. Compila ed esegui l'app e nota che viene visualizzata l'immagine di errore:
- Tocca il pulsante Indietro per chiudere l'app e disattivare la modalità aereo. Utilizza la schermata delle app recenti per tornare all'app. A seconda della velocità della connessione di rete, potresti vedere un indicatore di caricamento molto breve quando l'app esegue una query sul servizio web prima che le immagini inizino a caricarsi.
Progetto Android Studio: MarsRealEstateGrid
- Per semplificare il processo di gestione delle immagini, utilizza la libreria Glide per scaricare, memorizzare nel buffer, decodificare e memorizzare nella cache le immagini nella tua app.
- Per caricare un'immagine da internet, Glide ha bisogno di due cose: l'URL di un'immagine e un oggetto
ImageView
in cui inserirla. Per specificare queste opzioni, utilizza i metodiload()
einto()
con Glide. - Gli adattatori di binding sono metodi di estensione che si trovano tra una visualizzazione e i dati associati. Gli adattatori di binding forniscono un comportamento personalizzato quando i dati cambiano, ad esempio per chiamare Glide per caricare un'immagine da un URL in un
ImageView
. - Gli adattatori di binding sono metodi di estensione annotati con l'annotazione
@BindingAdapter
. - Per aggiungere opzioni alla richiesta Glide, utilizza il metodo
apply()
. Ad esempio, utilizzaapply()
conplaceholder()
per specificare una risorsa disegnabile di caricamento eapply()
conerror()
per specificare una risorsa disegnabile di errore. - Per produrre una griglia di immagini, utilizza un
RecyclerView
con unGridLayoutManager
. - Per aggiornare l'elenco delle proprietà quando cambia, utilizza un adattatore di binding tra
RecyclerView
e il layout.
Corso Udacity:
Documentazione per sviluppatori Android:
- Panoramica di ViewModel
- Panoramica di LiveData
- Coroutine, documentazione ufficiale
- Adattatori per attacchi
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
Quale metodo Glide utilizzi per indicare il ImageView
che conterrà l'immagine caricata?
▢ into()
▢ with()
▢ imageview()
▢ apply()
Domanda 2
Come si specifica un'immagine segnaposto da mostrare durante il caricamento di Glide?
▢ Utilizza il metodo into()
con una risorsa disegnabile.
▢ Utilizza RequestOptions()
e chiama il metodo placeholder()
con una risorsa disegnabile.
▢ Assegna la proprietà Glide.placeholder
a una risorsa disegnabile.
▢ Utilizza RequestOptions()
e chiama il metodo loadingImage()
con una risorsa disegnabile.
Domanda 3
Come si indica che un metodo è un adattatore di binding?
▢ Chiama il metodo setBindingAdapter()
su LiveData
.
▢ Inserisci il metodo in un file Kotlin denominato BindingAdapters.kt
.
▢ Utilizza l'attributo android:adapter
nel layout XML.
▢ Annota il metodo con @BindingAdapter
.
Inizia la lezione successiva:
Per i link ad altri codelab di questo corso, consulta la pagina di destinazione dei codelab di Android Kotlin Fundamentals.