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
W poprzednich ćwiczeniach z programowania w tej lekcji ulepszyliśmy kod aplikacji GuessTheWord. Aplikacja używa teraz obiektów ViewModel
, dzięki czemu dane aplikacji są zachowywane po zmianach konfiguracji urządzenia, takich jak obracanie ekranu czy zmiany dostępności klawiatury. Dodano też obiekt obserwowany LiveData
, więc widoki są automatycznie powiadamiane o zmianach w obserwowanych danych.
W tym laboratorium kodowania będziesz kontynuować pracę nad aplikacją GuessTheWord. Powiążesz widoki z klasami ViewModel
w aplikacji, aby widoki w układzie komunikowały się bezpośrednio z obiektami ViewModel
. (Do tej pory w aplikacji widoki komunikowały się z ViewModel
pośrednio za pomocą fragmentów aplikacji). Po zintegrowaniu powiązania danych z obiektami ViewModel
nie potrzebujesz już w fragmentach aplikacji obsługi kliknięć, więc możesz ją usunąć.
Zmienisz też aplikację GuessTheWord, aby używała LiveData
jako źródła powiązania danych do powiadamiania interfejsu o zmianach w danych bez używania metod obserwatora LiveData
.
Co warto wiedzieć
- Jak tworzyć podstawowe aplikacje na Androida w języku Kotlin.
- Jak działają cykle życia aktywności i fragmentów.
- Jak używać obiektów
ViewModel
w aplikacji. - Jak przechowywać dane za pomocą
LiveData
wViewModel
. - Jak dodać metody obserwatora, aby obserwować zmiany w danych
LiveData
.
Czego się nauczysz
- Jak używać elementów biblioteki powiązań danych.
- Jak zintegrować
ViewModel
z powiązaniem danych. - Jak zintegrować
LiveData
z powiązaniem danych. - Jak używać powiązań odbiorników, aby zastąpić odbiorniki kliknięć we fragmencie.
- Jak dodawać formatowanie ciągów znaków do wyrażeń wiązania danych.
Jakie zadania wykonasz
- Widoki w układach GuessTheWord komunikują się pośrednio z obiektami
ViewModel
, używając kontrolerów interfejsu (fragmentów) do przekazywania informacji. W tych ćwiczeniach powiążesz widoki aplikacji z obiektamiViewModel
, aby widoki komunikowały się bezpośrednio z obiektamiViewModel
. - Zmieniasz aplikację, aby używała
LiveData
jako źródła powiązań danych. Po tej zmianie obiektyLiveData
powiadamiają interfejs użytkownika o zmianach w danych, a metody obserwatoraLiveData
nie są już potrzebne.
W ćwiczeniach z programowania w lekcji 5 tworzysz aplikację GuessTheWord, zaczynając od kodu początkowego. GuessTheWord to gra w kalambury dla 2 osób, w której gracze współpracują, aby uzyskać jak najwyższy wynik.
Pierwszy gracz patrzy na słowa w aplikacji i kolejno odgrywa każde z nich, uważając, aby nie pokazać słowa drugiemu graczowi. Drugi gracz próbuje odgadnąć słowo.
Aby rozpocząć grę, pierwszy gracz otwiera aplikację na urządzeniu i widzi słowo, np. „gitara”, jak pokazano na zrzucie ekranu poniżej.
Pierwszy gracz odgrywa słowo, uważając, aby go nie wypowiedzieć.
- Gdy drugi gracz odgadnie słowo, pierwszy gracz naciśnie przycisk Got It (Mam to), co zwiększy liczbę o 1 i wyświetli kolejne słowo.
- Jeśli drugi gracz nie odgadnie słowa, pierwszy gracz naciśnie przycisk Pomiń, co zmniejszy liczbę o 1 i spowoduje przejście do następnego słowa.
- Aby zakończyć grę, naciśnij przycisk Zakończ grę. (Ta funkcja nie jest dostępna w kodzie początkowym pierwszego laboratorium w tej serii).
W tym laboratorium nauczysz się ulepszać aplikację GuessTheWord, integrując powiązanie danych z LiveData
w obiektach ViewModel
. Automatyzuje to komunikację między widokami w układzie a obiektami ViewModel
i umożliwia uproszczenie kodu przez użycie LiveData
.
Ekran tytułowy | Ekran gry | Ekran wyników |
W tym zadaniu znajdziesz i uruchomisz kod początkowy do tego ćwiczenia z programowania. Możesz użyć aplikacji GuessTheWord utworzonej w poprzednim laboratorium, lub pobrać aplikację startową.
- (Opcjonalnie) Jeśli nie używasz kodu z poprzednich ćwiczeń z programowania, pobierz kod początkowy do tych ćwiczeń. Rozpakuj kod i otwórz projekt w Android Studio.
- Uruchom aplikację i zagraj w grę.
- Zauważ, że przycisk OK wyświetla następne słowo i zwiększa wynik o 1, a przycisk Pomiń wyświetla następne słowo i zmniejsza wynik o 1. Przycisk Zakończ grę kończy grę.
- Przejdź przez wszystkie słowa i zauważ, że aplikacja automatycznie przechodzi do ekranu z wynikami.
W poprzednim module wykorzystaliśmy powiązanie danych jako bezpieczny pod względem typów sposób uzyskiwania dostępu do widoków w aplikacji GuessTheWord. Prawdziwa moc powiązania danych polega jednak na tym, co sugeruje nazwa: powiązaniu danych bezpośrednio z obiektami widoku w aplikacji.
Obecna architektura aplikacji
W aplikacji widoki są zdefiniowane w układzie XML, a dane tych widoków są przechowywane w obiektach ViewModel
. Pomiędzy każdym widokiem a odpowiadającym mu elementem ViewModel
znajduje się kontroler interfejsu, który działa jako przekaźnik między nimi.
Na przykład:
- Przycisk OK jest zdefiniowany jako widok
Button
w pliku układugame_fragment.xml
. - Gdy użytkownik kliknie przycisk OK, odbiornik kliknięć we fragmencie
GameFragment
wywoła odpowiedni odbiornik kliknięć we fragmencieGameViewModel
. - Wynik zostanie zaktualizowany w
GameViewModel
.
Widok Button
i GameViewModel
nie komunikują się bezpośrednio – potrzebują odbiornika kliknięć w GameFragment
.
ViewModel przekazywany do powiązania danych
Byłoby prościej, gdyby widoki w układzie komunikowały się bezpośrednio z danymi w obiektach ViewModel
bez polegania na kontrolerach interfejsu jako pośrednikach.
Obiekty ViewModel
zawierają wszystkie dane interfejsu w aplikacji GuessTheWord. Przekazując obiekty ViewModel
do powiązania danych, możesz zautomatyzować część komunikacji między widokami a obiektami ViewModel
.
W tym ćwiczeniu powiążesz klasy GameViewModel
i ScoreViewModel
z odpowiednimi układami XML. Konfigurujesz też powiązania odbiorników, aby obsługiwać zdarzenia kliknięcia.
Krok 1. Dodaj wiązanie danych dla klasy GameViewModel
W tym kroku powiążesz plik GameViewModel
z odpowiednim plikiem układu game_fragment.xml
.
- W pliku
game_fragment.xml
dodaj zmienną wiązania danych typuGameViewModel
. Jeśli w Android Studio występują błędy, wyczyść i ponownie skompiluj projekt.
<layout ...>
<data>
<variable
name="gameViewModel"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>
<androidx.constraintlayout...
- W pliku
GameFragment
przekażGameViewModel
do powiązania danych.
Aby to zrobić, przypiszviewModel
do zmiennejbinding.gameViewModel
zadeklarowanej w poprzednim kroku. Umieść ten kod w taguonCreateView()
po zainicjowaniu taguviewModel
. Jeśli w Android Studio występują błędy, wyczyść i ponownie skompiluj projekt.
// Set the viewmodel for databinding - this allows the bound layout access
// to all the data in the ViewModel
binding.gameViewModel = viewModel
Krok 2. Używaj powiązań odbiorników do obsługi zdarzeń
Powiązania detektorów to wyrażenia powiązań, które są uruchamiane, gdy wyzwalane są zdarzenia takie jak onClick()
, onZoomIn()
lub onZoomOut()
. Powiązania odbiorników są zapisywane jako wyrażenia lambda.
Powiązanie danych tworzy odbiornik i ustawia go w widoku. Gdy nastąpi zdarzenie, którego nasłuchuje detektor, ocenia on wyrażenie lambda. Powiązania odbiorników działają z wtyczką Androida do obsługi Gradle w wersji 2.0 lub nowszej. Więcej informacji znajdziesz w artykule Układy i wyrażenia wiążące.
W tym kroku zastąpisz odbiorniki kliknięć w pliku GameFragment
powiązaniami odbiorników w pliku game_fragment.xml
.
- W sekcji
game_fragment.xml
dodaj atrybutonClick
do elementuskip_button
. Zdefiniuj wyrażenie wiążące i wywołaj metodęonSkip()
wGameViewModel
. To wyrażenie wiążące jest nazywane wiązaniem odbiornika.
<Button
android:id="@+id/skip_button"
...
android:onClick="@{() -> gameViewModel.onSkip()}"
... />
- Podobnie powiąż zdarzenie kliknięcia elementu
correct_button
z metodąonCorrect
()
w elemencieGameViewModel
.
<Button
android:id="@+id/correct_button"
...
android:onClick="@{() -> gameViewModel.onCorrect()}"
... />
- Powiąż zdarzenie kliknięcia elementu
end_game_button
z metodąonGameFinish
()
wGameViewModel
.
<Button
android:id="@+id/end_game_button"
...
android:onClick="@{() -> gameViewModel.onGameFinish()}"
... />
- W
GameFragment
usuń instrukcje, które ustawiają odbiorniki kliknięć, oraz funkcje, które te odbiorniki wywołują. Nie potrzebujesz już tych informacji.
Kod do usunięcia:
binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
binding.endGameButton.setOnClickListener { onEndGame() }
/** Methods for buttons presses **/
private fun onSkip() {
viewModel.onSkip()
}
private fun onCorrect() {
viewModel.onCorrect()
}
private fun onEndGame() {
gameFinished()
}
Krok 3. Dodaj powiązanie danych dla klasy ScoreViewModel
W tym kroku powiążesz plik ScoreViewModel
z odpowiednim plikiem układu score_fragment.xml
.
- W pliku
score_fragment.xml
dodaj zmienną wiązania typuScoreViewModel
. Ten krok jest podobny do tego, który wykonaliśmy w przypadkuGameViewModel
powyżej.
<layout ...>
<data>
<variable
name="scoreViewModel"
type="com.example.android.guesstheword.screens.score.ScoreViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
- W sekcji
score_fragment.xml
dodaj atrybutonClick
do elementuplay_again_button
. Zdefiniuj powiązanie odbiornika i wywołaj metodęonPlayAgain()
wScoreViewModel
.
<Button
android:id="@+id/play_again_button"
...
android:onClick="@{() -> scoreViewModel.onPlayAgain()}"
... />
- W
ScoreFragment
wonCreateView()
zainicjujviewModel
. Następnie zainicjuj zmienną wiązaniabinding.scoreViewModel
.
viewModel = ...
binding.scoreViewModel = viewModel
- W
ScoreFragment
usuń kod, który ustawia odbiornik kliknięć dla elementuplayAgainButton
. Jeśli Android Studio wyświetli błąd, wyczyść i ponownie skompiluj projekt.
Kod do usunięcia:
binding.playAgainButton.setOnClickListener { viewModel.onPlayAgain() }
- Uruchom aplikację. Powinna działać jak wcześniej, ale teraz widoki przycisków komunikują się bezpośrednio z obiektami
ViewModel
. Widoki nie komunikują się już za pomocą funkcji obsługi kliknięć przycisków wScoreFragment
.
Rozwiązywanie problemów z komunikatami o błędach powiązań danych
Gdy aplikacja korzysta z powiązania danych, proces kompilacji generuje klasy pośrednie, które są używane do powiązania danych. Aplikacja może zawierać błędy, których Android Studio nie wykryje, dopóki nie spróbujesz skompilować aplikacji. Dlatego podczas pisania kodu nie widzisz ostrzeżeń ani czerwonego kodu. Podczas kompilacji pojawiają się jednak niejasne błędy pochodzące z wygenerowanych klas pośrednich.
Jeśli pojawi się niejasny komunikat o błędzie:
- Przyjrzyj się uważnie komunikatowi w panelu Build (Kompilacja) w Android Studio. Jeśli widzisz lokalizację, która kończy się na
databinding
, oznacza to błąd w powiązaniu danych. - W pliku XML układu sprawdź, czy nie ma błędów w atrybutach
onClick
, które używają powiązania danych. Wyszukaj funkcję, do której odwołuje się wyrażenie lambda, i upewnij się, że istnieje. - W sekcji
<data>
pliku XML sprawdź pisownię zmiennej powiązania danych.
Na przykład zwróć uwagę na błąd w nazwie funkcji onCorrect()
w poniższej wartości atrybutu:
android:onClick="@{() -> gameViewModel.onCorrectx()}"
Zwróć też uwagę na błąd w pisowni słowa gameViewModel
w sekcji <data>
pliku XML:
<data>
<variable
name="gameViewModelx"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>
Android Studio nie wykrywa takich błędów, dopóki nie skompilujesz aplikacji. Kompilator wyświetla wtedy komunikat o błędzie, np. ten:
error: cannot find symbol import com.example.android.guesstheword.databinding.GameFragmentBindingImpl" symbol: class GameFragmentBindingImpl location: package com.example.android.guesstheword.databinding
Wiązanie danych dobrze współpracuje z LiveData
używanym z obiektami ViewModel
. Po dodaniu powiązania danych do obiektów ViewModel
możesz włączyć LiveData
.
W tym ćwiczeniu zmienisz aplikację GuessTheWord, aby używała LiveData
jako źródła powiązań danych do powiadamiania interfejsu o zmianach w danych bez używania metod obserwatora LiveData
.
Krok 1. Dodaj LiveData słowa do pliku game_fragment.xml
W tym kroku powiążesz bieżący widok tekstu słowa bezpośrednio z obiektem LiveData
w ViewModel
.
- W
game_fragment.xml
dodaj atrybutandroid:text
do widoku tekstuword_text
.
Ustaw go na obiekt LiveData
, word
z GameViewModel
, używając zmiennej wiążącej gameViewModel
.
<TextView
android:id="@+id/word_text"
...
android:text="@{gameViewModel.word}"
... />
Pamiętaj, że nie musisz używać znaku word.value
. Zamiast tego możesz użyć rzeczywistego obiektu LiveData
. Obiekt LiveData
wyświetla aktualną wartość word
. Jeśli wartość word
to null, obiekt LiveData
wyświetla pusty ciąg znaków.
- W
GameFragment
wonCreateView()
po zainicjowaniugameViewModel
ustaw bieżącą aktywność jako właściciela cyklu życia zmiennejbinding
. Określa to zakres powyższego obiektuLiveData
, co umożliwia mu automatyczne aktualizowanie widoków w układziegame_fragment.xml
.
binding.gameViewModel = ...
// Specify the current activity as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = this
- W sekcji
GameFragment
usuń obserwatora z kontaLiveData
word
.
Kod do usunięcia:
/** Setting up LiveData observation relationship **/
viewModel.word.observe(this, Observer { newWord ->
binding.wordText.text = newWord
})
- Uruchom aplikację i zagraj w grę. Obecne słowo jest teraz aktualizowane bez metody obserwatora w kontrolerze interfejsu.
Krok 2. Dodaj obiekt LiveData wyniku do pliku score_fragment.xml
W tym kroku powiążesz LiveData
score
z widokiem tekstu wyniku we fragmencie wyniku.
- W
score_fragment.xml
dodaj atrybutandroid:text
do widoku tekstu wyniku. PrzypiszscoreViewModel.score
do atrybututext
. Ponieważscore
jest liczbą całkowitą, przekształć ją w ciąg znaków za pomocą funkcjiString.valueOf()
.
<TextView
android:id="@+id/score_text"
...
android:text="@{String.valueOf(scoreViewModel.score)}"
... />
- W
ScoreFragment
po zainicjowaniuscoreViewModel
ustaw bieżącą aktywność jako właściciela cyklu życia zmiennejbinding
.
binding.scoreViewModel = ...
// Specify the current activity as the lifecycle owner of the binding.
// This is used so that the binding can observe LiveData updates
binding.lifecycleOwner = this
- W
ScoreFragment
usuń obserwatora obiektuscore
.
Kod do usunięcia:
// Add observer for score
viewModel.score.observe(this, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})
- Uruchom aplikację i zagraj w grę. Zwróć uwagę, że wynik w fragmencie partytury jest wyświetlany prawidłowo, bez obserwatora w tym fragmencie.
Krok 3. Dodaj formatowanie ciągów znaków za pomocą wiązania danych
W układzie możesz dodać formatowanie ciągów znaków wraz z wiązaniem danych. W tym zadaniu sformatujesz bieżące słowo, dodając do niego cudzysłów. Musisz też sformatować ciąg znaków z wynikiem, aby dodać do niego prefiks Current Score, jak pokazano na poniższym obrazie.
- W
string.xml
dodaj te ciągi tekstowe, których użyjesz do sformatowania widoków tekstuword
iscore
. Symbole%s
i%d
to symbole zastępcze dla bieżącego słowa i bieżącego wyniku.
<string name="quote_format">\"%s\"</string>
<string name="score_format">Current Score: %d</string>
- W pliku
game_fragment.xml
zaktualizuj atrybuttext
widoku tekstuword_text
, aby używać zasobu ciągu znakówquote_format
. Przekaż zagameViewModel.word
. Spowoduje to przekazanie bieżącego słowa jako argumentu do ciągu formatującego.
<TextView
android:id="@+id/word_text"
...
android:text="@{@string/quote_format(gameViewModel.word)}"
... />
- Sformatuj widok tekstu
score
podobnie jakword_text
. Wgame_fragment.xml
dodaj atrybuttext
do widoku tekstuscore_text
. Użyj zasobu ciągu znakówscore_format
, który przyjmuje jeden argument liczbowy reprezentowany przez obiekt zastępczy%d
. Przekaż obiektLiveData
,score
, jako argument do tego ciągu formatującego.
<TextView
android:id="@+id/score_text"
...
android:text="@{@string/score_format(gameViewModel.score)}"
... />
- W klasie
GameFragment
, w metodzieonCreateView()
, usuń kod obserwatorascore
.
Kod do usunięcia:
viewModel.score.observe(this, Observer { newScore ->
binding.scoreText.text = newScore.toString()
})
- Wyczyść, ponownie skompiluj i uruchom aplikację, a potem zagraj w grę. Zwróć uwagę, że bieżące słowo i wynik są sformatowane na ekranie gry.
Gratulacje! W aplikacji masz zintegrowane LiveData
i ViewModel
z powiązaniem danych. Dzięki temu widoki w układzie mogą bezpośrednio komunikować się z ViewModel
bez używania w fragmencie funkcji obsługi kliknięć. Używasz też obiektów LiveData
jako źródła powiązania danych, aby automatycznie powiadamiać interfejs o zmianach w danych bez użycia metod obserwatora LiveData
.
Projekt Android Studio: GuessTheWord
- Biblioteka powiązań danych bezproblemowo współpracuje z komponentami architektury Androida, takimi jak
ViewModel
iLiveData
. - Układy w aplikacji mogą być powiązane z danymi w komponentach architektury, które już pomagają zarządzać cyklem życia kontrolera interfejsu i powiadamiać o zmianach w danych.
Wiązanie danych ViewModel
- Możesz powiązać
ViewModel
z układem za pomocą powiązania danych. - Obiekty
ViewModel
zawierają dane interfejsu. Przekazując obiektyViewModel
do powiązania danych, możesz zautomatyzować część komunikacji między widokami a obiektamiViewModel
.
Aby powiązać ViewModel
z układem:
- W pliku układu dodaj zmienną wiązania danych typu
ViewModel
.
<data>
<variable
name="gameViewModel"
type="com.example.android.guesstheword.screens.game.GameViewModel" />
</data>
- W pliku
GameFragment
przekażGameViewModel
do powiązania danych.
binding.gameViewModel = viewModel
Powiązania odbiorników
- Powiązania detektorów to wyrażenia powiązań w układzie, które są uruchamiane, gdy wywoływane są zdarzenia kliknięcia, takie jak
onClick()
. - Powiązania odbiorników są zapisywane jako wyrażenia lambda.
- Za pomocą powiązań detektorów zastępujesz detektory kliknięć w kontrolerach interfejsu użytkownika powiązaniami detektorów w pliku układu.
- Powiązanie danych tworzy odbiornik i ustawia go w widoku.
android:onClick="@{() -> gameViewModel.onSkip()}"
Dodawanie LiveData do wiązania danych
- Obiekty
LiveData
mogą być używane jako źródło powiązania danych, aby automatycznie powiadamiać interfejs o zmianach w danych. - Widok możesz powiązać bezpośrednio z obiektem
LiveData
wViewModel
. Gdy wartośćLiveData
wViewModel
się zmieni, widoki w układzie mogą być automatycznie aktualizowane bez użycia metod obserwatora w kontrolerach interfejsu.
android:text="@{gameViewModel.word}"
- Aby powiązanie danych
LiveData
działało, ustaw bieżącą aktywność (kontroler interfejsu) jako właściciela cyklu życia zmiennejbinding
w kontrolerze interfejsu.
binding.lifecycleOwner = this
Formatowanie ciągów znaków za pomocą wiązania danych
- Za pomocą powiązania danych możesz formatować zasób ciągu znaków za pomocą symboli zastępczych, takich jak
%s
w przypadku ciągów znaków i%d
w przypadku liczb całkowitych. - Aby zaktualizować atrybut
text
widoku, przekaż obiektLiveData
jako argument do ciągu formatującego.
android:text="@{@string/quote_format(gameViewModel.word)}"
Kurs Udacity:
Dokumentacja dla deweloperów aplikacji na Androida:
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ń dotyczących powiązań odbiorców nie jest prawdziwe?
- Powiązania detektorów to wyrażenia powiązań, które są uruchamiane, gdy wystąpi zdarzenie.
- Powiązania odbiorników działają we wszystkich wersjach wtyczki Androida do obsługi Gradle.
- Powiązania odbiorników są zapisywane jako wyrażenia lambda.
- Powiązania odbiorników są podobne do odwołań do metod, ale umożliwiają uruchamianie dowolnych wyrażeń powiązania danych.
Pytanie 2
Załóżmy, że Twoja aplikacja zawiera ten zasób w postaci ciągu tekstowego:<string name="generic_name">Hello %s</string>
Która z poniższych opcji zawiera prawidłową składnię formatowania ciągu znaków przy użyciu wyrażenia wiązania danych?
android:text= "@{@string/generic_name(user.name)}"
android:text= "@{string/generic_name(user.name)}"
android:text= "@{@generic_name(user.name)}"
android:text= "@{@string/generic_name,user.name}"
Pytanie 3
Kiedy wyrażenie powiązane z odbiornikiem jest oceniane i uruchamiane?
- Gdy dane przechowywane przez
LiveData
ulegną zmianie - Gdy aktywność zostanie ponownie utworzona w wyniku zmiany konfiguracji
- Gdy wystąpi zdarzenie, np.
onClick()
- Gdy aktywność przechodzi w tło
Rozpocznij kolejną lekcję:
Linki do innych ćwiczeń z tego kursu znajdziesz na stronie docelowej ćwiczeń z podstaw języka Kotlin na Androidzie.