Android Kotlin Fundamentals 05.3: Data binding with ViewModel and LiveData

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ą LiveDataViewModel.
  • 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 obiektami ViewModel, aby widoki komunikowały się bezpośrednio z obiektami ViewModel.
  • Zmieniasz aplikację, aby używała LiveData jako źródła powiązań danych. Po tej zmianie obiekty LiveData powiadamiają interfejs użytkownika o zmianach w danych, a metody obserwatora LiveData 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ą.

  1. (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.
  2. Uruchom aplikację i zagraj w grę.
  3. 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ę.
  4. 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ładu game_fragment.xml.
  • Gdy użytkownik kliknie przycisk OK, odbiornik kliknięć we fragmencie GameFragment wywoła odpowiedni odbiornik kliknięć we fragmencie GameViewModel.
  • Wynik zostanie zaktualizowany w GameViewModel.

Widok ButtonGameViewModel 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 GameViewModelScoreViewModel 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.

  1. W pliku game_fragment.xml dodaj zmienną wiązania danych typu GameViewModel. 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...
  1. W pliku GameFragment przekaż GameViewModel do powiązania danych.

    Aby to zrobić, przypisz viewModel do zmiennej binding.gameViewModel zadeklarowanej w poprzednim kroku. Umieść ten kod w tagu onCreateView() po zainicjowaniu tagu viewModel. 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.

  1. W sekcji game_fragment.xml dodaj atrybut onClick do elementu skip_button. Zdefiniuj wyrażenie wiążące i wywołaj metodę onSkip() w GameViewModel. To wyrażenie wiążące jest nazywane wiązaniem odbiornika.
<Button
   android:id="@+id/skip_button"
   ...
   android:onClick="@{() -> gameViewModel.onSkip()}"
   ... />
  1. Podobnie powiąż zdarzenie kliknięcia elementu correct_button z metodą onCorrect() w elemencie GameViewModel.
<Button
   android:id="@+id/correct_button"
   ...
   android:onClick="@{() -> gameViewModel.onCorrect()}"
   ... />
  1. Powiąż zdarzenie kliknięcia elementu end_game_button z metodą onGameFinish() w GameViewModel.
<Button
   android:id="@+id/end_game_button"
   ...
   android:onClick="@{() -> gameViewModel.onGameFinish()}"
   ... />
  1. 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.

  1. W pliku score_fragment.xml dodaj zmienną wiązania typu ScoreViewModel. Ten krok jest podobny do tego, który wykonaliśmy w przypadku GameViewModel powyżej.
<layout ...>
   <data>
       <variable
           name="scoreViewModel"
           type="com.example.android.guesstheword.screens.score.ScoreViewModel" />
   </data>
   <androidx.constraintlayout.widget.ConstraintLayout
  1. W sekcji score_fragment.xml dodaj atrybut onClick do elementu play_again_button. Zdefiniuj powiązanie odbiornika i wywołaj metodę onPlayAgain()ScoreViewModel.
<Button
   android:id="@+id/play_again_button"
   ...
   android:onClick="@{() -> scoreViewModel.onPlayAgain()}"
   ... />
  1. ScoreFragmentonCreateView() zainicjuj viewModel. Następnie zainicjuj zmienną wiązania binding.scoreViewModel.
viewModel = ...
binding.scoreViewModel = viewModel
  1. ScoreFragment usuń kod, który ustawia odbiornik kliknięć dla elementu playAgainButton. Jeśli Android Studio wyświetli błąd, wyczyść i ponownie skompiluj projekt.

Kod do usunięcia:

binding.playAgainButton.setOnClickListener {  viewModel.onPlayAgain()  }
  1. 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 w ScoreFragment.

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:

  1. 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.
  2. 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.
  3. 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 LiveDataViewModel.

  1. game_fragment.xml dodaj atrybut android:text do widoku tekstu word_text.

Ustaw go na obiekt LiveData, wordGameViewModel, 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.

  1. GameFragmentonCreateView() po zainicjowaniu gameViewModel ustaw bieżącą aktywność jako właściciela cyklu życia zmiennej binding. Określa to zakres powyższego obiektu LiveData, co umożliwia mu automatyczne aktualizowanie widoków w układzie game_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
  1. W sekcji GameFragment usuń obserwatora z konta LiveData word.

Kod do usunięcia:

/** Setting up LiveData observation relationship **/
viewModel.word.observe(this, Observer { newWord ->
   binding.wordText.text = newWord
})
  1. 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.

  1. score_fragment.xml dodaj atrybut android:text do widoku tekstu wyniku. Przypisz scoreViewModel.score do atrybutu text. Ponieważ score jest liczbą całkowitą, przekształć ją w ciąg znaków za pomocą funkcji String.valueOf().
<TextView
   android:id="@+id/score_text"
   ...
   android:text="@{String.valueOf(scoreViewModel.score)}"
   ... />
  1. ScoreFragment po zainicjowaniu scoreViewModel ustaw bieżącą aktywność jako właściciela cyklu życia zmiennej binding.
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
  1. ScoreFragment usuń obserwatora obiektu score.

Kod do usunięcia:

// Add observer for score
viewModel.score.observe(this, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})
  1. 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.

  1. string.xml dodaj te ciągi tekstowe, których użyjesz do sformatowania widoków tekstu wordscore. Symbole %s%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>
  1. W pliku game_fragment.xml zaktualizuj atrybut text widoku tekstu word_text, aby używać zasobu ciągu znaków quote_format. Przekaż za gameViewModel.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)}"
   ... />
  1. Sformatuj widok tekstu score podobnie jak word_text. W game_fragment.xml dodaj atrybut text do widoku tekstu score_text. Użyj zasobu ciągu znaków score_format, który przyjmuje jeden argument liczbowy reprezentowany przez obiekt zastępczy %d. Przekaż obiekt LiveData, score, jako argument do tego ciągu formatującego.
<TextView
   android:id="@+id/score_text"
   ...
   android:text="@{@string/score_format(gameViewModel.score)}"
   ... />
  1. W klasie GameFragment, w metodzie onCreateView(), usuń kod obserwatora score.

Kod do usunięcia:

viewModel.score.observe(this, Observer { newScore ->
   binding.scoreText.text = newScore.toString()
})
  1. 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 LiveDataViewModel 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 ViewModelLiveData.
  • 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 obiekty ViewModel do powiązania danych, możesz zautomatyzować część komunikacji między widokami a obiektami ViewModel.

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 LiveDataViewModel. Gdy wartość LiveDataViewModel 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 zmiennej binding 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ż obiekt LiveData 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ę: 5.4. Przekształcenia LiveData

Linki do innych ćwiczeń z tego kursu znajdziesz na stronie docelowej ćwiczeń z podstaw języka Kotlin na Androidzie.