Android Kotlin Fundamentals 06.2: Coroutines and Room

Te ćwiczenia są częścią kursu Android Kotlin Fundamentals. Skorzystaj z tego kursu, jeśli będziesz wykonywać kolejno kilka ćwiczeń z programowania. Wszystkie ćwiczenia z kursu są wymienione na stronie docelowej ćwiczeń z programowania na temat Kotlin.

Wprowadzenie

Jednym z priorytetów jest zapewnianie użytkownikom bezproblemowego korzystania z aplikacji, ponieważ zapewnia to, że interfejs użytkownika jest zawsze elastyczny i działa płynnie. Jednym ze sposobów na poprawę wydajności interfejsu jest przeniesienie w tle zadań długotrwałych (na przykład operacji baz danych).

W ramach tego ćwiczenia implementujesz widoczną dla użytkownika część aplikacji TrackMySleepQuality, używając algorytmów Kotlin do wykonywania operacji baz danych z głównego wątku.

Co musisz wiedzieć

Pamiętaj:

  • Tworzenie podstawowego interfejsu użytkownika za pomocą aktywności, fragmentów, widoków i modułów obsługi kliknięć.
  • Przechodzenie między fragmentami i przekazywanie między nimi prostych danych za pomocą safeArgs.
  • Wyświetlanie modeli, wyświetlanie fabryk modeli, przekształceń i LiveData.
  • Jak utworzyć bazę danych Room, utworzyć DAO i określić encje.
  • Jest to pomocne, jeśli znasz zagadnienia związane z wątkami i wieloprocesowaniem.

Czego się nauczysz

  • Jak działają wątki na Androidzie
  • Jak używać algorytmów Kotlin do przenoszenia operacji bazy danych poza główny wątek.
  • Jak wyświetlać sformatowane dane w polu TextView.

Jakie zadania wykonasz:

  • Rozszerz aplikację TrackMySleepQuality, aby zbierać, przechowywać i wyświetlać dane w bazie danych.
  • Używaj baz danych, aby uruchamiać długotrwałe operacje bazy danych w tle.
  • Użyj elementu LiveData, by włączyć nawigację i wyświetlać pasek z przekąskami.
  • Użyj LiveData, aby włączyć lub wyłączyć przyciski.

W tym ćwiczeniu z programowania tworzysz model widoku danych, współprogramy i elementy do wyświetlania danych w aplikacji TrackMySleepQuality.

Aplikacja ma 2 ekrany reprezentowane przez fragmenty, co widać na rysunku poniżej.

Pierwszy ekran (po lewej stronie) ma przyciski do włączania i wyłączania śledzenia. Na ekranie widoczne są wszystkie dane dotyczące snu użytkownika. Kliknięcie przycisku Wyczyść powoduje trwałe usunięcie wszystkich danych użytkownika zbieranych przez aplikację.

Na drugim ekranie (po prawej) możesz wybrać ocenę jakości snu. W aplikacji ocena jest wyrażana numerycznie. Na potrzeby programowania aplikacja wyświetla ikony twarzy i ich odpowiedniki liczbowe.

Proces użytkownika wygląda tak:

  • Użytkownik otwiera aplikację i wyświetla się ekran monitorowania snu.
  • Użytkownik klika przycisk Start. Rejestruje godzinę rozpoczęcia i wyświetla ją. Przycisk Start jest wyłączony, a przycisk Stop jest włączony.
  • Użytkownik klika przycisk Zatrzymaj. Rejestruje godzinę zakończenia i otwiera ekran jakości snu.
  • Użytkownik wybiera ikonę jakości snu. Ekran zostanie zamknięty, a na ekranie śledzenia pojawi się godzina zakończenia snu oraz jakość snu. Przycisk Stop jest wyłączony, a przycisk Start – włączony. Aplikacja jest gotowa na kolejną noc.
  • Przycisk Wyczyść jest włączony zawsze, gdy w bazie danych znajdują się dane. Gdy użytkownik kliknie przycisk Wyczyść, wszystkie jego dane zostaną usunięte bez możliwości ich odzyskania.

Ta aplikacja używa uproszczonej architektury, jak pokazano poniżej, w kontekście pełnej architektury. Aplikacja używa tylko tych komponentów:

  • Kontroler interfejsu
  • Wyświetl model i LiveData
  • baza danych pokoju;

W tym zadaniu korzystasz z TextView, aby wyświetlać sformatowane dane monitorowania snu. (nie jest to interfejs końcowy). Więcej dowiesz się z innych ćwiczeń z programowania.

Możesz przejść do aplikacji TrackMySleepQuality, która została przez Ciebie utworzona w poprzednim module. Możesz też pobrać aplikację startową z tego ćwiczenia.

Krok 1. Pobierz i uruchom aplikację startową

  1. Pobierz aplikację TrackMySleepQuality-Coroutines-Starter z GitHuba.
  2. Utwórz i uruchom aplikację. Aplikacja wyświetla interfejs dla fragmentu SleepTrackerFragment, ale nie zawiera danych. Przyciski nie reagują na dotknięcia.

Krok 2. Sprawdź kod

Kod startowy tego ćwiczenia z programowania jest taki sam jak kod rozwiązania 6.1 Tworzenie ćwiczenia z bazy danych w pokoju.

  1. Otwórz plik res/layout/activity_main.xml. Ten układ zawiera fragment nav_host_fragment. Zwróć też uwagę na tag <merge>.

    Tagu merge można użyć do wyeliminowania zbędnych układów podczas dodawania układów. Dobrym pomysłem jest używanie go. Przykładem nadmiarowego układu jest ConstraintLayout > LinearLayout > TextView, gdzie system może usunąć układ LinearLayout. Ten rodzaj optymalizacji może uprościć widok i zwiększyć skuteczność aplikacji.
  2. W folderze Nawigacja otwórz plik Nawigacja.xml. Zobaczysz dwa fragmenty i działania nawigacyjne, które je łączą.
  3. W folderze układ kliknij dwukrotnie fragment skryptu do monitorowania snu, by zobaczyć jego układ XML. Zwróć uwagę na te kwestie:
  • Dane układu są opakowane w elemencie <layout>, aby umożliwić wiązanie danych.
  • ConstraintLayout i inne widoki są rozmieszczone wewnątrz elementu <layout>.
  • Plik zawiera zastępczy tag <data>.

Aplikacja startowa zapewnia też wymiary, kolory i styl interfejsu. Aplikacja zawiera bazę danych Room, DAO i element SleepNight. Jeśli nie ukończyłeś(aś) poprzedniego ćwiczenia z programowania, pamiętaj, by samodzielnie zbadać te aspekty kodu.

Po utworzeniu bazy danych i interfejsu musisz je zebrać, dodać do niej dane i je wyświetlić. Wszystkie te czynności wykonuje się w modelu widoku danych. Model widoku monitorowania snu będzie obsługiwać kliknięcia przycisków, wchodzić w interakcje z bazą danych za pomocą DAO i udostępniać dane w interfejsie za pomocą interfejsu LiveData. Wszystkie operacje na bazach danych będą musiały się odłączać od głównego wątku UI. Należy to robić za pomocą odpowiednich algorytmów.

Krok 1: DodajSleepTrackerViewModel

  1. W pakiecie sleeptracker otwórz SleepTrackerViewModel.kt.
  2. Sprawdź klasę SleepTrackerViewModel, którą znajdziesz w aplikacji startowej. Znajdziesz ją również poniżej. Pamiętaj, że klasa rozciąga się do AndroidViewModel(). Ta klasa jest taka sama jak właściwość ViewModel, ale pobiera kontekst aplikacji jako parametr i udostępnia go jako właściwość. Te informacje będą Ci potrzebne później.
class SleepTrackerViewModel(
       val database: SleepDatabaseDao,
       application: Application) : AndroidViewModel(application) {
}

Krok 2. DodajSleepTrackerViewModelFactory

  1. W pakiecie sleeptracker otwórz SleepTrackerViewModelFactory.kt.
  2. Przeanalizuj poniższy kod fabryki:
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")
   }
}

Pamiętaj o tych kwestiach:

  • Podany SleepTrackerViewModelFactory przyjmuje ten sam argument co ViewModel i rozszerza ViewModelProvider.Factory.
  • W fabryce kod zastępuje właściwość create(), która przyjmuje dowolny typ klasy jako argument i zwraca ViewModel.
  • W treści kodu create() kod sprawdza, czy jest dostępna klasa SleepTrackerViewModel, a jeśli jest dostępna, zwraca wystąpienie tej klasy. W przeciwnym razie kod zgłasza wyjątek.

Krok 3. Zaktualizuj fragment z kodem śledzenia snu

  1. W SleepTrackerFragment zapoznaj się z kontekstem aplikacji. Umieść plik referencyjny w kolumnie onCreateView() poniżej binding. Aby przekazać dane do dostawcy fabryki modeli widoków, potrzebujesz odwołania do aplikacji, z którą jest połączony.

    Funkcja requireNotNull Kotlin generuje IllegalArgumentException, jeśli wartość to null.
val application = requireNotNull(this.activity).application
  1. Musisz podać odniesienie do źródła danych przez odwołanie do DAO. W: onCreateView() przed return określ dataSource. Aby uzyskać odwołanie do DAO bazy danych, użyj SleepDatabase.getInstance(application).sleepDatabaseDao.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
  1. W onCreateView() przed return utwórz wystąpienie viewModelFactory. Musisz zdać test dataSource i application.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
  1. Teraz gdy masz już fabrykę, możesz sięgnąć do SleepTrackerViewModel. Parametr SleepTrackerViewModel::class.java odwołuje się do klasy środowiska wykonawczego Java tego obiektu.
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)
  1. Gotowy kod powinien wyglądać tak:
// 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)

Oto bieżąca metoda onCreateView():

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
    }

Krok 4. Dodaj wiązanie danych dla modelu widoku danych

Gdy podstawowe ViewModel jest już skonfigurowane, musisz dokończyć konfigurowanie wiązania danych w SleepTrackerFragment, by połączyć ViewModel z interfejsem.


W pliku układu fragment_sleep_tracker.xml:

  1. W bloku <data> utwórz <variable>, który odwołuje się do klasy SleepTrackerViewModel.
<data>
   <variable
       name="sleepTrackerViewModel"
       type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>

Za SleepTrackerFragment:

  1. Ustaw bieżącą aktywność jako właściciela cyklu życia wiązania. Dodaj ten kod do metody onCreateView() przed instrukcją return:
binding.setLifecycleOwner(this)
  1. Przypisz zmienną wiązania sleepTrackerViewModel do elementu sleepTrackerViewModel. Umieść ten kod w sekcji onCreateView() pod kodem, który tworzy SleepTrackerViewModel:
binding.sleepTrackerViewModel = sleepTrackerViewModel
  1. Prawdopodobnie zobaczysz błąd, ponieważ trzeba odtworzyć obiekt powiązania. Wyczyść i przebuduj projekt, aby usunąć błąd.
  2. Na koniec: jak zawsze upewnij się, że kod jest kompilowany i działa bez błędów.

W Kotlinie dzięki koordynacjom możesz robić to efektywnie i skutecznie. Kotlin pozwala przekonwertować kod oparty na wywołaniach na kod sekwencyjny. Kod sekwencyjny jest zazwyczaj łatwiejszy do odczytania i nawet można w nim używać funkcji językowych takich jak wyjątki. W końcu zrobić to samo i odczekują, aż wynik będzie dostępny od długo trwającego zadania, i kontynuuje wykonywanie zadania.

Współrzędne mają następujące właściwości:

  • Korutyny są asynchroniczne i nie blokują.
  • Algorytmy używają funkcji zawieszenia, by utworzyć sekwencyjny kod asynchroniczny.

Koordynacje są asynchroniczne.

Schemat uruchamia się niezależnie od głównych etapów wykonywania programu. Może to być równoległe lub na oddzielnym procesorze. Możliwe też, że pozostała część aplikacji czeka na Twoją odpowiedź, Jednym z ważnych aspektów synchronizacji jest fakt, że nie można oczekiwać, że wynik będzie dostępny, dopóki nie zostanie on wprost wyświetlony.

Załóżmy, że masz pytanie, które wymaga badania, i chcesz poprosić o pomoc współpracownika. Zaczynają nad tym pracować, jak to robią – w osobnym wątku &&tt. Możesz kontynuować inną pracę, która nie zależy od odpowiedzi, dopóki inny nie wróci do miejsca i nie opowie, o co chodzi.

Koordynacje nie blokują.

Nieblokujące oznacza, że współprogram nie blokuje wątku głównego lub wątku interfejsu. W przypadku algorytmów użytkownicy zawsze mają jak największą wygodę, ponieważ interakcja z interfejsem użytkownika jest zawsze priorytetem.

Koordynaty używają funkcji zawieszenia, aby ustawić sekwencyjny kod asynchroniczny.

Słowo kluczowe suspend to sposób na określenie funkcji lub typu funkcji Kotlin jako współdzielenie. Gdy współprogramowana wywołuje funkcję oznaczoną znakiem suspend, zamiast blokować ją, dopóki nie zostanie zwrócona tak jak normalne wywołanie funkcji, zostanie ona zawieszona do czasu, aż wynik będzie gotowy. Następnie przetwarzanie zostanie wznowione w miejscu, w którym zostało przerwane, i wynik.

Współrzędna jest zawieszona i oczekuje na wynik, odblokowuje wątek, w którym jest on używany. Dzięki temu można uruchomić inne funkcje lub współprogramy.

Słowo kluczowe suspend nie określa wątku, w którym jest uruchamiany kod. Funkcja zawieszenia może być uruchomiona w wątku głównym lub w wątku głównym.

Aby używać koordynacji w Kotlinie, potrzebujesz trzech rzeczy:

  • Oferta pracy
  • Dyspozytor
  • Zakres

Zadanie: praca to wszystko, co można anulować. Każdy algorytm ma zadanie, którego można użyć do anulowania zadania. Zadania można uporządkować w hierarchie nadrzędne i podrzędne. Anulowanie zadania nadrzędnego powoduje natychmiastowe anulowanie wszystkich zadań zadania, co jest dużo wygodniejszym rozwiązaniem niż ręczne anulowanie każdej rutyny.

Dyspozytor wysyła dystrybutory do pracy w różnych wątkach. Na przykład: Dispatcher.Main uruchamia zadania w wątku głównym, a Dispatcher.IO przenosi zadania blokujące I/O do udostępnionej puli wątków.

Zakres: zakres współrzędnej określa kontekst, w którym jest uruchomiona. Zakres łączy informacje o zadaniu i dyspozytorze. Zakresy służą do śledzenia współprogramów. Gdy uruchomisz taką regułę, oznacza to, że określony zakres będzie śledzony.

Chcesz, aby użytkownik mógł korzystać z danych o śnie w następujący sposób:

  • Gdy użytkownik kliknie przycisk Rozpocznij, aplikacja utworzy nową noc i zapisze ją w bazie danych.
  • Gdy użytkownik kliknie przycisk Zatrzymaj, aplikacja zaktualizuje godzinę zakończenia snu.
  • Gdy użytkownik kliknie przycisk Wyczyść, aplikacja wykasuje dane z bazy danych.

Te operacje bazy danych mogą zająć dużo czasu, więc powinny zostać uruchomione w nowym wątku.

Krok 1. Skonfiguruj reguły operacji bazy danych

Po kliknięciu przycisku Start w aplikacji Monitorowanie snu możesz wywołać funkcję w SleepTrackerViewModel, aby utworzyć nową instancję SleepNight i zapisać tę instancję w bazie danych.

Kliknięcie dowolnego przycisku powoduje uruchomienie operacji bazy danych, na przykład utworzenie lub zaktualizowanie elementu SleepNight. Z tego i innych powodów stosujesz moduły do obsługi modułów obsługi kliknięć na przyciskach aplikacji.

  1. Otwórz plik build.gradle na poziomie aplikacji i odszukaj zależności dla współprogramów. Aby używać współprogramów, potrzebujesz tych zależności, które zostały już dodane.

    Pole $coroutine_version jest zdefiniowane w pliku build.gradle projektu jako coroutine_version = '1.0.0'.
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
  1. Otwórz plik SleepTrackerViewModel.
  2. W treści klasy określ właściwość viewModelJob i przypisz jej instancję Job. Ten model viewModelJob umożliwia anulowanie wszystkich algorytmów uruchamianych przez ten model widoku danych, gdy model nie jest już używany i zostanie zniszczony. W ten sposób nie dotrzesz do koordynatorów, do których nie musisz wracać.
private var viewModelJob = Job()
  1. Na końcu treści zajęć zastąp onCleared() i anuluj wszystkie reguły. Po zniszczeniu ViewModel następuje wywołanie onCleared().
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}
  1. Tuż pod definicją elementu viewModelJob zdefiniuj definicję uiScope. Zakres decyduje o tym, który wątek będzie działać, a także zakres informacji o zadaniu. Aby uzyskać zakres, poproś o instancję CoroutineScope i przekaż dyspozytora oraz zadanie.

Użycie zasady Dispatchers.Main oznacza, że w głównym wątku będą uruchamiane algorytmy uruchamiane w narzędziu uiScope. Ma to sens w przypadku wielu algorytmów uruchamianych w ViewModel, ponieważ gdy te przetwarzanie zakończy się jakieś przetwarzaniem, następuje aktualizacja interfejsu użytkownika.

private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
  1. Pod definicją uiScope zdefiniuj zmienną o nazwie tonight, która będzie przechowywał bieżącą noc. Utwórz zmienną MutableLiveData, ponieważ musisz mieć możliwość obserwacji i zmiany danych.
private var tonight = MutableLiveData<SleepNight?>()
  1. Aby jak najszybciej zainicjować zmienną tonight, utwórz blok init pod definicją tagu tonight i wywołuj metodę initializeTonight(). Definiujesz initializeTonight() w następnym kroku.
init {
   initializeTonight()
}
  1. Pod blokiem init zaimplementuj initializeTonight(). Uruchom uiScope w terenie. Pobierz z bazy danych wartość tonight dla wywołania getTonightFromDatabase() i przypisz ją do tonight.value. Definiujesz getTonightFromDatabase() w następnym kroku.
private fun initializeTonight() {
   uiScope.launch {
       tonight.value = getTonightFromDatabase()
   }
}
  1. Użyj mechanizmu getTonightFromDatabase(). Zdefiniuj ją jako funkcję private suspend, która zwraca SleepNight o wartości null. Jeśli SleepNight nie został uruchomiony wcześniej, Powoduje to wystąpienie błędu, ponieważ funkcja musi zwrócić jakąś wartość.
private suspend fun getTonightFromDatabase(): SleepNight? { }
  1. Wewnątrz treści funkcji getTonightFromDatabase() zwróć wynik z kohorty, która działa w kontekście Dispatchers.IO. Używaj dyspozytora I/O, ponieważ pobieranie danych z bazy danych jest operacjami I/O i nie ma nic wspólnego z interfejsem użytkownika.
  return withContext(Dispatchers.IO) {}
  1. Zewnętrzna baza danych powinna pozwolić na dotarcie z bazy do bazy (najnowszej nocy). Jeśli czas rozpoczęcia i zakończenia nie są takie same, co oznacza, że noc już się zakończył, zwróć null. Jeśli tego nie zrobisz, wróć na noc.
       var night = database.getTonight()
       if (night?.endTimeMilli != night?.startTimeMilli) {
           night = null
       }
       night

Ukończona funkcja zawieszenia getTonightFromDatabase() powinna wyglądać tak. Nie powinno już być żadnych błędów.

private suspend fun getTonightFromDatabase(): SleepNight? {
   return withContext(Dispatchers.IO) {
       var night = database.getTonight()
       if (night?.endTimeMilli != night?.startTimeMilli) {
           night = null
       }
       night
   }
}

Krok 2. Dodaj moduł do obsługi kliknięć przycisku Start

Teraz możesz zaimplementować tag onStartTracking() do obsługi kliknięć przycisku Start. Musisz utworzyć nowy element SleepNight, wstawić go do bazy danych i przypisać do tonight. Struktura obiektu onStartTracking() będzie zbliżona do initializeTonight().

  1. Zacznij od definicji funkcji dla onStartTracking(). Moduły obsługi kliknięć możesz umieścić w pliku SleepTrackerViewModel powyżej onCleared().
fun onStartTracking() {}
  1. Otwórz onStartTracking() w uiScope, aby kontynuować i zaktualizować interfejs użytkownika.
uiScope.launch {}
  1. W ramach uruchamiania wewnętrznego utwórz nowy element SleepNight, który rejestruje godzinę bieżącą jako godzinę rozpoczęcia.
        val newNight = SleepNight()
  1. Nie wychodząc z rutyny, wywołaj insert(), aby wstawić newNight do bazy danych. Pojawi się błąd, ponieważ nie masz jeszcze zdefiniowanej funkcji zawieszenia insert(). (To nie jest funkcja DAO o tej samej nazwie).
       insert(newNight)
  1. W przypadku tak wprowadzonych zmian zaktualizuj tonight.
       tonight.value = getTonightFromDatabase()
  1. Poniżej onStartTracking() definiuje insert() jako funkcję private suspend, która przyjmuje SleepNight za argument.
private suspend fun insert(night: SleepNight) {}
  1. W przypadku treści insert() uruchom rutynę w kontekście I/O i wstaw noc do bazy danych, wywołując insert() z DAO.
   withContext(Dispatchers.IO) {
       database.insert(night)
   }
  1. W pliku układu fragment_sleep_tracker.xml dodaj moduł obsługi kliknięć onStartTracking() do start_button, korzystając z magii skonfigurowanego wcześniej wiązania danych. Zapis funkcji @{() -> tworzy funkcję lambdy, która nie przyjmuje argumentów i wywołuje metodę obsługi kliknięcia w elemencie sleepTrackerViewModel.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
  1. Utwórz i uruchom swoją aplikację. Kliknij przycisk Start. To działanie spowoduje utworzenie danych, ale jeszcze nic nie możesz zobaczyć. Już to poprawisz.
fun someWorkNeedsToBeDone {
   uiScope.launch {

        suspendFunction()

   }
}

suspend fun suspendFunction() {
   withContext(Dispatchers.IO) {
       longrunningWork()
   }
}

Krok 3. Wyświetl dane

W SleepTrackerViewModel zmienna nights odwołuje się do LiveData, bo getAllNights() w DAO zwraca wartość LiveData.

To jest funkcja Room, że za każdym razem, gdy dane w bazie danych się zmieniają, LiveData nights jest aktualizowany, aby wyświetlać najnowsze dane. Nie musisz ustawiać atrybutu LiveData ani go aktualizować. Room aktualizuje dane, by były zgodne z bazą danych.

Jeśli jednak wyświetlisz atrybut nights w widoku tekstowym, zobaczysz odniesienie do obiektu. Aby zobaczyć zawartość obiektu, przekonwertuj dane na sformatowany ciąg znaków. Używaj mapy Transformation uruchamianej za każdym razem, gdy nights otrzyma nowe dane z bazy danych.

  1. Otwórz plik Util.kt i cofnij komentarz do kodu definicji formatNights() i powiązanych instrukcji import. Aby usunąć komentarz w Android Studio, zaznacz cały kod oznaczony symbolem // i naciśnij Cmd+/ lub Control+/.
  2. Zauważ, że formatNights() zwraca typ Spanned, który jest ciągiem w formacie HTML.
  3. Otwórz plik strings.xml. Zwróć uwagę na to, jak CDATA sformatował zasoby ciągu znaków na potrzeby wyświetlania danych dotyczących snu.
  4. Otwórz SleepTrackerViewModel. W klasie SleepTrackerViewModel pod definicją uiScope zdefiniuj zmienną o nazwie nights. Pobierz wszystkie noce z bazy danych i przypisz je do zmiennej nights.
private val nights = database.getAllNights()
  1. Tuż pod definicją atrybutu nights dodaj kod, który przekształci nights w element nightsString. Użyj funkcji formatNights() z Util.kt.

    Przekaż nights do funkcji map() z klasy Transformations. Aby uzyskać dostęp do zasobów ciągu znaków, zdefiniuj funkcję mapowania o nazwie formatNights(). Podaj nights i obiekt Resources.
val nightsString = Transformations.map(nights) { nights ->
   formatNights(nights, application.resources)
}
  1. Otwórz plik z układem fragment_sleep_tracker.xml. W usłudze TextView we właściwości android:text możesz teraz zastąpić ciąg zasobu ciągiem nightsString.
"@{sleepTrackerViewModel.nightsString}"
  1. Utwórz ponownie kod i uruchom aplikację. Wszystkie dane dotyczące snu z czasem rozpoczęcia powinny być już widoczne.
  2. Naciśnij kilka razy przycisk Start, aby wyświetlić więcej danych.

W kolejnym kroku włączysz funkcję przycisku Zatrzymaj.

Krok 4. Dodaj moduł do obsługi kliknięć przycisku zatrzymania

Wzorcu do obsługi kliknięcia przycisku Stop w SleepTrackerViewModel. zastosuj ten sam wzorzec co w poprzednim kroku.

  1. Dodaj onStopTracking() do ViewModel. Uruchom algorytm w uiScope. Jeśli godzina zakończenia nie została jeszcze ustawiona, ustaw endTimeMilli na bieżący czas systemowy i wywołaj update() z danymi nocnymi.

    W Kotlin składnia return@label określa funkcję, z której zwracana jest ta instrukcja, wraz z kilkoma zagnieżdżonymi funkcjami.
fun onStopTracking() {
   uiScope.launch {
       val oldNight = tonight.value ?: return@launch
       oldNight.endTimeMilli = System.currentTimeMillis()
       update(oldNight)
   }
}
  1. Zaimplementuj update(), używając tego samego wzoru co przy implementacji insert().
private suspend fun update(night: SleepNight) {
   withContext(Dispatchers.IO) {
       database.update(night)
   }
}
  1. Aby połączyć moduł obsługi kliknięć z interfejsem użytkownika, otwórz plik układu fragment_sleep_tracker.xml i dodaj ten moduł do stop_button.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
  1. Utwórz i uruchom swoją aplikację.
  2. Kliknij Start, a następnie Zatrzymaj. Zobaczysz godzinę rozpoczęcia, czas zakończenia, jakość snu bez wartości i czas snu.

Krok 5. Dodaj moduł do obsługi kliknięć przycisku Wyczyść

  1. I podobnie zaimplementuj onClear() i clear().
fun onClear() {
   uiScope.launch {
       clear()
       tonight.value = null
   }
}

suspend fun clear() {
   withContext(Dispatchers.IO) {
       database.clear()
   }
}
  1. Aby połączyć moduł obsługi kliknięć z interfejsem użytkownika, otwórz fragment_sleep_tracker.xml i dodaj ten moduł do clear_button.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
  1. Utwórz i uruchom swoją aplikację.
  2. Aby usunąć wszystkie dane, kliknij Wyczyść. Następnie kliknij Rozpocznij i Zatrzymaj, aby utworzyć nowe dane.

Projekt na Android Studio: TrackMySleepQualityCoroutines

  • Użyj ViewModel, ViewModelFactory i wiązania danych, aby skonfigurować architekturę interfejsu aplikacji.
  • Aby interfejs działał płynnie, używaj długotrwałych zadań, takich jak wszystkie operacje na bazach danych.
  • Korutyny są asynchroniczne i nie blokują. Używają funkcji suspend, aby przekształcać asynchroniczny kod sekwencyjny.
  • Gdy współprogramowana wywołuje funkcję oznaczoną znakiem suspend, zamiast blokować ją, dopóki nie zostanie zwrócona tak jak normalne wywołanie funkcji, zostanie zawieszona do czasu, aż wynik będzie gotowy. Następnie zostanie wznowiony od miejsca, w którym został wyświetlony.
  • Różnica między blokowaniem a zawieszaniem polega na tym, że jeśli wątek zostanie zablokowany, nic się nie stanie. Jeśli wątek jest zawieszony, inne zadania są wykonywane, dopóki wynik nie będzie dostępny.

Aby uruchomić współtwórcę, musisz mieć zadanie, dyspozytora i zakres:

  • Zadanie to zadanie, które można anulować. Każdy algorytm ma zadanie, którego możesz użyć do anulowania zadania.
  • Dyspozytor wysyła trasy do różnych wątków. Dispatcher.Main uruchamia zadania w wątku głównym, a Dispartcher.IO uruchamia przeniesienie zadań we/wy do udostępnionej puli wątków.
  • Zakres łączy informacje, w tym zadanie i dyspozytora, aby określić kontekst, w którym działa koordynacja. Zakresy służą do śledzenia współprogramów.

Aby zaimplementować moduły obsługi kliknięć, które uruchamiają operacje bazy danych:

  1. Uruchom algorytm działający w wątku głównym lub w wątku, ponieważ wynik ma wpływ na interfejs użytkownika.
  2. Wywołaj funkcję zawieszenia, aby wykonać długotrwałą pracę, dzięki czemu nie musisz blokować wątku interfejsu, czekając na wynik.
  3. Długotrwałe prace nie mają nic wspólnego z interfejsem użytkownika, więc przełącz się na kontekst wejścia-wyjścia. Dzięki temu mogą działać w puli wątków, która jest zoptymalizowana pod kątem takich operacji.
  4. Następnie wykonaj wywołanie funkcji bazy danych.

Użyj mapy Transformations, aby utworzyć ciąg z obiektu LiveData za każdym razem, gdy obiekt się zmieni.

Kurs Udacity:

Dokumentacja Androida dla programistów:

Inne dokumenty i artykuły:

Ta sekcja zawiera listę możliwych zadań domowych dla uczniów, którzy pracują w ramach tego ćwiczenia w ramach kursu prowadzonego przez nauczyciela. To nauczyciel może wykonać te czynności:

  • W razie potrzeby przypisz zadanie domowe.
  • Poinformuj uczniów, jak przesyłać zadania domowe.
  • Oceń projekty domowe.

Nauczyciele mogą wykorzystać te sugestie tak długo, jak chcą lub chcą, i mogą przypisać dowolne zadanie domowe.

Jeśli samodzielnie wykonujesz te ćwiczenia z programowania, możesz sprawdzić swoją wiedzę w tych zadaniach domowych.

Odpowiedz na te pytania.

Pytanie 1

Które z poniższych są zalety algorytmów współprogramów:

  • Nie blokują
  • Działają one asynchronicznie.
  • Można je wykonać w wątku innym niż główny.
  • Zawsze przyspieszają działanie aplikacji
  • Mogą używać wyjątków.
  • Można je zapisywać i odczytywać jako kod liniowy.

Pytanie 2

Co to jest funkcja zawieszenia?

  • Zwykła funkcja oznaczona słowem kluczowym suspend.
  • Funkcja, która może zostać wywołana w korelacjach.
  • Gdy funkcja zawieszenia jest uruchomiona, wątek połączenia jest zawieszony.
  • Funkcja zawieszenia musi zawsze działać w tle.

Pytanie 3

Jaka jest różnica między blokowaniem a zawieszaniem wątku? Zaznacz wszystkie, które są prawdziwe.

  • Gdy zadanie jest zablokowane, w zablokowanym wątku nie można wykonywać innych zadań.
  • Gdy zadanie jest zawieszone, wątek może wykonywać inne czynności podczas oczekiwania na zakończenie przeciążenia.
  • Zawieszenie jest skuteczniejsze, bo wątki mogą nie czekać.
  • Niezależnie od tego, czy zadanie zostało zablokowane, czy zostało zawieszone, wykonanie nadal oczekuje na wyniki.

Przejdź do następnej lekcji: 6.3 Używanie LiveData do kontrolowania stanów przycisków

Linki do innych ćwiczeń z programowania w tym kursie znajdziesz na stronie docelowej z ćwiczeniami z podstaw Androida Kotlin.