Android Kotlin Fundamentals 06.2: Coroutine e Room

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

  1. Scarica l'app TrackMySleepQualità-Coroutines-Starter da GitHub.
  2. 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.

  1. Apri res/layout/activity_main.xml. Questo layout contiene il frammento nav_host_fragment. Inoltre, tieni presente il tag <merge>.

    Il tag merge 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.
  2. Nella cartella browsing, apri il file navigation.xml. Sono visibili due frammenti e le azioni di navigazione che li collegano.
  3. 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

  1. Nel pacchetto sleeptracker, apri SleepTrackerViewmodel.kt.
  2. Esamina il corso SleepTrackerViewModel, che ti viene fornito nell'app Starter e è anche mostrato di seguito. Tieni presente che il corso si estende per AndroidViewModel(). Questa classe è uguale a ViewModel, 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

  1. Nel pacchetto sleeptracker, apri SleepTrackerViewmodelFactory.kt.
  2. 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 di ViewModel ed estende ViewModelProvider.Factory.
  • All'interno della fabbrica, il codice sostituisce create(), che accetta qualsiasi tipo di classe come argomento e restituisce ViewModel.
  • Nel corpo di create(), il codice verifica che sia disponibile una classe SleepTrackerViewModel e, in caso affermativo, ne restituisce un'istanza. Altrimenti, il codice genera un'eccezione.

Passaggio 3: aggiorna il monitoraggio del sonno

  1. In SleepTrackerFragment, trova un riferimento al contesto dell'applicazione. Inserisci il riferimento in onCreateView(), sotto binding. Per passare al provider del modello di visualizzazione modello di visualizzazione, devi fare riferimento all'app a cui è collegato questo frammento.

    La funzione requireNotNull Kotlin genera un elemento IllegalArgumentException se il valore è null.
val application = requireNotNull(this.activity).application
  1. È necessario un riferimento all'origine dati tramite un riferimento al DAO. In onCreateView(), prima di return, definisci un dataSource. Per ottenere un riferimento al DAO del database, utilizza SleepDatabase.getInstance(application).sleepDatabaseDao.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
  1. In onCreateView(), prima di return, crea un'istanza di viewModelFactory. Devi superare l'evento dataSource e application.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
  1. Ora che disponi di una fabbrica, rivolgiti a SleepTrackerViewModel. Il parametro SleepTrackerViewModel::class.java si riferisce alla classe Java di runtime di questo oggetto.
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)
  1. 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:

  1. All'interno del blocco <data>, crea un elemento <variable> che faccia riferimento alla classe SleepTrackerViewModel.
<data>
   <variable
       name="sleepTrackerViewModel"
       type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>

In SleepTrackerFragment:

  1. Imposta l'attività corrente come proprietario del ciclo di vita dell'associazione. Aggiungi questo codice nel metodo onCreateView(), prima dell'istruzione return:
binding.setLifecycleOwner(this)
  1. Assegna la variabile di associazione sleepTrackerViewModel a sleepTrackerViewModel. Inserisci questo codice all'interno di onCreateView(), sotto il codice che crea il SleepTrackerViewModel:
binding.sleepTrackerViewModel = sleepTrackerViewModel
  1. Probabilmente vedrai un errore perché devi ricreare l'oggetto associazione. Pulisci e ricrea il progetto per eliminare l'errore.
  2. 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.

  1. 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 file build.gradle del progetto è definito come coroutine_version = '1.0.0'.
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
  1. Apri il file SleepTrackerViewModel.
  2. Nel corpo della classe, definisci viewModelJob e assegnagli un'istanza di Job. Questo viewModelJob 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()
  1. Alla fine del corpo del corso, sostituisci onCleared() e annulla tutte le coroutine. Quando ViewModel viene eliminato, viene chiamato il metodo onCleared().
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}
  1. Appena sotto la definizione di viewModelJob, definisci un uiScope 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 di CoroutineScope 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)
  1. Sotto la definizione di uiScope, definisci una variabile denominata tonight per mantenere la notte corrente. Imposta la variabile MutableLiveData perché devi essere in grado di osservare i dati e di modificarli.
private var tonight = MutableLiveData<SleepNight?>()
  1. Per inizializzare il prima possibile la variabile tonight, crea un blocco init sotto la definizione di tonight e chiama initializeTonight(). Definisci initializeTonight() nel passaggio successivo.
init {
   initializeTonight()
}
  1. Sotto il blocco init, implementa initializeTonight(). Nel uiScope, lancia una coroutine. All'interno, recupera il valore per tonight dal database chiamando getTonightFromDatabase() e assegna il valore a tonight.value. Definisci getTonightFromDatabase() nel passaggio successivo.
private fun initializeTonight() {
   uiScope.launch {
       tonight.value = getTonightFromDatabase()
   }
}
  1. Implementa getTonightFromDatabase(). Definiscila come funzione private suspend che restituisce un valore SleepNight non valido, se al momento non è stato avviato SleepNight. Questo genera un errore, in quanto la funzione deve restituire qualcosa.
private suspend fun getTonightFromDatabase(): SleepNight? { }
  1. All'interno del corpo della funzione getTonightFromDatabase(), restituisci il risultato di una coroutine in esecuzione nel contesto Dispatchers.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) {}
  1. 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().

  1. Inizia con la definizione della funzione per onStartTracking(). Puoi inserire i gestori dei clic al di sopra di onCleared() nel file SleepTrackerViewModel.
fun onStartTracking() {}
  1. All'interno di onStartTracking(), avvia una coroutine in uiScope, poiché ti serve questo risultato per continuare e aggiornare l'interfaccia utente.
uiScope.launch {}
  1. All'interno del lancio della coroutine, crea una nuova SleepNight, che acquisisce l'ora corrente come ora di inizio.
        val newNight = SleepNight()
  1. Sempre all'interno del lancio della coroutine, chiama insert() per inserire newNight nel database. Verrà visualizzato un errore perché non hai ancora definito la funzione di sospensione insert(). Questa non è la funzione DAO con lo stesso nome.
       insert(newNight)
  1. Nello stesso lancio della coroutine, aggiorna tonight.
       tonight.value = getTonightFromDatabase()
  1. Sotto onStartTracking(), definisci insert() come una funzione private suspend che utilizza SleepNight come argomento.
private suspend fun insert(night: SleepNight) {}
  1. Per il corpo di insert(), avvia una coroutine nel contesto I/O e inserisci la notte nel database chiamando insert() dal DAO.
   withContext(Dispatchers.IO) {
       database.insert(night)
   }
  1. Nel file di layout fragment_sleep_tracker.xml, aggiungi il gestore dei clic per onStartTracking() a start_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 in sleepTrackerViewModel.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
  1. 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.

  1. Apri il file Util.kt e rimuovi il commento dal codice per la definizione di formatNights() e delle istruzioni import associate. Per annullare il commento del codice in Android Studio, seleziona tutto il codice contrassegnato con // e premi Cmd+/ o Control+/.
  2. Tieni presente che formatNights() restituisce un tipo Spanned, che è una stringa in formato HTML.
  3. Apri strings.xml. Utilizza CDATA per formattare le risorse stringa per visualizzare i dati relativi al sonno.
  4. Apri SleepTrackerViewmodel. Nella classe SleepTrackerViewModel, sotto la definizione di uiScope, definisci una variabile denominata nights. Prendi tutte le notti dal database e assegnale alla variabile nights.
private val nights = database.getAllNights()
  1. Appena sotto la definizione di nights, aggiungi il codice per trasformare nights in nightsString. Utilizza la funzione formatNights() di Util.kt.

    Passa nights nella funzione map() della classe Transformations. Per ottenere l'accesso alle tue risorse stringa, definisci la funzione di mappatura come chiamata formatNights(). Fornisci nights e un oggetto Resources.
val nightsString = Transformations.map(nights) { nights ->
   formatNights(nights, application.resources)
}
  1. Apri il file di layout fragment_sleep_tracker.xml. Nella proprietà android:text di TextView, ora puoi sostituire la stringa di risorse con un riferimento a nightsString.
"@{sleepTrackerViewModel.nightsString}"
  1. Ricrea il codice ed esegui l'app. Tutti i dati relativi al sonno con orari di inizio dovrebbero essere visualizzati ora.
  2. 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..

  1. Aggiungi onStopTracking() a ViewModel. Avvia una coroutine nella uiScope. Se l'ora di fine non è stata ancora impostata, imposta endTimeMilli sull'ora di sistema corrente e chiama update() con i dati notturni.

    In Kotlin, la sintassi return@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)
   }
}
  1. Implementa update() utilizzando lo stesso pattern utilizzato per implementare insert().
private suspend fun update(night: SleepNight) {
   withContext(Dispatchers.IO) {
       database.update(night)
   }
}
  1. Per collegare il gestore dei clic all'interfaccia utente, apri il file di layout fragment_sleep_tracker.xml e aggiungi il gestore dei clic a stop_button.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
  1. Crea ed esegui la tua app.
  2. 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

  1. Analogamente, implementa onClear() e clear().
fun onClear() {
   uiScope.launch {
       clear()
       tonight.value = null
   }
}

suspend fun clear() {
   withContext(Dispatchers.IO) {
       database.clear()
   }
}
  1. Per collegare il gestore dei clic all'interfaccia utente, apri fragment_sleep_tracker.xml e aggiungi il gestore dei clic a clear_button.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
  1. Crea ed esegui la tua app.
  2. 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 e Dispartcher.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:

  1. Avviare una coroutine in esecuzione sul thread principale o sull'interfaccia utente, perché il risultato influisce sull'interfaccia utente.
  2. 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.
  3. 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.
  4. 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:

Altra documentazione e articoli:

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: 6.3 Utilizzare LiveData per controllare gli stati dei pulsanti

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