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
Una delle priorità principali per creare un'esperienza utente impeccabile per la tua app è assicurarsi che l'UI sia sempre reattiva e funzioni senza problemi. Un modo per migliorare le prestazioni della UI è spostare in background le attività a esecuzione prolungata, come le operazioni sui database.
In questo codelab, implementerai la parte rivolta agli utenti dell'app TrackMySleepQuality, utilizzando le coroutine Kotlin per eseguire operazioni sul database al di fuori del thread principale.
Cosa devi già sapere
Devi avere familiarità con:
- Creazione di un'interfaccia utente (UI) di base utilizzando un'attività, frammenti, visualizzazioni e gestori di clic.
- Spostarsi tra i fragment e utilizzare
safeArgs
per passare dati semplici tra i fragment. - Visualizza modelli, fabbriche di modelli, trasformazioni e
LiveData
. - Come creare un database
Room
, creare un DAO e definire le entità. - È utile se hai familiarità con i concetti di threading e multiprocessing.
Obiettivi didattici
- Come funzionano i thread in Android.
- Come utilizzare le coroutine Kotlin per spostare le operazioni del database dal thread principale.
- Come visualizzare i dati formattati in un
TextView
.
In questo lab proverai a:
- Estendi l'app TrackMySleepQuality per raccogliere, archiviare e visualizzare i dati nel database e dal database.
- Utilizza le coroutine per eseguire operazioni di database a lunga esecuzione in background.
- Utilizza
LiveData
per attivare la navigazione e la visualizzazione di una snackbar. - Usa
LiveData
per attivare e disattivare i pulsanti.
In questo codelab, creerai il view model, le coroutine e le sezioni di visualizzazione dei dati dell'app TrackMySleepQuality.
L'app ha due schermate, rappresentate da frammenti, come mostrato nella figura seguente.
La prima schermata, mostrata a sinistra, ha pulsanti per avviare e interrompere il monitoraggio. La schermata mostra tutti i dati sul sonno dell'utente. Il pulsante Cancella elimina definitivamente tutti i dati raccolti dall'app per l'utente.
La seconda schermata, mostrata a destra, serve per selezionare una valutazione della qualità del sonno. Nell'app, la valutazione è rappresentata numericamente. A scopo di sviluppo, l'app mostra sia le icone delle facce sia i relativi equivalenti numerici.
Il flusso dell'utente è il seguente:
- L'utente apre l'app e visualizza la schermata di monitoraggio del sonno.
- L'utente tocca il pulsante Avvia. Viene registrata l'ora di inizio e visualizzata. Il pulsante Avvia è disattivato e il pulsante Interrompi è attivato.
- L'utente tocca il pulsante Interrompi. Viene registrata l'ora di fine e si apre la schermata della qualità del sonno.
- L'utente seleziona un'icona della qualità del sonno. La schermata si chiude e la schermata di monitoraggio mostra l'ora di fine del sonno e la qualità del sonno. Il pulsante Interrompi è disattivato e il pulsante Avvia è attivato. L'app è pronta per un'altra notte.
- Il pulsante Cancella è attivo ogni volta che sono presenti dati nel database. Quando l'utente tocca il pulsante Cancella, tutti i suoi dati vengono cancellati senza possibilità di recupero. Non viene visualizzato alcun messaggio di conferma.
Questa app utilizza un'architettura semplificata, come mostrato di seguito nel contesto dell'architettura completa. L'app utilizza solo i seguenti componenti:
- Controller UI
- Visualizza modello e
LiveData
- Un database delle stanze
In questa attività, utilizzerai un TextView
per visualizzare i dati di monitoraggio del sonno formattati. (Questa non è l'interfaccia finale. In un altro codelab imparerai un modo migliore.)
Puoi continuare con l'app TrackMySleepQuality che hai creato nel codelab precedente o scaricare l'app iniziale per questo codelab.
Passaggio 1: scarica ed esegui l'app iniziale
- Scarica l'app TrackMySleepQuality-Coroutines-Starter da GitHub.
- Crea ed esegui l'app. L'app mostra l'interfaccia utente per il fragment
SleepTrackerFragment
, ma nessun dato. I pulsanti non rispondono ai tocchi.
Passaggio 2: esamina il codice
Il codice iniziale di questo codelab è lo stesso del codice della soluzione del codelab 6.1 Crea un database Room.
- Apri res/layout/activity_main.xml. Questo layout contiene il fragmento
nav_host_fragment
. Nota anche il tag<merge>
.
Il tagmerge
può essere utilizzato per eliminare i layout ridondanti durante l'inclusione dei layout ed è consigliabile utilizzarlo. Un esempio di layout ridondante è ConstraintLayout > LinearLayout > TextView, in cui il sistema potrebbe essere in grado di eliminare LinearLayout. Questo tipo di ottimizzazione può semplificare la gerarchia delle visualizzazioni e migliorare il rendimento dell'app. - Nella cartella navigation, apri navigation.xml. Puoi visualizzare due frammenti e le azioni di navigazione che li collegano.
- Nella cartella layout, fai doppio clic sul frammento del monitoraggio del sonno per visualizzarne il layout XML. Tieni presente quanto segue:
- I dati del layout sono racchiusi in un elemento
<layout>
per consentire l'associazione di dati. ConstraintLayout
e le altre visualizzazioni sono disposte all'interno dell'elemento<layout>
.- Il file ha un tag segnaposto
<data>
.
L'app iniziale fornisce anche dimensioni, colori e stile per la UI. L'app contiene un database Room
, un DAO e un'entità SleepNight
. Se non hai completato il codelab precedente, assicurati di esplorare questi aspetti del codice in autonomia.
Ora che hai un database e una UI, devi raccogliere i dati, aggiungerli al database e visualizzarli. Tutto questo lavoro viene svolto nel modello di visualizzazione. Il modello di visualizzazione del tracker del sonno gestirà i clic sui pulsanti, interagirà con il database tramite il DAO e fornirà i dati alla UI tramite LiveData
. Tutte le operazioni del database dovranno essere eseguite lontano dal thread UI principale e lo farai utilizzando le coroutine.
Passaggio 1: aggiungi SleepTrackerViewModel
- Nel pacchetto sleeptracker, apri SleepTrackerViewModel.kt.
- Esamina la classe
SleepTrackerViewModel
, fornita nell'app iniziale e mostrata anche di seguito. Tieni presente che la classe estendeAndroidViewModel()
. Questa classe è uguale aViewModel
, ma accetta il contesto dell'applicazione come parametro e lo rende disponibile come proprietà. Ti servirà in un secondo momento.
class SleepTrackerViewModel(
val database: SleepDatabaseDao,
application: Application) : AndroidViewModel(application) {
}
Passaggio 2: aggiungi SleepTrackerViewModelFactory
- Nel pacchetto sleeptracker, apri SleepTrackerViewModelFactory.kt.
- Esamina il codice fornito per la fabbrica, mostrato di seguito:
class SleepTrackerViewModelFactory(
private val dataSource: SleepDatabaseDao,
private val application: Application) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(SleepTrackerViewModel::class.java)) {
return SleepTrackerViewModel(dataSource, application) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
Tieni presente quanto segue:
- Il
SleepTrackerViewModelFactory
fornito accetta lo stesso argomento diViewModel
ed estendeViewModelProvider.Factory
. - All'interno della fabbrica, il codice esegue l'override di
create()
, che accetta qualsiasi tipo di classe come argomento e restituisce unViewModel
. - Nel corpo di
create()
, il codice controlla che sia disponibile una classeSleepTrackerViewModel
e, in caso affermativo, ne restituisce un'istanza. In caso contrario, il codice genera un'eccezione.
Passaggio 3: aggiorna SleepTrackerFragment
- In
SleepTrackerFragment
, ottieni un riferimento al contesto dell'applicazione. Inserisci il riferimento inonCreateView()
, sottobinding
. Per passare al provider di factory del view model, devi fare riferimento all'app a cui è collegato questo fragment.
La funzione KotlinrequireNotNull
genera un'eccezioneIllegalArgumentException
se il valore ènull
.
val application = requireNotNull(this.activity).application
- Devi fare riferimento all'origine dati tramite un riferimento al DAO. In
onCreateView()
, prima direturn
, definisci undataSource
. Per ottenere un riferimento al DAO del database, utilizzaSleepDatabase.getInstance(application).sleepDatabaseDao
.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
- In
onCreateView()
, prima direturn
, crea un'istanza diviewModelFactory
. Devi superare il testdataSource
e il testapplication
.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
- Ora che hai una factory, ottieni un riferimento a
SleepTrackerViewModel
. Il parametroSleepTrackerViewModel::class.java
fa riferimento alla classe Java di runtime di questo oggetto.
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
- Il codice finale dovrebbe avere questo aspetto:
// Create an instance of the ViewModel Factory.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
// Get a reference to the ViewModel associated with this fragment.
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
Ecco il metodo onCreateView()
finora:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Get a reference to the binding object and inflate the fragment views.
val binding: FragmentSleepTrackerBinding = DataBindingUtil.inflate(
inflater, R.layout.fragment_sleep_tracker, container, false)
val application = requireNotNull(this.activity).application
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
return binding.root
}
Passaggio 4: aggiungi l'associazione di dati per il modello di visualizzazione
Con il ViewModel
di base in posizione, devi completare la configurazione del data binding nel SleepTrackerFragment
per connettere il ViewModel
all'interfaccia utente.
Nel file di layout fragment_sleep_tracker.xml
:
- All'interno del blocco
<data>
, crea un<variable>
che faccia riferimento alla classeSleepTrackerViewModel
.
<data>
<variable
name="sleepTrackerViewModel"
type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>
In SleepTrackerFragment
:
- Imposta l'attività corrente come proprietario del ciclo di vita del binding. Aggiungi questo codice all'interno del metodo
onCreateView()
, prima dell'istruzionereturn
:
binding.setLifecycleOwner(this)
- Assegna la variabile di binding
sleepTrackerViewModel
asleepTrackerViewModel
. Inserisci questo codice all'interno dionCreateView()
, sotto il codice che creaSleepTrackerViewModel
:
binding.sleepTrackerViewModel = sleepTrackerViewModel
- Probabilmente visualizzerai un errore perché devi ricreare l'oggetto di binding. Pulisci e ricompila il progetto per eliminare l'errore.
- Infine, come sempre, assicurati che la build e l'esecuzione del codice avvengano senza errori.
In Kotlin, le coroutine sono il modo per gestire le attività a lunga esecuzione in modo elegante ed efficiente. Le coroutine Kotlin ti consentono di convertire il codice basato su callback in codice sequenziale. Il codice scritto in sequenza è in genere più facile da leggere e può persino utilizzare funzionalità del linguaggio come le eccezioni. Alla fine, le coroutine e i callback fanno la stessa cosa: attendono che un risultato sia disponibile da un'attività a esecuzione prolungata e continuano l'esecuzione.
Le coroutine hanno le seguenti proprietà:
- Le coroutine sono asincrone e non bloccanti.
- Le coroutine utilizzano le funzioni suspend per rendere sequenziale il codice asincrono.
Le coroutine sono asincrone.
Una coroutine viene eseguita indipendentemente dai passaggi di esecuzione principali del programma. Potrebbe essere in parallelo o su un processore separato. Potrebbe anche succedere che, mentre il resto dell'app è in attesa di input, tu esegua un po' di elaborazione. Uno degli aspetti importanti di async è che non puoi aspettarti che il risultato sia disponibile finché non lo attendi esplicitamente.
Ad esempio, supponiamo che tu abbia una domanda che richiede una ricerca e chiedi a un collega di trovare la risposta. Vanno avanti e ci lavorano, come se stessero lavorando "in modo asincrono" e "su un thread separato". Puoi continuare a svolgere altre attività che non dipendono dalla risposta, finché il tuo collega non torna e ti comunica la risposta.
Le coroutine non sono bloccanti.
Non bloccante significa che una coroutine non blocca il thread principale o dell'interfaccia utente. In questo modo, con le coroutine, gli utenti hanno sempre l'esperienza più fluida possibile, perché l'interazione con la UI ha sempre la priorità.
Le coroutine utilizzano le funzioni di sospensione per rendere sequenziale il codice asincrono.
La parola chiave suspend
è il modo in cui Kotlin contrassegna una funzione o un tipo di funzione come disponibile per le coroutine. Quando una coroutine chiama una funzione contrassegnata con suspend
, anziché bloccarsi fino a quando la funzione non restituisce un valore come una normale chiamata di funzione, la coroutine sospende l'esecuzione fino a quando il risultato non è pronto. La coroutine riprende quindi da dove era stata interrotta, con il risultato.
Mentre la coroutine è sospesa e in attesa di un risultato, sblocca il thread su cui è in esecuzione. In questo modo, possono essere eseguite altre funzioni o coroutine.
La parola chiave suspend
non specifica il thread su cui viene eseguito il codice. Una funzione di sospensione può essere eseguita su un thread in background o sul thread principale.
Per utilizzare le coroutine in Kotlin, sono necessarie tre cose:
- Un lavoro
- Un dispatcher
- Un ambito
Job: in sostanza, un job è qualsiasi cosa possa essere annullata. Ogni coroutine ha un job e puoi utilizzare il job per annullare la coroutine. I job possono essere organizzati in gerarchie principale-secondario. L'annullamento di un job padre annulla immediatamente tutti i job secondari, il che è molto più comodo dell'annullamento manuale di ogni coroutine.
Dispatcher : il dispatcher invia le coroutine da eseguire su vari thread. Ad esempio, Dispatcher.Main
esegue le attività sul thread principale, mentre Dispatcher.IO
scarica le attività di I/O di blocco in un pool condiviso di thread.
Ambito : l'ambito di una coroutine definisce il contesto in cui viene eseguita. Un ambito combina informazioni sul job e sul dispatcher di una coroutine. Gli ambiti tengono traccia delle coroutine. Quando avvii una coroutine, questa si trova "in un ambito", il che significa che hai indicato quale ambito terrà traccia della coroutine.
Vuoi che l'utente possa interagire con i dati sul sonno nei seguenti modi:
- Quando l'utente tocca il pulsante Inizia, l'app crea una nuova notte di sonno e la memorizza nel database.
- Quando l'utente tocca il pulsante Interrompi, l'app aggiorna la notte con un orario di fine.
- Quando l'utente tocca il pulsante Cancella, l'app cancella i dati nel database.
Queste operazioni sul database possono richiedere molto tempo, quindi devono essere eseguite su un thread separato.
Passaggio 1: configura le coroutine per le operazioni del database
Quando viene toccato il pulsante Avvia nell'app Monitoraggio del sonno, vuoi chiamare una funzione in SleepTrackerViewModel
per creare una nuova istanza di SleepNight
e memorizzarla nel database.
Se tocchi uno dei pulsanti, viene attivata un'operazione di database, ad esempio la creazione o l'aggiornamento di un SleepNight
. Per questo e altri motivi, utilizzi le coroutine per implementare i gestori di clic per i pulsanti dell'app.
- Apri il file
build.gradle
a livello di app e trova le dipendenze per le coroutine. Per utilizzare le coroutine, hai bisogno di queste dipendenze, che sono state aggiunte per te.$coroutine_version
è definito nel filebuild.gradle
del progetto comecoroutine_version =
'1.0.0'
.
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
- Apri il file
SleepTrackerViewModel
. - Nel corpo della classe, definisci
viewModelJob
e assegnagli un'istanza diJob
. QuestoviewModelJob
ti consente di annullare tutte le coroutine avviate da questo modello di visualizzazione quando il modello di visualizzazione non viene più utilizzato e viene eliminato. In questo modo, non avrai coroutine che non hanno un punto di ritorno.
private var viewModelJob = Job()
- Alla fine del corpo della classe, esegui l'override di
onCleared()
e annulla tutte le coroutine. QuandoViewModel
viene eliminato, viene chiamatoonCleared()
.
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
- Subito sotto la definizione di
viewModelJob
, definisci unuiScope
per le coroutine. L'ambito determina il thread su cui verrà eseguita la coroutine e deve anche conoscere il job. Per ottenere un ambito, richiedi un'istanza diCoroutineScope
e trasmetti un dispatcher e un job.
L'utilizzo di Dispatchers.Main
significa che le coroutine avviate in uiScope
verranno eseguite sul thread principale. Questo è ragionevole per molte coroutine avviate da un ViewModel
, perché dopo che queste coroutine eseguono un'elaborazione, comportano un aggiornamento della UI.
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
- Sotto la definizione di
uiScope
, definisci una variabile chiamatatonight
per contenere la notte corrente. Imposta la variabile suMutableLiveData
, perché devi poter osservare i dati e modificarli.
private var tonight = MutableLiveData<SleepNight?>()
- Per inizializzare la variabile
tonight
il prima possibile, crea un bloccoinit
sotto la definizione ditonight
e chiamainitializeTonight()
. DefinisciinitializeTonight()
nel passaggio successivo.
init {
initializeTonight()
}
- Sotto il blocco
init
, implementainitializeTonight()
. InuiScope
, avvia una coroutine. All'interno, ottieni il valore ditonight
dal database chiamandogetTonightFromDatabase()
e assegna il valore atonight.value
. DefiniscigetTonightFromDatabase()
nel passaggio successivo.
private fun initializeTonight() {
uiScope.launch {
tonight.value = getTonightFromDatabase()
}
}
- Implementa
getTonightFromDatabase()
. Definisci questa funzione comeprivate suspend
che restituisce un valoreSleepNight
nullable, se non è presente alcunSleepNight
avviato. In questo modo, si verifica un errore perché la funzione deve restituire qualcosa.
private suspend fun getTonightFromDatabase(): SleepNight? { }
- All'interno del corpo della funzione di
getTonightFromDatabase()
, restituisci il risultato di una coroutine eseguita nel contestoDispatchers.IO
. Utilizza il dispatcher I/O, perché il recupero dei dati dal database è un'operazione di I/O e non ha nulla a che fare con la UI.
return withContext(Dispatchers.IO) {}
- All'interno del blocco return, lascia che la coroutine recuperi la notte più recente dal database. Se gli orari di inizio e di fine non coincidono, il che significa che la notte è già terminata, restituisci
null
. In caso contrario, restituisci la notte.
var night = database.getTonight()
if (night?.endTimeMilli != night?.startTimeMilli) {
night = null
}
night
La funzione di sospensione getTonightFromDatabase()
completata dovrebbe avere questo aspetto. Non dovrebbero essere presenti altri errori.
private suspend fun getTonightFromDatabase(): SleepNight? {
return withContext(Dispatchers.IO) {
var night = database.getTonight()
if (night?.endTimeMilli != night?.startTimeMilli) {
night = null
}
night
}
}
Passaggio 2: aggiungi il gestore di clic per il pulsante Avvia
Ora puoi implementare onStartTracking()
, il gestore dei clic per il pulsante Avvia. Devi creare un nuovo SleepNight
, inserirlo nel database e assegnarlo a tonight
. La struttura di onStartTracking()
sarà molto simile a quella di initializeTonight()
.
- Inizia con la definizione della funzione per
onStartTracking()
. Puoi inserire i gestori dei clic sopraonCleared()
nel fileSleepTrackerViewModel
.
fun onStartTracking() {}
- All'interno di
onStartTracking()
, avvia una coroutine inuiScope
, perché hai bisogno di questo risultato per continuare e aggiornare la UI.
uiScope.launch {}
- All'interno dell'avvio della coroutine, crea un nuovo
SleepNight
, che acquisisce l'ora corrente come ora di inizio.
val newNight = SleepNight()
- Sempre all'interno dell'avvio della coroutine, chiama
insert()
per inserirenewNight
nel database. Visualizzerai un errore perché non hai ancora definito questa funzione di sospensioneinsert()
. (Questa non è la funzione DAO con lo stesso nome.)
insert(newNight)
- Inoltre, all'interno dell'avvio della coroutine, aggiorna
tonight
.
tonight.value = getTonightFromDatabase()
- Sotto
onStartTracking()
, definisciinsert()
come funzioneprivate suspend
che accettaSleepNight
come argomento.
private suspend fun insert(night: SleepNight) {}
- Per il corpo di
insert()
, avvia una coroutine nel contesto I/O e inserisci la notte nel database chiamandoinsert()
dal DAO.
withContext(Dispatchers.IO) {
database.insert(night)
}
- Nel file di layout
fragment_sleep_tracker.xml
, aggiungi il gestore dei clic peronStartTracking()
astart_button
utilizzando la magia del data binding che hai configurato in precedenza. La notazione della funzione@{() ->
crea una funzione lambda che non accetta argomenti e chiama il gestore dei clic insleepTrackerViewModel
.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
- Crea ed esegui la tua app. Tocca il pulsante Avvia. Questa azione crea dati, ma non puoi ancora vedere nulla. Lo correggerai nel passaggio successivo.
fun someWorkNeedsToBeDone { uiScope.launch { suspendFunction() } } suspend fun suspendFunction() { withContext(Dispatchers.IO) { longrunningWork() } }
Passaggio 3: visualizza i dati
In SleepTrackerViewModel
, la variabile nights
fa riferimento a LiveData
perché getAllNights()
nel DAO restituisce LiveData
.
Si tratta di una funzionalità di Room
che, ogni volta che i dati nel database cambiano, il LiveData
nights
viene aggiornato per mostrare i dati più recenti. Non devi mai impostare esplicitamente LiveData
o aggiornarlo. Room
aggiorna i dati in modo che corrispondano al database.
Tuttavia, se visualizzi nights
in una visualizzazione di testo, verrà mostrato il riferimento all'oggetto. Per visualizzare i contenuti dell'oggetto, trasforma i dati in una stringa formattata. Utilizza una mappa Transformation
eseguita ogni volta che nights
riceve nuovi dati dal database.
- Apri il file
Util.kt
e rimuovi il commento dal codice per la definizione diformatNights()
e dalle istruzioniimport
associate. Per rimuovere il commento dal codice in Android Studio, seleziona tutto il codice contrassegnato con//
e premiCmd+/
oControl+/
. - Tieni presente che
formatNights()
restituisce un tipoSpanned
, ovvero una stringa formattata in HTML. - Apri strings.xml. Nota l'utilizzo di
CDATA
per formattare le risorse stringa per la visualizzazione dei dati sul sonno. - Apri SleepTrackerViewModel. Nella classe
SleepTrackerViewModel
, sotto la definizione diuiScope
, definisci una variabile denominatanights
. Recupera tutte le notti dal database e assegnale alla variabilenights
.
private val nights = database.getAllNights()
- Subito sotto la definizione di
nights
, aggiungi il codice per trasformarenights
in unnightsString
. Utilizza la funzioneformatNights()
diUtil.kt
.
Passanights
nella funzionemap()
della classeTransformations
. Per accedere alle risorse stringa, definisci la funzione di mappatura come chiamata diformatNights()
. Forniscinights
e un oggettoResources
.
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
- Apri il file di layout
fragment_sleep_tracker.xml
. InTextView
, nella proprietàandroid:text
, ora puoi sostituire la stringa della risorsa con un riferimento anightsString
.
"@{sleepTrackerViewModel.nightsString}"
- Ricompila il codice ed esegui l'app. Ora dovrebbero essere visualizzati tutti i dati sul sonno con gli orari di inizio.
- Tocca il pulsante Inizia altre volte per visualizzare altri dati.
Nel passaggio successivo, attiverai la funzionalità del pulsante Stop.
Passaggio 4: aggiungi il gestore dei clic per il pulsante Stop
Utilizzando lo stesso pattern del passaggio precedente, implementa il gestore dei clic per il pulsante Interrompi in SleepTrackerViewModel.
- Aggiungi
onStopTracking()
aViewModel
. Avvia una coroutine inuiScope
. Se l'ora di fine non è ancora stata impostata, impostaendTimeMilli
sull'ora di sistema attuale e chiamaupdate()
con i dati notturni.
In Kotlin, la sintassireturn@
label
specifica la funzione da cui viene restituita questa istruzione, tra diverse funzioni nidificate.
fun onStopTracking() {
uiScope.launch {
val oldNight = tonight.value ?: return@launch
oldNight.endTimeMilli = System.currentTimeMillis()
update(oldNight)
}
}
- Implementa
update()
utilizzando lo stesso pattern che hai utilizzato per implementareinsert()
.
private suspend fun update(night: SleepNight) {
withContext(Dispatchers.IO) {
database.update(night)
}
}
- Per connettere il gestore dei clic all'interfaccia utente, apri il file di layout
fragment_sleep_tracker.xml
e aggiungi il gestore dei clic astop_button
.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
- Crea ed esegui la tua app.
- Tocca Avvia, quindi tocca Interrompi. Vedi l'ora di inizio, l'ora di fine, la qualità del sonno senza valore e il tempo di sonno.
Passaggio 5: aggiungi il gestore dei clic per il pulsante Cancella
- Allo stesso modo, implementa
onClear()
eclear()
.
fun onClear() {
uiScope.launch {
clear()
tonight.value = null
}
}
suspend fun clear() {
withContext(Dispatchers.IO) {
database.clear()
}
}
- Per connettere il gestore dei clic alla UI, apri
fragment_sleep_tracker.xml
e aggiungi il gestore dei clic aclear_button
.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
- Crea ed esegui la tua app.
- Tocca Cancella per eliminare tutti i dati. Quindi tocca Avvia e Interrompi per creare nuovi dati.
Progetto Android Studio: TrackMySleepQualityCoroutines
- Utilizza
ViewModel
,ViewModelFactory
e il data binding per configurare l'architettura dell'interfaccia utente per l'app. - Per garantire il corretto funzionamento della UI, utilizza le coroutine per le attività a lunga esecuzione, come tutte le operazioni del database.
- Le coroutine sono asincrone e non bloccanti. Utilizzano le funzioni
suspend
per rendere sequenziale il codice asincrono. - Quando una coroutine chiama una funzione contrassegnata con
suspend
, anziché bloccarsi fino a quando la funzione non restituisce un valore come una normale chiamata di funzione, sospende l'esecuzione fino a quando il risultato non è pronto. Poi riprende da dove era stato interrotto con il risultato. - La differenza tra blocco e sospensione è che se un thread viene bloccato, non viene eseguito altro lavoro. Se il thread viene sospeso, vengono eseguite altre operazioni finché il risultato non è disponibile.
Per avviare una coroutine, hai bisogno di un job, un dispatcher e uno scope:
- In sostanza, un job è qualsiasi cosa possa essere annullata. Ogni coroutine ha un job e puoi utilizzare un job per annullare una coroutine.
- Il dispatcher invia le coroutine da eseguire su vari thread.
Dispatcher.Main
esegue le attività sul thread principale, mentreDispartcher.IO
serve per delegare le attività di I/O di blocco a un pool condiviso di thread. - L'ambito combina informazioni, tra cui un lavoro e un dispatcher, per definire il contesto in cui viene eseguita la coroutine. Gli ambiti tengono traccia delle coroutine.
Per implementare i gestori di clic che attivano le operazioni del database, segui questo pattern:
- Avvia una coroutine che viene eseguita sul thread principale o dell'interfaccia utente, perché il risultato influisce sull'interfaccia utente.
- Chiama una funzione di sospensione per eseguire il lavoro a esecuzione prolungata, in modo da non bloccare il thread UI durante l'attesa del risultato.
- Il lavoro di lunga durata non ha nulla a che fare con la UI, quindi passa al contesto I/O. In questo modo, il lavoro può essere eseguito in un pool di thread ottimizzato e riservato a questo tipo di operazioni.
- Quindi chiama la funzione del database per eseguire l'operazione.
Utilizza una mappa Transformations
per creare una stringa da un oggetto LiveData
ogni volta che l'oggetto cambia.
Corso Udacity:
Documentazione per sviluppatori Android:
RoomDatabase
- Riutilizzo dei layout con <include/>
ViewModelProvider.Factory
SimpleDateFormat
HtmlCompat
Altri documenti e articoli:
- Pattern di fabbrica
- Codelab sulle coroutine
- Coroutine, documentazione ufficiale
- Contesto e dispatcher delle coroutine
Dispatchers
- Superare il limite di velocità di Android
Job
launch
- Restituzioni e salti in Kotlin
- CDATA sta per dati di caratteri. CDATA indica che i dati tra queste stringhe includono dati che potrebbero essere interpretati come markup XML, ma non dovrebbero esserlo.
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
Quali dei seguenti sono vantaggi delle coroutine:
- Non sono bloccanti
- Vengono eseguiti in modo asincrono.
- Possono essere eseguiti su un thread diverso da quello principale.
- Rendono sempre più veloce l'esecuzione delle app.
- Può utilizzare le eccezioni.
- Possono essere scritti e letti come codice lineare.
Domanda 2
Che cos'è una funzione di sospensione?
- Una funzione ordinaria annotata con la parola chiave
suspend
. - Una funzione che può essere chiamata all'interno delle coroutine.
- Mentre è in esecuzione una funzione di sospensione, il thread chiamante viene sospeso.
- Le funzioni di sospensione devono essere sempre eseguite in background.
Domanda 3
Qual è la differenza tra bloccare e sospendere un thread? Contrassegna tutte le risposte vere.
- Quando l'esecuzione è bloccata, non è possibile eseguire altre operazioni sul thread bloccato.
- Quando l'esecuzione viene sospesa, il thread può svolgere altre attività in attesa del completamento del lavoro delegato.
- La sospensione è più efficiente, perché i thread potrebbero non essere in attesa e non fare nulla.
- Che sia bloccata o sospesa, l'esecuzione è ancora in attesa del risultato della coroutine prima di continuare.
Inizia la lezione successiva:
Per i link ad altri codelab di questo corso, consulta la pagina di destinazione dei codelab di Android Kotlin Fundamentals.