Ten moduł Codelab jest częścią kursu Android Kotlin Fundamentals. Najwięcej korzyści przyniesie Ci ukończenie wszystkich ćwiczeń w kolejności. Wszystkie ćwiczenia z tego kursu znajdziesz na stronie docelowej kursu Android Kotlin Fundamentals.
Wprowadzenie
Jednym z najważniejszych priorytetów podczas tworzenia aplikacji jest zapewnienie użytkownikom jak najlepszych wrażeń. W tym celu należy zadbać o to, aby interfejs użytkownika zawsze działał płynnie i szybko reagował na działania użytkownika. Jednym ze sposobów na poprawę wydajności interfejsu jest przeniesienie długotrwałych zadań, takich jak operacje na bazie danych, w tle.
W tym laboratorium kodowania zaimplementujesz część aplikacji TrackMySleepQuality widoczną dla użytkownika. Do wykonywania operacji na bazie danych poza wątkiem głównym użyjesz współprogramów Kotlin.
Co warto wiedzieć
Musisz znać:
- Tworzenie podstawowego interfejsu użytkownika za pomocą aktywności, fragmentów, widoków i procedur obsługi kliknięć.
- Przechodzenie między fragmentami i używanie
safeArgs
do przekazywania prostych danych między fragmentami. - Wyświetl modele, fabryki modeli, przekształcenia i
LiveData
. - Jak utworzyć bazę danych
Room
, utworzyć DAO i zdefiniować encje. - Warto znać koncepcje wątków i wieloprocesowości.
Czego się nauczysz
- Jak działają wątki na Androidzie.
- Jak używać korutyn w Kotlinie, aby przenieść operacje na bazie danych poza wątek główny.
- Jak wyświetlać sformatowane dane w
TextView
.
Jakie zadania wykonasz
- Rozszerz aplikację TrackMySleepQuality, aby zbierać, przechowywać i wyświetlać dane w bazie danych i z niej.
- Używaj korutyn do uruchamiania długotrwałych operacji na bazie danych w tle.
- Użyj
LiveData
, aby wywołać nawigację i wyświetlić pasek informacyjny. - Aby włączyć i wyłączyć przyciski, użyj
LiveData
.
W tym ćwiczeniu utworzysz model widoku, współprogramy i części aplikacji TrackMySleepQuality odpowiedzialne za wyświetlanie danych.
Aplikacja ma 2 ekrany reprezentowane przez fragmenty, jak pokazano na ilustracji poniżej.
Na pierwszym ekranie (po lewej) znajdują się przyciski rozpoczynania i zatrzymywania śledzenia. Na ekranie wyświetlają się wszystkie dane snu użytkownika. Przycisk Wyczyść trwale usuwa wszystkie dane zebrane przez aplikację na temat użytkownika.
Drugi ekran, widoczny po prawej stronie, służy do wybierania oceny jakości snu. W aplikacji ocena jest przedstawiana w formie liczbowej. Na potrzeby programowania aplikacja wyświetla zarówno ikony twarzy, jak i ich odpowiedniki liczbowe.
Proces wygląda następująco:
- Użytkownik otwiera aplikację i widzi ekran śledzenia snu.
- Użytkownik klika przycisk Rozpocznij. Zapisuje to czas rozpoczęcia i wyświetla go. Przycisk Start jest wyłączony, a przycisk Stop jest włączony.
- Użytkownik klika przycisk Zatrzymaj. Zapisze to czas zakończenia i otworzy ekran jakości snu.
- Użytkownik wybiera ikonę jakości snu. Ekran zamyka się, a na ekranie śledzenia wyświetla się godzina zakończenia snu i jego jakość. Przycisk Stop jest wyłączony, a przycisk Start jest włączony. Aplikacja jest gotowa na kolejną noc.
- Przycisk Wyczyść jest włączony, gdy w bazie danych znajdują się dane. Gdy użytkownik kliknie przycisk Wyczyść, wszystkie jego dane zostaną bezpowrotnie usunięte – nie pojawi się komunikat „Czy na pewno?”.
Ta aplikacja korzysta z uproszczonej architektury, jak pokazano poniżej w kontekście pełnej architektury. Aplikacja korzysta tylko z tych komponentów:
- kontroler interfejsu,
- Wyświetl model i
LiveData
- baza danych Room,
W tym zadaniu użyjesz TextView
, aby wyświetlić sformatowane dane śledzenia snu. (To nie jest ostateczny interfejs. Lepszy sposób poznasz w innym codelabie).
Możesz kontynuować pracę nad aplikacją TrackMySleepQuality, którą utworzono w poprzednim przewodniku, lub pobrać aplikację początkową do tego przewodnika.
Krok 1. Pobierz i uruchom aplikację startową
- Pobierz aplikację TrackMySleepQuality-Coroutines-Starter z GitHuba.
- Skompiluj i uruchom aplikację. Wyświetli ona interfejs fragmentu
SleepTrackerFragment
, ale bez danych. Przyciski nie reagują na dotknięcia.
Krok 2. Sprawdź kod
Kod początkowy do tych ćwiczeń z programowania jest taki sam jak kod rozwiązania do ćwiczeń 6.1. Tworzenie bazy danych Room.
- Otwórz plik res/layout/activity_main.xml. Ten układ zawiera fragment
nav_host_fragment
. Zwróć też uwagę na tag<merge>
.
Tagmerge
może być używany do eliminowania zbędnych układów podczas ich uwzględniania. Warto go używać. Przykładem zbędnego układu może być ConstraintLayout > LinearLayout > TextView, w którym system może wyeliminować LinearLayout. Tego rodzaju optymalizacja może uprościć hierarchię widoków i poprawić wydajność aplikacji. - W folderze navigation otwórz plik navigation.xml. Widoczne będą 2 fragmenty i działania nawigacyjne, które je łączą.
- W folderze layout kliknij dwukrotnie fragment śledzenia snu, aby wyświetlić jego układ XML. Zwróć uwagę na te kwestie:
- Dane układu są zawarte w elemencie
<layout>
, aby umożliwić wiązanie danych. ConstraintLayout
i pozostałe widoki są umieszczone w elemencie<layout>
.- Plik zawiera tag zastępczy
<data>
.
Aplikacja startowa zawiera też wymiary, kolory i style interfejsu. Aplikacja zawiera bazę danych Room
, DAO i SleepNight
. Jeśli nie udało Ci się ukończyć poprzedniego laboratorium, zapoznaj się z tymi aspektami kodu.
Teraz, gdy masz już bazę danych i interfejs, musisz zbierać dane, dodawać je do bazy danych i wyświetlać. Wszystkie te działania są wykonywane w modelu widoku. Model widoku trackera snu będzie obsługiwać kliknięcia przycisków, wchodzić w interakcje z bazą danych za pomocą obiektu DAO i dostarczać dane do interfejsu za pomocą LiveData
. Wszystkie operacje na bazie danych będą musiały być wykonywane poza głównym wątkiem interfejsu, a do tego celu będziesz używać korutyn.
Krok 1. Dodaj SleepTrackerViewModel
- W pakiecie sleeptracker otwórz plik SleepTrackerViewModel.kt.
- Sprawdź klasę
SleepTrackerViewModel
, która jest dostępna w aplikacji początkowej i jest też pokazana poniżej. Zwróć uwagę, że klasa rozszerzaAndroidViewModel()
. Ta klasa jest taka sama jakViewModel
, ale przyjmuje kontekst aplikacji jako parametr i udostępnia go jako właściwość. Będzie Ci potrzebny później.
class SleepTrackerViewModel(
val database: SleepDatabaseDao,
application: Application) : AndroidViewModel(application) {
}
Krok 2. Dodaj SleepTrackerViewModelFactory
- W pakiecie sleeptracker otwórz plik SleepTrackerViewModelFactory.kt.
- Sprawdź kod fabryki, który jest podany poniżej:
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")
}
}
Zwróć uwagę na te kwestie:
- Podana funkcja
SleepTrackerViewModelFactory
przyjmuje ten sam argument co funkcjaViewModel
i rozszerzaViewModelProvider.Factory
. - W fabryce kod zastępuje
create()
, który przyjmuje jako argument dowolny typ klasy i zwracaViewModel
. - W treści funkcji
create()
kod sprawdza, czy dostępna jest klasaSleepTrackerViewModel
, a jeśli tak, zwraca jej instancję. W przeciwnym razie kod zgłosi wyjątek.
Krok 3. Zaktualizuj SleepTrackerFragment
- W
SleepTrackerFragment
uzyskaj odniesienie do kontekstu aplikacji. Umieść odwołanie wonCreateView()
, poniżejbinding
. Aby przekazać do dostawcy fabryki modelu widoku, musisz mieć odwołanie do aplikacji, do której jest dołączony ten fragment.
FunkcjarequireNotNull
w Kotlinie zgłasza wyjątekIllegalArgumentException
, jeśli wartość tonull
.
val application = requireNotNull(this.activity).application
- Musisz mieć odniesienie do źródła danych za pomocą odniesienia do obiektu DAO. W polu
onCreateView()
przed znakiemreturn
określdataSource
. Aby uzyskać odniesienie do obiektu DAO bazy danych, użyjSleepDatabase.getInstance(application).sleepDatabaseDao
.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
- W
onCreateView()
przedreturn
utwórz instancjęviewModelFactory
. Musisz przekazaćdataSource
iapplication
.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
- Teraz, gdy masz już fabrykę, uzyskaj odwołanie do
SleepTrackerViewModel
. ParametrSleepTrackerViewModel::class.java
odnosi się do klasy Java w czasie działania 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 dotychczasowe wyniki 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 do modelu widoku
Po utworzeniu podstawowego ViewModel
musisz dokończyć konfigurowanie powiązania danych w SleepTrackerFragment
, aby połączyć ViewModel
z interfejsem.
W pliku układu fragment_sleep_tracker.xml
:
- W bloku
<data>
utwórz element<variable>
, który odwołuje się do klasySleepTrackerViewModel
.
<data>
<variable
name="sleepTrackerViewModel"
type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>
W SleepTrackerFragment
:
- Ustaw bieżącą aktywność jako właściciela cyklu życia powiązania. Dodaj ten kod w metodzie
onCreateView()
przed instrukcjąreturn
:
binding.setLifecycleOwner(this)
- Przypisz zmienną wiążącą
sleepTrackerViewModel
dosleepTrackerViewModel
. Umieść ten kod w sekcjionCreateView()
, pod kodem, który tworzySleepTrackerViewModel
:
binding.sleepTrackerViewModel = sleepTrackerViewModel
- Prawdopodobnie zobaczysz błąd, ponieważ musisz ponownie utworzyć obiekt powiązania. Aby pozbyć się błędu, wyczyść i ponownie skompiluj projekt.
- Na koniec, jak zawsze, upewnij się, że kod kompiluje się i działa bez błędów.
W Kotlinie korutyny to sposób na eleganckie i wydajne obsługiwanie długotrwałych zadań. Korutyny Kotlin umożliwiają przekształcenie kodu opartego na wywołaniach zwrotnych w kod sekwencyjny. Kod napisany sekwencyjnie jest zwykle łatwiejszy do odczytania i może nawet korzystać z funkcji języka, takich jak wyjątki. Ostatecznie korutyny i wywołania zwrotne robią to samo: czekają, aż wynik długotrwałego zadania będzie dostępny, i kontynuują wykonywanie.
Korutyny mają te właściwości:
- Korutyny są asynchroniczne i nieblokujące.
- Korutyny używają funkcji suspend, aby kod asynchroniczny był wykonywany sekwencyjnie.
Korutyny są asynchroniczne.
Korutyna działa niezależnie od głównych kroków wykonywania programu. Może to być przetwarzanie równoległe lub na osobnym procesorze. Może też się zdarzyć, że gdy reszta aplikacji czeka na dane wejściowe, Ty wykonujesz część przetwarzania. Jednym z ważnych aspektów programowania asynchronicznego jest to, że nie możesz oczekiwać, że wynik będzie dostępny, dopóki na niego nie poczekasz.
Załóżmy na przykład, że masz pytanie, które wymaga przeprowadzenia badań, i prosisz współpracownika o znalezienie odpowiedzi. Zajmują się tym, co jest podobne do pracy „asynchronicznej” i „w osobnym wątku”. Możesz kontynuować inne zadania, które nie zależą od odpowiedzi, dopóki kolega nie wróci i nie powie Ci, jaka jest odpowiedź.
Korutyny nie blokują wątków.
Nieblokowanie oznacza, że korutyna nie blokuje wątku głównego ani wątku interfejsu. Dzięki korutynom użytkownicy zawsze mają zapewnioną jak największą wygodę, ponieważ interakcja z interfejsem użytkownika ma zawsze priorytet.
Korutyny używają funkcji zawieszających, aby kod asynchroniczny był wykonywany sekwencyjnie.
Słowo kluczowe suspend
w języku Kotlin oznacza, że funkcja lub typ funkcji jest dostępny dla współprogramów. Gdy korutyna wywołuje funkcję oznaczoną symbolem suspend
, zamiast blokować się do momentu zwrócenia wyniku przez funkcję, jak w przypadku normalnego wywołania funkcji, korutyna wstrzymuje wykonanie do momentu, aż wynik będzie gotowy. Następnie korutyna wznawia działanie w miejscu, w którym zostało ono przerwane, i zwraca wynik.
Gdy korutyna jest zawieszona i oczekuje na wynik, odblokowuje wątek, w którym jest uruchomiona. Dzięki temu mogą działać inne funkcje lub współprogramy.
Słowo kluczowe suspend
nie określa wątku, w którym jest uruchamiany kod. Funkcja zawieszania może być uruchamiana w wątku w tle lub w wątku głównym.
Aby używać w Kotlinie korutyn, potrzebujesz 3 rzeczy:
- Zadanie
- dyspozytor,
- zakres,
Zadanie: zadanie to w zasadzie wszystko, co można anulować. Każda korutyna ma zadanie, za pomocą którego możesz ją anulować. Zadania można układać w hierarchie nadrzędne i podrzędne. Anulowanie zadania nadrzędnego powoduje natychmiastowe anulowanie wszystkich jego zadań podrzędnych, co jest znacznie wygodniejsze niż ręczne anulowanie każdej korutyny.
Dispatcher : wysyła korutyny do uruchomienia w różnych wątkach. Na przykład Dispatcher.Main
wykonuje zadania w wątku głównym, a Dispatcher.IO
przenosi blokujące zadania wejścia-wyjścia do wspólnej puli wątków.
Zakres: zakres korutyny określa kontekst, w którym jest ona wykonywana. Zakres łączy informacje o zadaniu i dispatcherze korutyny. Zakresy śledzą coroutines. Gdy uruchamiasz korutynę, jest ona „w zakresie”, co oznacza, że wskazujesz, który zakres będzie śledzić korutynę.
Chcesz, aby użytkownik mógł korzystać z danych o śnie w ten sposób:
- Gdy użytkownik naciśnie przycisk Rozpocznij, aplikacja utworzy nowy nocny sen i zapisze go w bazie danych.
- Gdy użytkownik naciśnie przycisk Stop, aplikacja zaktualizuje noc, dodając godzinę zakończenia.
- Gdy użytkownik kliknie przycisk Wyczyść, aplikacja usunie dane z bazy danych.
Te operacje na bazie danych mogą trwać długo, więc powinny być wykonywane w osobnym wątku.
Krok 1. Skonfiguruj współprogramy do operacji na bazie danych
Gdy użytkownik kliknie przycisk Start w aplikacji Sleep Tracker, chcesz wywołać funkcję w SleepTrackerViewModel
, aby utworzyć nową instancję SleepNight
i zapisać ją w bazie danych.
Kliknięcie dowolnego przycisku powoduje wykonanie operacji na bazie danych, np. utworzenie lub zaktualizowanie SleepNight
. Z tego i innych powodów do implementowania procedur obsługi kliknięć przycisków aplikacji używasz korutyn.
- Otwórz plik
build.gradle
na poziomie aplikacji i znajdź zależności dla współprogramów. Aby używać współprogramów, musisz mieć te zależności, które zostały dodane za Ciebie.$coroutine_version
jest zdefiniowany 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 zdefiniuj
viewModelJob
i przypisz do niego instancjęJob
. TenviewModelJob
umożliwia anulowanie wszystkich współprogramów uruchomionych przez ten model widoku, gdy model widoku nie jest już używany i zostaje zniszczony. Dzięki temu nie będziesz mieć korutyn, które nie mają dokąd wrócić.
private var viewModelJob = Job()
- Na końcu treści klasy zastąp
onCleared()
i anuluj wszystkie korutyny. GdyViewModel
zostanie zniszczony, wywoływana jest funkcjaonCleared()
.
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
- Bezpośrednio pod definicją
viewModelJob
zdefiniujuiScope
dla korutyn. Zakres określa, w którym wątku będzie działać korutyna, i musi też znać zadanie. Aby uzyskać zakres, poproś o instancjęCoroutineScope
i przekaż dyspozytora oraz zadanie.
Użycie Dispatchers.Main
oznacza, że korutyny uruchomione w uiScope
będą działać w wątku głównym. Jest to rozsądne w przypadku wielu coroutines uruchamianych przez ViewModel
, ponieważ po wykonaniu pewnych operacji powodują one aktualizację interfejsu.
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
- Pod definicją
uiScope
zdefiniuj zmienną o nazwietonight
, która będzie przechowywać bieżącą noc. Ustaw zmienną naMutableLiveData
, ponieważ musisz mieć możliwość obserwowania danych i ich zmiany.
private var tonight = MutableLiveData<SleepNight?>()
- Aby zainicjować zmienną
tonight
jak najszybciej, utwórz blokinit
poniżej definicji zmiennejtonight
i wywołaj funkcjęinitializeTonight()
. W następnym kroku zdefiniujeszinitializeTonight()
.
init {
initializeTonight()
}
- Pod blokiem
init
wdróżinitializeTonight()
. WuiScope
uruchom korutynę. Wewnątrz pobierz wartośćtonight
z bazy danych, wywołującgetTonightFromDatabase()
, i przypisz ją dotonight.value
. W następnym kroku zdefiniujeszgetTonightFromDatabase()
.
private fun initializeTonight() {
uiScope.launch {
tonight.value = getTonightFromDatabase()
}
}
- Użyj mechanizmu
getTonightFromDatabase()
. Zdefiniuj ją jako funkcjęprivate suspend
, która zwraca wartość dopuszczającą wartość nullSleepNight
, jeśli nie ma obecnie uruchomionegoSleepNight
. W takim przypadku wystąpi błąd, ponieważ funkcja musi coś zwrócić.
private suspend fun getTonightFromDatabase(): SleepNight? { }
- W treści funkcji
getTonightFromDatabase()
zwróć wynik z korutyny, która działa w kontekścieDispatchers.IO
. Użyj dyspozytora wejścia/wyjścia, ponieważ pobieranie danych z bazy danych to operacja wejścia/wyjścia, która nie ma nic wspólnego z interfejsem.
return withContext(Dispatchers.IO) {}
- W bloku zwracania pozwól korutynie pobrać z bazy danych dzisiejszą noc (najnowszą). Jeśli godziny rozpoczęcia i zakończenia nie są takie same, co oznacza, że noc już się skończyła, zwróć wartość
null
. W przeciwnym razie zwróć noc.
var night = database.getTonight()
if (night?.endTimeMilli != night?.startTimeMilli) {
night = null
}
night
Ukończona funkcja getTonightFromDatabase()
zawieszania powinna wyglądać tak. Nie powinno być już ż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 obsługę kliknięcia przycisku Start
Teraz możesz zaimplementować onStartTracking()
, czyli procedurę obsługi kliknięć przycisku Start. Musisz utworzyć nowy SleepNight
, wstawić go do bazy danych i przypisać do tonight
. Struktura onStartTracking()
będzie bardzo podobna do struktury initializeTonight()
.
- Zacznij od definicji funkcji
onStartTracking()
. Moduły obsługi kliknięć możesz umieścić nadonCleared()
w plikuSleepTrackerViewModel
.
fun onStartTracking() {}
- W funkcji
onStartTracking()
uruchom korutynę w funkcjiuiScope
, ponieważ potrzebujesz tego wyniku, aby kontynuować i zaktualizować interfejs.
uiScope.launch {}
- W ramach uruchomienia korutyny utwórz nowy obiekt
SleepNight
, który rejestruje bieżący czas jako czas rozpoczęcia.
val newNight = SleepNight()
- W ramach uruchomienia korutyny wywołaj funkcję
insert()
, aby wstawić do bazy danych wartośćnewNight
. Pojawi się błąd, ponieważ nie zdefiniowano jeszcze funkcjiinsert()
suspend. (Nie jest to funkcja DAO o tej samej nazwie).
insert(newNight)
- W ramach uruchomienia korutyny zaktualizuj też
tonight
.
tonight.value = getTonightFromDatabase()
- Poniżej
onStartTracking()
zdefiniujinsert()
jako funkcjęprivate suspend
, która przyjmuje argumentSleepNight
.
private suspend fun insert(night: SleepNight) {}
- W treści funkcji
insert()
uruchom korutynę w kontekście wejścia/wyjścia i wstaw noc do bazy danych, wywołując funkcjęinsert()
z obiektu DAO.
withContext(Dispatchers.IO) {
database.insert(night)
}
- W pliku układu
fragment_sleep_tracker.xml
dodaj moduł obsługi kliknięć elementuonStartTracking()
do elementustart_button
, korzystając z powiązywania danych skonfigurowanego wcześniej. Notacja funkcji@{() ->
tworzy funkcję lambda, która nie przyjmuje argumentów i wywołuje moduł obsługi kliknięć wsleepTrackerViewModel
.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
- Utwórz i uruchom aplikację. Kliknij przycisk Start. To działanie tworzy dane, ale na razie nic nie widać. Zrobisz to w następnym kroku.
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
, ponieważ getAllNights()
w obiekcie dostępu do danych zwraca LiveData
.
Jest to funkcja Room
, która powoduje, że za każdym razem, gdy dane w bazie ulegają zmianie, LiveData
nights
jest aktualizowany, aby wyświetlać najnowsze dane. Nie musisz jawnie ustawiać ani aktualizować LiveData
. Room
aktualizuje dane, aby pasowały do bazy danych.
Jeśli jednak wyświetlisz nights
w widoku tekstowym, pojawi się odwołanie do obiektu. Aby zobaczyć zawartość obiektu, przekształć dane w sformatowany ciąg znaków. Użyj mapy Transformation
, która jest wykonywana za każdym razem, gdy nights
otrzymuje nowe dane z bazy danych.
- Otwórz plik
Util.kt
i usuń komentarz z kodu definicjiformatNights()
oraz powiązanych instrukcjiimport
. Aby odkomentować kod w Android Studio, zaznacz cały kod oznaczony symbolem//
i naciśnij klawiszCmd+/
lubControl+/
. - Zwraca typ
Spanned
, czyli ciąg znaków w formacie HTML.formatNights()
- Otwórz plik strings.xml. Zwróć uwagę na użycie
CDATA
do sformatowania zasobów ciągów znaków w celu wyświetlania danych o śnie. - Otwórz plik SleepTrackerViewModel. W klasie
SleepTrackerViewModel
, poniżej definicjiuiScope
, zdefiniuj zmienną o nazwienights
. Pobierz wszystkie noce z bazy danych i przypisz je do zmiennejnights
.
private val nights = database.getAllNights()
- Bezpośrednio pod definicją zmiennej
nights
dodaj kod, który przekształci zmiennąnights
w zmiennąnightsString
. Użyj funkcjiformatNights()
z klasyUtil.kt
.
Przekaż wartośćnights
do funkcjimap()
z klasyTransformations
. Aby uzyskać dostęp do zasobów ciągów, zdefiniuj funkcję mapowania jako wywołującąformatNights()
. Podajnights
i obiektResources
.
val nightsString = Transformations.map(nights) { nights ->
formatNights(nights, application.resources)
}
- Otwórz plik układu
fragment_sleep_tracker.xml
. WTextView
we właściwościandroid:text
możesz teraz zastąpić ciąg znaków zasobu odwołaniem donightsString
.
"@{sleepTrackerViewModel.nightsString}"
- Ponownie skompiluj kod i uruchom aplikację. Powinny się teraz wyświetlać wszystkie dane o śnie, w tym godziny rozpoczęcia.
- Kliknij przycisk Rozpocznij jeszcze kilka razy, aby zobaczyć więcej danych.
W następnym kroku włączysz funkcję przycisku Stop.
Krok 4. Dodaj obsługę kliknięcia przycisku Stop
Korzystając z tego samego wzorca co w poprzednim kroku, zaimplementuj procedurę obsługi kliknięcia przycisku Stop w SleepTrackerViewModel.
- Dodaj
onStopTracking()
doViewModel
. Uruchomienie korutyny wuiScope
. Jeśli czas zakończenia nie został jeszcze ustawiony, ustawendTimeMilli
na bieżący czas systemowy i wywołajupdate()
z danymi nocnymi.
W języku Kotlin składniareturn@
label
określa funkcję, z której ten komunikat jest zwracany, spośród kilku zagnieżdżonych funkcji.
fun onStopTracking() {
uiScope.launch {
val oldNight = tonight.value ?: return@launch
oldNight.endTimeMilli = System.currentTimeMillis()
update(oldNight)
}
}
- Zaimplementuj
update()
, używając tego samego wzorca co w przypadkuinsert()
.
private suspend fun update(night: SleepNight) {
withContext(Dispatchers.IO) {
database.update(night)
}
}
- Aby połączyć moduł obsługi kliknięć z interfejsem, otwórz plik układu
fragment_sleep_tracker.xml
i dodaj moduł obsługi kliknięć do elementustop_button
.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
- Skompiluj i uruchom aplikację.
- Kliknij Start, a potem Stop. Wyświetli się godzina rozpoczęcia i zakończenia snu, jakość snu bez wartości oraz czas snu.
Krok 5. Dodaj procedurę obsługi kliknięcia przycisku Wyczyść
- Podobnie wdróż atrybuty
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, otwórz
fragment_sleep_tracker.xml
i dodaj moduł obsługi kliknięć doclear_button
.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
- Skompiluj i uruchom aplikację.
- Kliknij Wyczyść, aby usunąć wszystkie dane. Następnie kliknij Start i Stop, aby utworzyć nowe dane.
Projekt Android Studio: TrackMySleepQualityCoroutines
- Użyj
ViewModel
,ViewModelFactory
i powiązania danych, aby skonfigurować architekturę interfejsu aplikacji. - Aby interfejs działał płynnie, używaj w przypadku długotrwałych zadań, takich jak wszystkie operacje na bazie danych, współprogramów.
- Korutyny są asynchroniczne i nieblokujące. Używają funkcji
suspend
, aby kod asynchroniczny był wykonywany sekwencyjnie. - Gdy korutyna wywołuje funkcję oznaczoną symbolem
suspend
, zamiast blokować się do momentu zwrócenia przez nią wartości (jak w przypadku zwykłego wywołania funkcji), zawiesza wykonanie do czasu, aż wynik będzie gotowy. Następnie wznawia działanie od miejsca, w którym zostało przerwane, i wyświetla wynik. - Różnica między blokowaniem a zawieszaniem polega na tym, że jeśli wątek jest zablokowany, nie są wykonywane żadne inne działania. Jeśli wątek jest zawieszony, inne zadania są wykonywane do momentu uzyskania wyniku.
Aby uruchomić korutynę, potrzebujesz zadania, dyspozytora i zakresu:
- Zadanie to w zasadzie wszystko, co można anulować. Każda korutyna ma zadanie, którego możesz użyć do jej anulowania.
- Dispatcher wysyła coroutines do uruchomienia w różnych wątkach.
Dispatcher.Main
wykonuje zadania w wątku głównym, aDispartcher.IO
służy do przenoszenia blokujących zadań wejścia-wyjścia do wspólnej puli wątków. - Zakres łączy informacje, w tym zadanie i dyspozytora, aby zdefiniować kontekst, w którym działa korutyna. Zakresy śledzą coroutines.
Aby zaimplementować procedury obsługi kliknięć, które wywołują operacje na bazie danych, postępuj zgodnie z tym wzorcem:
- Uruchomienie korutyny, która działa w głównym wątku lub wątku interfejsu, ponieważ wynik wpływa na interfejs.
- Wywołaj funkcję zawieszającą, aby wykonać długotrwałą pracę, dzięki czemu nie zablokujesz wątku interfejsu podczas oczekiwania na wynik.
- Długotrwałe zadanie nie ma nic wspólnego z interfejsem, więc przełącz się na kontekst wejścia/wyjścia. Dzięki temu zadanie może być wykonywane w puli wątków zoptymalizowanej i zarezerwowanej dla tego typu operacji.
- Następnie wywołaj funkcję bazy danych, aby wykonać zadanie.
Użyj mapowania Transformations
, aby utworzyć ciąg znaków z obiektu LiveData
za każdym razem, gdy obiekt się zmieni.
Kurs Udacity:
Dokumentacja dla deweloperów Androida:
RoomDatabase
- Ponowne wykorzystywanie układów za pomocą tagu <include/>
ViewModelProvider.Factory
SimpleDateFormat
HtmlCompat
Inne dokumenty i artykuły:
- Wzór fabryczny
- Ćwiczenia z programowania dotyczące współprogramów
- Korutyny, oficjalna dokumentacja
- Kontekst i dyspozytorzy korutyny
Dispatchers
- Przekraczanie limitu prędkości na Androidzie
Job
launch
- Instrukcje return i jump w języku Kotlin
- CDATA to skrót od character data (dane znakowe). CDATA oznacza, że dane między tymi ciągami znaków zawierają dane, które można zinterpretować jako znaczniki XML, ale nie powinny być tak interpretowane.
W tej sekcji znajdziesz listę możliwych zadań domowych dla uczniów, którzy wykonują ten moduł w ramach kursu prowadzonego przez instruktora. Nauczyciel musi:
- W razie potrzeby przypisz pracę domową.
- Poinformuj uczniów, jak przesyłać projekty.
- Oceń zadania domowe.
Instruktorzy mogą korzystać z tych sugestii w dowolnym zakresie i mogą zadawać inne zadania domowe, które uznają za odpowiednie.
Jeśli wykonujesz ten kurs samodzielnie, możesz użyć tych zadań domowych, aby sprawdzić swoją wiedzę.
Odpowiedz na te pytania
Pytanie 1
Które z tych stwierdzeń opisują zalety korutyn:
- Nie blokują one działania aplikacji.
- Działają asynchronicznie.
- Mogą być uruchamiane w wątku innym niż główny.
- Zawsze przyspieszają działanie aplikacji.
- Może użyć wyjątków.
- Można je zapisywać i odczytywać jako kod liniowy.
Pytanie 2
Co to jest funkcja zawieszania?
- Zwykła funkcja oznaczona słowem kluczowym
suspend
. - Funkcja, którą można wywoływać w korutynach.
- Gdy funkcja zawieszania jest uruchomiona, wątek wywołujący jest zawieszony.
- Funkcje zawieszania muszą zawsze działać w tle.
Pytanie 3
Jaka jest różnica między blokowaniem a zawieszaniem wątku? Zaznacz wszystkie prawdziwe stwierdzenia.
- Gdy wykonanie jest zablokowane, w zablokowanym wątku nie można wykonywać żadnych innych działań.
- Gdy wykonanie jest zawieszone, wątek może wykonywać inne zadania, czekając na zakończenie przeniesionej pracy.
- Zawieszanie jest bardziej wydajne, ponieważ wątki mogą nie czekać i nic nie robić.
- Niezależnie od tego, czy jest zablokowana, czy zawieszona, funkcja oczekuje na wynik korutyny, zanim będzie mogła kontynuować działanie.
Przejdź do następnej lekcji:
Linki do innych ćwiczeń z tego kursu znajdziesz na stronie docelowej ćwiczeń z podstaw języka Kotlin na Androidzie.