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ą
- Pobierz aplikację TrackMySleepQuality-Coroutines-Starter z GitHuba.
- 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.
- Otwórz plik res/layout/activity_main.xml. Ten układ zawiera fragment
nav_host_fragment
. Zwróć też uwagę na tag<merge>
.
Tagumerge
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. - W folderze Nawigacja otwórz plik Nawigacja.xml. Zobaczysz dwa fragmenty i działania nawigacyjne, które je łączą.
- 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
- W pakiecie sleeptracker otwórz SleepTrackerViewModel.kt.
- Sprawdź klasę
SleepTrackerViewModel
, którą znajdziesz w aplikacji startowej. Znajdziesz ją również poniżej. Pamiętaj, że klasa rozciąga się doAndroidViewModel()
. 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
- W pakiecie sleeptracker otwórz SleepTrackerViewModelFactory.kt.
- 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 coViewModel
i rozszerzaViewModelProvider.Factory
. - W fabryce kod zastępuje właściwość
create()
, która przyjmuje dowolny typ klasy jako argument i zwracaViewModel
. - W treści kodu
create()
kod sprawdza, czy jest dostępna klasaSleepTrackerViewModel
, 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
- W
SleepTrackerFragment
zapoznaj się z kontekstem aplikacji. Umieść plik referencyjny w kolumnieonCreateView()
poniżejbinding
. Aby przekazać dane do dostawcy fabryki modeli widoków, potrzebujesz odwołania do aplikacji, z którą jest połączony.
FunkcjarequireNotNull
Kotlin generujeIllegalArgumentException
, jeśli wartość tonull
.
val application = requireNotNull(this.activity).application
- Musisz podać odniesienie do źródła danych przez odwołanie do DAO. W:
onCreateView()
przedreturn
określdataSource
. Aby uzyskać odwołanie do DAO bazy danych, użyjSleepDatabase.getInstance(application).sleepDatabaseDao
.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
- W
onCreateView()
przedreturn
utwórz wystąpienieviewModelFactory
. Musisz zdać testdataSource
iapplication
.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
- Teraz gdy masz już fabrykę, możesz sięgnąć do
SleepTrackerViewModel
. ParametrSleepTrackerViewModel::class.java
odwołuje się do klasy środowiska wykonawczego Java tego obiektu.
val sleepTrackerViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(SleepTrackerViewModel::class.java)
- 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
:
- W bloku
<data>
utwórz<variable>
, który odwołuje się do klasySleepTrackerViewModel
.
<data>
<variable
name="sleepTrackerViewModel"
type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>
Za SleepTrackerFragment
:
- Ustaw bieżącą aktywność jako właściciela cyklu życia wiązania. Dodaj ten kod do metody
onCreateView()
przed instrukcjąreturn
:
binding.setLifecycleOwner(this)
- Przypisz zmienną wiązania
sleepTrackerViewModel
do elementusleepTrackerViewModel
. Umieść ten kod w sekcjionCreateView()
pod kodem, który tworzySleepTrackerViewModel
:
binding.sleepTrackerViewModel = sleepTrackerViewModel
- Prawdopodobnie zobaczysz błąd, ponieważ trzeba odtworzyć obiekt powiązania. Wyczyść i przebuduj projekt, aby usunąć błąd.
- 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.
- 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 plikubuild.gradle
projektu jakocoroutine_version =
'1.0.0'
.
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
- Otwórz plik
SleepTrackerViewModel
. - W treści klasy określ właściwość
viewModelJob
i przypisz jej instancjęJob
. Ten modelviewModelJob
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()
- Na końcu treści zajęć zastąp
onCleared()
i anuluj wszystkie reguły. Po zniszczeniuViewModel
następuje wywołanieonCleared()
.
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
- 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)
- Pod definicją
uiScope
zdefiniuj zmienną o nazwietonight
, 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?>()
- Aby jak najszybciej zainicjować zmienną
tonight
, utwórz blokinit
pod definicją tagutonight
i wywołuj metodęinitializeTonight()
. DefiniujeszinitializeTonight()
w następnym kroku.
init {
initializeTonight()
}
- Pod blokiem
init
zaimplementujinitializeTonight()
. UruchomuiScope
w terenie. Pobierz z bazy danych wartośćtonight
dla wywołaniagetTonightFromDatabase()
i przypisz ją dotonight.value
. DefiniujeszgetTonightFromDatabase()
w następnym kroku.
private fun initializeTonight() {
uiScope.launch {
tonight.value = getTonightFromDatabase()
}
}
- Użyj mechanizmu
getTonightFromDatabase()
. Zdefiniuj ją jako funkcjęprivate suspend
, która zwracaSleepNight
o wartości null. JeśliSleepNight
nie został uruchomiony wcześniej, Powoduje to wystąpienie błędu, ponieważ funkcja musi zwrócić jakąś wartość.
private suspend fun getTonightFromDatabase(): SleepNight? { }
- Wewnątrz treści funkcji
getTonightFromDatabase()
zwróć wynik z kohorty, która działa w kontekścieDispatchers.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) {}
- 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()
.
- Zacznij od definicji funkcji dla
onStartTracking()
. Moduły obsługi kliknięć możesz umieścić w plikuSleepTrackerViewModel
powyżejonCleared()
.
fun onStartTracking() {}
- Otwórz
onStartTracking()
wuiScope
, aby kontynuować i zaktualizować interfejs użytkownika.
uiScope.launch {}
- W ramach uruchamiania wewnętrznego utwórz nowy element
SleepNight
, który rejestruje godzinę bieżącą jako godzinę rozpoczęcia.
val newNight = SleepNight()
- Nie wychodząc z rutyny, wywołaj
insert()
, aby wstawićnewNight
do bazy danych. Pojawi się błąd, ponieważ nie masz jeszcze zdefiniowanej funkcji zawieszeniainsert()
. (To nie jest funkcja DAO o tej samej nazwie).
insert(newNight)
- W przypadku tak wprowadzonych zmian zaktualizuj
tonight
.
tonight.value = getTonightFromDatabase()
- Poniżej
onStartTracking()
definiujeinsert()
jako funkcjęprivate suspend
, która przyjmujeSleepNight
za argument.
private suspend fun insert(night: SleepNight) {}
- W przypadku treści
insert()
uruchom rutynę w kontekście I/O i wstaw noc do bazy danych, wywołującinsert()
z DAO.
withContext(Dispatchers.IO) {
database.insert(night)
}
- W pliku układu
fragment_sleep_tracker.xml
dodaj moduł obsługi kliknięćonStartTracking()
dostart_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 elemenciesleepTrackerViewModel
.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
- 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.
- Otwórz plik
Util.kt
i cofnij komentarz do kodu definicjiformatNights()
i powiązanych instrukcjiimport
. Aby usunąć komentarz w Android Studio, zaznacz cały kod oznaczony symbolem//
i naciśnijCmd+/
lubControl+/
. - Zauważ, że
formatNights()
zwraca typSpanned
, który jest ciągiem w formacie HTML. - 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. - Otwórz SleepTrackerViewModel. W klasie
SleepTrackerViewModel
pod definicjąuiScope
zdefiniuj zmienną o nazwienights
. Pobierz wszystkie noce z bazy danych i przypisz je do zmiennejnights
.
private val nights = database.getAllNights()
- Tuż pod definicją atrybutu
nights
dodaj kod, który przekształcinights
w elementnightsString
. Użyj funkcjiformatNights()
zUtil.kt
.
Przekażnights
do funkcjimap()
z klasyTransformations
. Aby uzyskać dostęp do zasobów ciągu znaków, zdefiniuj funkcję mapowania o nazwieformatNights()
. Podajnights
i obiektResources
.
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
- Otwórz plik z układem
fragment_sleep_tracker.xml
. W usłudzeTextView
we właściwościandroid:text
możesz teraz zastąpić ciąg zasobu ciągiemnightsString
.
"@{sleepTrackerViewModel.nightsString}"
- Utwórz ponownie kod i uruchom aplikację. Wszystkie dane dotyczące snu z czasem rozpoczęcia powinny być już widoczne.
- 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.
- Dodaj
onStopTracking()
doViewModel
. Uruchom algorytm wuiScope
. Jeśli godzina zakończenia nie została jeszcze ustawiona, ustawendTimeMilli
na bieżący czas systemowy i wywołajupdate()
z danymi nocnymi.
W Kotlin składniareturn@
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)
}
}
- Zaimplementuj
update()
, używając tego samego wzoru co przy implementacjiinsert()
.
private suspend fun update(night: SleepNight) {
withContext(Dispatchers.IO) {
database.update(night)
}
}
- Aby połączyć moduł obsługi kliknięć z interfejsem użytkownika, otwórz plik układu
fragment_sleep_tracker.xml
i dodaj ten moduł dostop_button
.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
- Utwórz i uruchom swoją aplikację.
- 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ść
- I podobnie zaimplementuj
onClear()
iclear()
.
fun onClear() {
uiScope.launch {
clear()
tonight.value = null
}
}
suspend fun clear() {
withContext(Dispatchers.IO) {
database.clear()
}
}
- Aby połączyć moduł obsługi kliknięć z interfejsem użytkownika, otwórz
fragment_sleep_tracker.xml
i dodaj ten moduł doclear_button
.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
- Utwórz i uruchom swoją aplikację.
- 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, aDispartcher.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:
- Uruchom algorytm działający w wątku głównym lub w wątku, ponieważ wynik ma wpływ na interfejs użytkownika.
- Wywołaj funkcję zawieszenia, aby wykonać długotrwałą pracę, dzięki czemu nie musisz blokować wątku interfejsu, czekając na wynik.
- 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.
- 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:
RoomDatabase
- Ponowne używanie układów z parametrem <include/>
ViewModelProvider.Factory
SimpleDateFormat
HtmlCompat
Inne dokumenty i artykuły:
- Wzór fabryczny
- Ćwiczenia z programowania
- Koordynacje, oficjalna dokumentacja
- Koordynacyjny kontekst i dyspozytorzy
Dispatchers
- Przekroczenie limitu szybkości Androida
Job
launch
- Powroty i skoki w Kotlinie
- CDATA oznacza dane znaków. Element CDATA oznacza, że dane między tymi ciągami zawierają dane, które mogą zostać zinterpretowane jako znaczniki XML, ale nie powinny.
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:
Linki do innych ćwiczeń z programowania w tym kursie znajdziesz na stronie docelowej z ćwiczeniami z podstaw Androida Kotlin.