Questo codelab fa parte del corso Android Kotlin Fundamentals. Otterrai il massimo valore da questo corso se lavori in sequenza nei codelab. Tutti i codelab del corso sono elencati nella pagina di destinazione di Android Kotlin Fundamentals.
Introduzione
Una delle priorità principali per creare un'esperienza utente impeccabile per la tua app è assicurarsi che l'interfaccia utente sia sempre reattiva e funzioni senza problemi. Un modo per migliorare le prestazioni dell'interfaccia utente è spostare le attività di lunga durata, come le operazioni di database, in background.
In questo codelab, implementerai la parte rivolta all'utente dell'app TrackMySleepQualità, utilizzando le coroutine Kotlin per eseguire operazioni del database lontano dal thread principale.
Informazioni importanti
Dovresti acquisire familiarità con:
- Creare un'interfaccia utente di base (UI) utilizzando un'attività, frammenti, viste e gestori dei clic.
- Spostamento tra frammenti e utilizzo di
safeArgs
per trasmettere dati semplici tra frammenti. - 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 thread e multi-elaborazione.
Obiettivi didattici
- Come funzionano i thread in Android.
- Come utilizzare le coroutine Kotlin per allontanare le operazioni di database dal thread principale.
- Come visualizzare i dati formattati in un
TextView
.
In questo lab proverai a:
- Estendi l'app TrackMySleepQualità per raccogliere, archiviare e visualizzare i dati all'interno e dal database.
- Utilizza coroutine per eseguire operazioni di database a lunga esecuzione in background.
- Utilizza
LiveData
per attivare la navigazione e la visualizzazione di uno snack bar. - Usa
LiveData
per attivare e disattivare i pulsanti.
In questo codelab, creerai il modello di visualizzazione, le coroutine e le parti di visualizzazione dei dati dell'app TrackMySleepQualità.
L'app ha due schermate, rappresentate da frammenti, come mostrato nella figura che segue.
La prima schermata, mostrata a sinistra, ha i pulsanti per avviare e interrompere il monitoraggio. Lo schermo mostra tutti i dati relativi al sonno dell'utente. Il pulsante Cancella elimina definitivamente tutti i dati raccolti dall'app per l'utente.
La seconda schermata, mostrata a destra, consente di selezionare una valutazione della qualità del sonno. Nell'app, la valutazione è rappresentata da un valore numerico. Ai fini dello sviluppo, l'app mostra sia le icone dei volti sia gli 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. Registra l'ora di inizio e la visualizza. Il pulsante Avvia è disattivato e il pulsante Interrompi è attivato.
- L'utente tocca il pulsante Interrompi. Registra l'ora di fine e apre la schermata relativa alla qualità del sonno.
- L'utente seleziona un'icona relativa alla qualità del sonno. Lo schermo si chiude e la schermata di monitoraggio mostra l'ora di fine e la qualità del sonno. Il pulsante Stop è disattivato e il pulsante Start è abilitato. L'app è pronta per un'altra notte.
- Il pulsante Cancella è abilitato ogni volta che nel database sono presenti dati. Quando l'utente tocca il pulsante Cancella, tutti i suoi dati vengono cancellati senza ricorrere a un ricorso: non c'è un messaggio di Sei sicuro,
Questa app utilizza un'architettura semplificata, come mostrato di seguito nel contesto dell'architettura completa. L'app utilizza solo i seguenti componenti:
- Controller dell'interfaccia utente
- Visualizza modello e
LiveData
- Un database delle stanze
In questa attività utilizzerai un TextView
per visualizzare i dati formattati per il monitoraggio del sonno. Questa non è l'interfaccia finale. Imparerai in modo migliore con un altro codelab.
Puoi continuare con l'app TrackMySleepQualità 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 TrackMySleepQualità-Coroutines-Starter da GitHub.
- Crea ed esegui l'app. L'app mostra l'UI del frammento
SleepTrackerFragment
, ma non i dati. I pulsanti non rispondono ai tocchi.
Passaggio 2: esamina il codice
Il codice di avvio di questo codelab è lo stesso del codice di soluzione per il codelab su 6.1 Create a Database del database.
- Apri res/layout/activity_main.xml. Questo layout contiene il frammento
nav_host_fragment
. Inoltre, tieni presente il tag<merge>
.
Il tagmerge
può essere utilizzato per eliminare i layout ridondanti quando sono inclusi i layout ed è una buona idea utilizzarlo. Un layout ridondante potrebbe essere ConstraintLayout > LinearLayout > TextView, dove il sistema potrebbe essere in grado di eliminare LinearLayout. Questo tipo di ottimizzazione può semplificare la gerarchia delle visualizzazioni e migliorare le prestazioni dell'app. - Nella cartella browsing, apri il file navigation.xml. Sono visibili due frammenti e le azioni di navigazione che li collegano.
- Nella cartella layout, fai doppio clic sul frammento del tracker del sonno per vedere il suo layout XML. Nota:
- I dati di layout vengono aggregati in un elemento
<layout>
per consentire l'associazione di dati. ConstraintLayout
e le altre viste sono organizzate all'interno dell'elemento<layout>
.- Il file ha un tag
<data>
segnaposto.
L'app iniziale fornisce anche dimensioni, colori e stili per l'interfaccia utente. L'app contiene un database Room
, un DAO e un'entità SleepNight
. Se non hai completato il codelab precedente, assicurati di esplorare questi elementi del codice autonomamente.
Ora che disponi di un database e di un'interfaccia utente, devi raccogliere i dati, aggiungerli al database e visualizzarli. Tutto questo avviene nel modello di visualizzazione. Il modello di visualizzazione del monitoraggio del sonno gestirà i clic sui pulsanti, interagirà con il database tramite il DAO e fornirà i dati all'interfaccia utente tramite LiveData
. Tutte le operazioni di database dovranno essere eseguite dal thread principale dell'interfaccia utente e puoi farlo utilizzando coroutine.
Passaggio 1: aggiungi SleepTrackerViewmodel
- Nel pacchetto sleeptracker, apri SleepTrackerViewmodel.kt.
- Esamina il corso
SleepTrackerViewModel
, che ti viene fornito nell'app Starter e è anche mostrato di seguito. Tieni presente che il corso si estende perAndroidViewModel()
. Questa classe è uguale aViewModel
, ma utilizza il contesto dell'applicazione come parametro e la 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 che ti abbiamo fornito per la fabbrica, indicato 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 valore fornito in
SleepTrackerViewModelFactory
prende lo stesso argomento diViewModel
ed estendeViewModelProvider.Factory
. - All'interno della fabbrica, il codice sostituisce
create()
, che accetta qualsiasi tipo di classe come argomento e restituisceViewModel
. - Nel corpo di
create()
, il codice verifica che sia disponibile una classeSleepTrackerViewModel
e, in caso affermativo, ne restituisce un'istanza. Altrimenti, il codice genera un'eccezione.
Passaggio 3: aggiorna il monitoraggio del sonno
- In
SleepTrackerFragment
, trova un riferimento al contesto dell'applicazione. Inserisci il riferimento inonCreateView()
, sottobinding
. Per passare al provider del modello di visualizzazione modello di visualizzazione, devi fare riferimento all'app a cui è collegato questo frammento.
La funzionerequireNotNull
Kotlin genera un elementoIllegalArgumentException
se il valore ènull
.
val application = requireNotNull(this.activity).application
- È necessario un 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 l'eventodataSource
eapplication
.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
- Ora che disponi di una fabbrica, rivolgiti a
SleepTrackerViewModel
. Il parametroSleepTrackerViewModel::class.java
si riferisce alla classe Java di runtime di questo oggetto.
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
- Il codice completo deve avere il seguente 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)
A oggi il metodo onCreateView()
è il seguente:
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
Dopo aver configurato ViewModel
di base, devi completare la configurazione dell'associazione di dati in SleepTrackerFragment
per collegare ViewModel
all'interfaccia utente.
Nel file di layout fragment_sleep_tracker.xml
:
- All'interno del blocco
<data>
, crea un elemento<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 dell'associazione. Aggiungi questo codice nel metodo
onCreateView()
, prima dell'istruzionereturn
:
binding.setLifecycleOwner(this)
- Assegna la variabile di associazione
sleepTrackerViewModel
asleepTrackerViewModel
. Inserisci questo codice all'interno dionCreateView()
, sotto il codice che crea ilSleepTrackerViewModel
:
binding.sleepTrackerViewModel = sleepTrackerViewModel
- Probabilmente vedrai un errore perché devi ricreare l'oggetto associazione. Pulisci e ricrea il progetto per eliminare l'errore.
- Infine, come sempre, assicurati che il codice venga creato e eseguito senza errori.
In Kotlin, le coroutine sono il modo migliore per gestire le attività di lunga durata. Le coroutine Kotlin consentono di convertire il codice basato su callback in un codice sequenziale. Il codice scritto in sequenza è generalmente più facile da leggere e può persino utilizzare funzionalità linguistiche come le eccezioni. Alla fine, le coroutine e i callback fanno la stessa cosa: attendono che un risultato sia disponibile da un'attività a lunga esecuzione e continui l'esecuzione.
Le coroutine hanno le seguenti proprietà:
- Le coroutine sono asincrone e non bloccanonti.
- Lecoroutine utilizzano le funzioni sospendi per rendere sequenziale il codice asincrono.
Le routine sono asincrone.
Una coroutine viene eseguita in modo indipendente dai passaggi di esecuzione principali del programma. La riproduzione potrebbe essere in parallelo o su un processore separato. Potrebbe anche darsi che il resto dell'app sia in attesa di input e che tu debba dare un'occhiata. Uno degli aspetti importanti dell'asinc è 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 che chiedi a un collega di trovare la risposta. E se ne andranno via, come se stessero facendo "il lavoro in modo asincrono" e "in un thread separato". Puoi continuare a svolgere un altro lavoro che non dipende dalla risposta, finché il tuo collega non rientra e non ti comunica qual è.
Le coroutine non bloccano la vita.
Non bloccante: una coroutine non blocca il thread principale o dell'interfaccia utente. Con le coroutine, gli utenti usufruiscono sempre dell'esperienza utente più fluida, in quanto l'interazione con l'interfaccia utente ha sempre la priorità.
Le coroutine utilizzano la funzione di sospensione per rendere il codice asincrono in sequenza.
La parola chiave suspend
è il modo in cui Kotlin è in grado di contrassegnare una funzione, o tipo di funzione, come disponibile per le coroutine. Quando una coroutine chiama una funzione contrassegnata con suspend
, invece di bloccarla finché non torna come una normale chiamata a funzione, la coroutine sospende l'esecuzione fino a quando il risultato non è pronto. La coroutine riprende da dove si era interrotta e il risultato.
Mentre la corona è 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 in cui viene eseguito il codice. Una funzione di sospensione può essere eseguita in un thread di sfondo o nel thread principale.
Per utilizzare le coroutine in Kotlin devi avere tre cose:
- Un job
- Un supervisore
- Un ambito
Job: in pratica, qualsiasi lavoro può essere annullato. Ogni coroutine ha un lavoro e puoi utilizzarla per annullare la coroutine. I job possono essere organizzati in gerarchie padre-figlio. L'annullamento di un lavoro principale annulla immediatamente tutti i figli del lavoro, il che è molto più pratico che annullare manualmente ogni coroutine.
Mittente: il supervisore invia coroutine ai fini della corsa su vari thread. Ad esempio, Dispatcher.Main
esegue attività nel thread principale e Dispatcher.IO
trasferisce le attività di I/O bloccandole in un pool condiviso di thread.
Ambito: un ambito di coroutine definisce il contesto in cui viene eseguita la coroutine. Un ambito combina informazioni sul lavoro e sul supervisore di una coroutine. Gli ambiti tengono traccia delle coroutine. Quando lanci una coroutine, la campana è in ambito, il che significa che hai indicato quale ambito monitorerà la coroutine.
Vuoi che l'utente sia in grado di interagire con i dati relativi al sonno nei seguenti modi:
- Quando l'utente tocca il pulsante Avvia, l'app crea una nuova notte di sonno e memorizza la notte di sonno nel database.
- Quando l'utente tocca il pulsante Interrompi, l'app aggiorna la notte a un'ora di fine.
- Quando l'utente tocca il pulsante Cancella, l'app cancella i dati del database.
Queste operazioni di database possono richiedere molto tempo, quindi devono essere eseguite su un thread separato.
Passaggio 1: configura le coroutine per le operazioni di database
Quando viene toccato il pulsante Avvia nell'app Tracker del sonno, vuoi chiamare una funzione nella SleepTrackerViewModel
per creare una nuova istanza di SleepNight
e archiviare l'istanza nel database.
Toccando uno dei pulsanti si attiva un'operazione di database, come la creazione o l'aggiornamento di un SleepNight
. Per questo e per altri motivi, utilizzi le coroutine per implementare i gestori dei 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 sono necessarie queste dipendenze, che sono state aggiunte per te.
Il file$coroutine_version
nel filebuild.gradle
del progetto è definito 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
consente di annullare tutte le coroutine avviate da questo modello di visualizzazione quando il modello di vista non viene più utilizzato e viene eliminato. In questo modo, non troverete altre coroutine in cui non c'è niente da tornare.
private var viewModelJob = Job()
- Alla fine del corpo del corso, sostituisci
onCleared()
e annulla tutte le coroutine. QuandoViewModel
viene eliminato, viene chiamato il metodoonCleared()
.
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
- Appena 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 acquisire un ambito, chiedi un'istanza diCoroutineScope
e passa un supervisore e un lavoro.
L'uso di Dispatchers.Main
significa che le coroutine lanciate nel campo uiScope
verranno eseguite nel thread principale. Questo è ragionevole per molte coroutine avviate da un ViewModel
, perché dopo che queste coroutine eseguono un'elaborazione, comportano un aggiornamento dell'interfaccia utente.
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
- Sotto la definizione di
uiScope
, definisci una variabile denominatatonight
per mantenere la notte corrente. Imposta la variabileMutableLiveData
perché devi essere in grado di osservare i dati e di modificarli.
private var tonight = MutableLiveData<SleepNight?>()
- Per inizializzare il prima possibile la variabile
tonight
, crea un bloccoinit
sotto la definizione ditonight
e chiamainitializeTonight()
. DefinisciinitializeTonight()
nel passaggio successivo.
init {
initializeTonight()
}
- Sotto il blocco
init
, implementainitializeTonight()
. NeluiScope
, lancia una coroutine. All'interno, recupera il valore pertonight
dal database chiamandogetTonightFromDatabase()
e assegna il valore atonight.value
. DefiniscigetTonightFromDatabase()
nel passaggio successivo.
private fun initializeTonight() {
uiScope.launch {
tonight.value = getTonightFromDatabase()
}
}
- Implementa
getTonightFromDatabase()
. Definiscila come funzioneprivate suspend
che restituisce un valoreSleepNight
non valido, se al momento non è stato avviatoSleepNight
. Questo genera un errore, in quanto la funzione deve restituire qualcosa.
private suspend fun getTonightFromDatabase(): SleepNight? { }
- All'interno del corpo della funzione
getTonightFromDatabase()
, restituisci il risultato di una coroutine in esecuzione nel contestoDispatchers.IO
. Utilizza il supervisore I/O, perché ricevere dati dal database è un'operazione di I/O e non ha nulla a che fare con l'interfaccia utente.
return withContext(Dispatchers.IO) {}
- All'interno del blocco di ritorno, consenti alla coroutine di stanotte (la notte più recente) dal database. Se gli orari di inizio e di fine non corrispondono, il che significa che la notte è già stata completata, restituisci
null
. Altrimenti, restituisciti la notte.
var night = database.getTonight()
if (night?.endTimeMilli != night?.startTimeMilli) {
night = null
}
night
La funzione di sospensione getTonightFromDatabase()
completata dovrebbe avere il seguente aspetto. Non ci dovrebbero più essere 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 dei clic per il pulsante Start
Ora puoi implementare onStartTracking()
, il gestore dei clic per il pulsante Start. Devi creare un nuovo SleepNight
, inserirlo nel database e assegnarlo a tonight
. La struttura di onStartTracking()
sarà molto simile a initializeTonight()
.
- Inizia con la definizione della funzione per
onStartTracking()
. Puoi inserire i gestori dei clic al di sopra dionCleared()
nel fileSleepTrackerViewModel
.
fun onStartTracking() {}
- All'interno di
onStartTracking()
, avvia una coroutine inuiScope
, poiché ti serve questo risultato per continuare e aggiornare l'interfaccia utente.
uiScope.launch {}
- All'interno del lancio della coroutine, crea una nuova
SleepNight
, che acquisisce l'ora corrente come ora di inizio.
val newNight = SleepNight()
- Sempre all'interno del lancio della coroutine, chiama
insert()
per inserirenewNight
nel database. Verrà visualizzato un errore perché non hai ancora definito la funzione di sospensioneinsert()
. Questa non è la funzione DAO con lo stesso nome.
insert(newNight)
- Nello stesso lancio della coroutine, aggiorna
tonight
.
tonight.value = getTonightFromDatabase()
- Sotto
onStartTracking()
, definisciinsert()
come una funzioneprivate suspend
che utilizzaSleepNight
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 dell'associazione di dati che hai impostato in precedenza. La notazione della funzione@{() ->
crea una funzione lambda che non impiega 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 è ancora visibile nulla. Correggi il problema in seguito.
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
.
È una funzionalità Room
che ogni volta che i dati nel database cambiano, il valore nights
LiveData
viene aggiornato in modo da mostrare i dati più recenti. Non è necessario impostare esplicitamente LiveData
o aggiornarlo. Room
aggiorna i dati in modo che corrispondano al database.
Tuttavia, se mostri nights
in una visualizzazione di testo, verrà mostrato il riferimento dell'oggetto. Per vedere i contenuti dell'oggetto, trasforma i dati in una stringa formattata. Utilizza una mappa Transformation
che viene 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 delle istruzioniimport
associate. Per annullare il commento del codice in Android Studio, seleziona tutto il codice contrassegnato con//
e premiCmd+/
oControl+/
. - Tieni presente che
formatNights()
restituisce un tipoSpanned
, che è una stringa in formato HTML. - Apri strings.xml. Utilizza
CDATA
per formattare le risorse stringa per visualizzare i dati relativi al sonno. - Apri SleepTrackerViewmodel. Nella classe
SleepTrackerViewModel
, sotto la definizione diuiScope
, definisci una variabile denominatanights
. Prendi tutte le notti dal database e assegnale alla variabilenights
.
private val nights = database.getAllNights()
- Appena sotto la definizione di
nights
, aggiungi il codice per trasformarenights
innightsString
. Utilizza la funzioneformatNights()
diUtil.kt
.
Passanights
nella funzionemap()
della classeTransformations
. Per ottenere l'accesso alle tue risorse stringa, definisci la funzione di mappatura come chiamataformatNights()
. Forniscinights
e un oggettoResources
.
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
- Apri il file di layout
fragment_sleep_tracker.xml
. Nella proprietàandroid:text
diTextView
, ora puoi sostituire la stringa di risorse con un riferimento anightsString
.
"@{sleepTrackerViewModel.nightsString}"
- Ricrea il codice ed esegui l'app. Tutti i dati relativi al sonno con orari di inizio dovrebbero essere visualizzati ora.
- Tocca il pulsante Avvia altre volte per visualizzare altri dati.
Nel passaggio successivo, attiverai la funzionalità per il pulsante Stop.
Passaggio 4: aggiungi il gestore dei clic per il pulsante Interrompi
Utilizzando lo stesso pattern del passaggio precedente, implementa il gestore dei clic per il pulsante Stop in SleepTrackerViewModel.
.
- Aggiungi
onStopTracking()
aViewModel
. Avvia una coroutine nellauiScope
. Se l'ora di fine non è stata ancora impostata, impostaendTimeMilli
sull'ora di sistema corrente e chiamaupdate()
con i dati notturni.
In Kotlin, la sintassireturn@
label
specifica la funzione da cui viene restituita questa istruzione, tra le 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 utilizzato per implementareinsert()
.
private suspend fun update(night: SleepNight) {
withContext(Dispatchers.IO) {
database.update(night)
}
}
- Per collegare 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 Inizia, quindi tocca Interrompi. Puoi vedere l'ora di inizio, l'ora di fine, la qualità del sonno senza alcun valore e il tempo di sonno.
Passaggio 5: aggiungi il gestore dei clic per il pulsante Cancella
- Analogamente, implementa
onClear()
eclear()
.
fun onClear() {
uiScope.launch {
clear()
tonight.value = null
}
}
suspend fun clear() {
withContext(Dispatchers.IO) {
database.clear()
}
}
- Per collegare il gestore dei clic all'interfaccia utente, 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: TrackMySleep QualityCoroutines
- Utilizza
ViewModel
,ViewModelFactory
e l'associazione di dati per configurare l'architettura dell'interfaccia utente dell'app. - Per assicurare il corretto funzionamento dell'interfaccia utente, utilizza coroutine per le attività di lunga durata, come tutte le operazioni di database.
- Le coroutine sono asincrone e non bloccanonti. Utilizzano le funzioni
suspend
per rendere sequenziale il codice asincrono. - Quando una corsia chiama una funzione contrassegnata con la dicitura
suspend
, invece di bloccarla finché non viene restituita una chiamata funzione normale, l'esecuzione dell'azione viene sospesa finché il risultato non è pronto. Quindi riprende la visione da dove si era interrotta. - La differenza tra blocco e sospensione è che, se un thread è bloccato, non succede altro. Se il thread è sospeso, il lavoro viene eseguito finché il risultato non è disponibile.
Per lanciare una coroutine, devi avere un lavoro, un supervisore e un ambito:
- In pratica, un job è tutto ciò che può essere annullato. Ogni coroutine ha un lavoro e puoi utilizzarla per annullare una coroutine.
- Lo strumento di distribuzione invia coroutine ai fini di essere eseguite su vari thread.
Dispatcher.Main
esegue attività nel thread principale eDispartcher.IO
serve per trasferire le attività di I/O di blocco in un pool condiviso di thread. - L'ambito combina le informazioni, inclusi un job e il supervisore, per definire il contesto in cui viene eseguita la coroutine. Gli ambiti tengono traccia delle coroutine.
Per implementare i gestori dei clic che attivano le operazioni di database, segui questo pattern:
- Avviare una coroutine in esecuzione sul thread principale o sull'interfaccia utente, perché il risultato influisce sull'interfaccia utente.
- Chiama una funzione di sospensione per svolgere il lavoro a lunga esecuzione, in modo da non bloccare il thread dell'interfaccia utente mentre attendi il risultato.
- Il lavoro a lungo termine non ha nulla a che fare con l'interfaccia utente, quindi passa al contesto I/O. In questo modo, il lavoro può essere eseguito in un pool di thread ottimizzato e riservato per questi tipi di operazioni.
- A questo punto, chiama la funzione database per svolgere il lavoro.
Usa una mappa Transformations
per creare una stringa da un oggetto LiveData
ogni volta che quest'ultimo cambia.
Corso Udacity:
Documentazione per gli sviluppatori Android:
RoomDatabase
- Riutilizzo dei layout con <include/>
ViewModelProvider.Factory
SimpleDateFormat
HtmlCompat
Altra documentazione e articoli:
- Modello di fabbrica
- Codelab su Coroutines
- Coroutine, documentazione ufficiale
- Contesto di coroutine e supervisori
Dispatchers
- Supera il limite di velocità di Android
Job
launch
- Resi e salti in Kotlin
- CDATA sta per character data (dati dei caratteri). Con CDATA, i dati compresi in queste stringhe includono dati che potrebbero essere interpretati come markup XML, ma non lo sono.
In questa sezione sono elencati i possibili compiti per gli studenti che lavorano attraverso questo codelab nell'ambito di un corso tenuto da un insegnante. Spetta all'insegnante fare quanto segue:
- Assegna i compiti, se necessario.
- Comunica agli studenti come inviare compiti.
- Valuta i compiti.
Gli insegnanti possono utilizzare i suggerimenti solo quanto e come vogliono e dovrebbero assegnare i compiti che ritengono appropriati.
Se stai lavorando da solo a questo codelab, puoi utilizzare questi compiti per mettere alla prova le tue conoscenze.
Rispondi a queste domande
Domanda 1
Quali dei seguenti vantaggi presentano coroutine:
- senza bloccare
- Vengono eseguiti in modo asincrono.
- Possono essere eseguiti su un thread diverso dal thread principale.
- Velocizzano sempre l'esecuzione dell'app.
- Possono utilizzare eccezioni.
- Possono essere scritti e letti come codice lineare.
Domanda 2
Che cos'è una funzione di sospensione?
- Una funzione normale annotata con la parola chiave
suspend
. - Una funzione che può essere chiamata all'interno di coroutine.
- Mentre è in esecuzione una funzione di sospensione, il thread di chiamate è sospeso.
- Le funzioni di sospensione devono essere sempre eseguite in background.
Domanda 3
Qual è la differenza tra bloccare e sospendere un thread? Contrassegna le due condizioni.
- Quando l'esecuzione è bloccata, non è possibile eseguire nessun'altra operazione sul thread bloccato.
- Quando l'esecuzione è sospesa, il thread può eseguire altre operazioni in attesa del completamento del lavoro.
- La sospensione è più efficiente, perché i thread potrebbero non attendere nulla.
- In caso di blocco o sospensione, l'esecuzione è ancora in attesa del risultato della coroutine prima di continuare.
Inizia la lezione successiva:
Per i link ad altri codelab in questo corso, consulta la pagina di destinazione di Android Kotlin Fundamentals.