Android Kotlin Fundamentals 07.5: nagłówki w RecyclerView

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

Z tego ćwiczenia dowiesz się, jak dodać nagłówek obejmujący szerokość listy wyświetlanej w narzędziu RecyclerView. Korzystasz z aplikacji do monitorowania snu z poprzednich ćwiczeń.

Co musisz wiedzieć

  • Jak utworzyć podstawowy interfejs, używając aktywności, fragmentów i widoków danych.
  • Jak poruszać się między fragmentami i jak używać safeArgs do przekazywania danych między fragmentami.
  • Wyświetlanie modeli, wyświetlanie fabryk modeli, przekształceń oraz LiveData i ich obserwatorów.
  • Jak utworzyć bazę danych Room, utworzyć DAO i określić encje.
  • Sposób używania schematów na potrzeby interakcji z bazą danych lub innych długotrwałych zadań.
  • Jak wdrożyć podstawowy element RecyclerView z elementem Adapter, ViewHolder i układem elementu.
  • Jak wdrożyć wiązanie danych dla RecyclerView.
  • Tworzenie i używanie adapterów wiązań do przekształcania danych.
  • Jak korzystać z GridLayoutManager.
  • Jak przechwytywać kliknięcia produktów i zarządzać nimi w: RecyclerView.

Czego się nauczysz

  • Używanie kilku elementów ViewHolder z elementem RecyclerView w celu dodania elementów o innym układzie. W szczególności używaj drugiego atrybutu ViewHolder, aby dodawać nagłówek nad elementami wyświetlanymi w elemencie RecyclerView.

Jakie zadania wykonasz:

  • Skorzystaj z aplikacji TrackMySleepQuality z poprzedniego ćwiczenia z tej serii.
  • Dodaj nagłówek, który rozciąga się na szerokość ekranu nad nocami wyświetlanymi w narzędziu RecyclerView.

Aplikacja do monitorowania snu ma początkowo 3 ekrany reprezentowane fragmentami, jak widać na rysunku poniżej.

Na pierwszym ekranie po lewej stronie znajdują się przyciski rozpoczynające i zatrzymujące śledzenie. Na ekranie pojawią się niektóre 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 (środkowym) wybierasz ocenę jakości snu. Trzeci ekran to widok szczegółowy otwierany, gdy użytkownik kliknie element na siatce.

Ta aplikacja ma uproszczoną architekturę z kontrolerem interfejsu, modelem wyświetlania i LiveData, a także bazę danych Room do przechowywania danych o śnie.

W ramach tego ćwiczenia dodajesz nagłówek do siatki wyświetlanych elementów. Końcowy ekran główny będzie wyglądał tak:

Z tego modułu ćwiczeń dowiesz się, jak dodawać elementy o różnych układach w RecyclerView. Przykładem może być nagłówek na liście lub na siatce. Lista może mieć jeden nagłówek opisujący zawartość elementu. Lista może też zawierać wiele nagłówków, które pozwalają grupować i rozdzielać elementy w ramach jednej listy.

RecyclerView nie ma żadnych informacji o Twoich danych ani o układzie każdego elementu. LayoutManager umieszcza elementy na ekranie, ale adapter dostosowuje wyświetlane dane i przekazuje je do RecyclerView. Aby więc utworzyć nagłówki w adapterze, musisz dodać kod.

Dwa sposoby dodawania nagłówków

W RecyclerView każdy element na liście odpowiada numerowi indeksującemu od 0. Przykład:

[Rzeczywiste dane] -> [Widoki adaptera]

[0: Sen snu] -> [0: nocna]

[1: Sennoc] -> [1: Sennoc]

[2: Sennoc] -> [2: Sennoc]

Dobrym sposobem na dodanie nagłówków do listy jest zmodyfikowanie adaptera tak, by używał innego atrybutu ViewHolder, sprawdzając indeksy w miejscu, w którym ma być wyświetlany nagłówek. Adapter jest odpowiedzialny za śledzenie nagłówka. Aby na przykład wyświetlać nagłówek u góry tabeli, musisz zwrócić inny nagłówek ViewHolder podczas układania elementu z indeksem zero. Następnie pozostałe elementy zostaną zmapowane za pomocą przesunięcia nagłówka, jak pokazano poniżej.

[Rzeczywiste dane] -> [Widoki adaptera]

[0: Nagłówek]

[0: Sen.noc] -> [1: Sen.noc]

[1: Sennoc] -> [2: Sennoc]

[2: Sen snu] -> [3: Sen.

Innym sposobem dodawania nagłówków jest zmodyfikowanie zbioru danych backendu dla siatki danych. Wszystkie dane, które mają być wyświetlane, są przechowywane na liście, dlatego możesz ją zmodyfikować, aby zawierała elementy reprezentujące nagłówek. Jest to nieco łatwiejsze do zrozumienia, ale wymaga przemyślenia sposobu projektowania obiektów, dzięki czemu możesz połączyć różne typy elementów w jedną listę. W ten sposób adapter wyświetli elementy, które zostaną do niego przesłane. Element na pozycji 0 jest nagłówkiem, a element na pozycji 1 to SleepNight, który mapuje bezpośrednio na to, co widać na ekranie.

[Rzeczywiste dane] -> [Widoki adaptera]

[0: Nagłówek] -> [0: Nagłówek]

[1: Sennoc] -> [1: Sennoc]

[2: Sennoc] -> [2: Sennoc]

[3: Sennoc] -> [3: Sennoc]

Każda metoda ma zalety i wady. Zmiana zbioru danych nie spowoduje wprowadzenia znaczących zmian w pozostałej części kodu adaptera. Możesz dodać logikę nagłówka, manipulując listą danych. Z drugiej strony użycie różnych nagłówków ViewHolder przez sprawdzenie indeksów daje więcej swobody w określaniu układu nagłówków. Umożliwia też adapterowi dostosowywanie danych w widoku danych bez konieczności modyfikowania tych danych.

W ćwiczeniach z programowania zaktualizujesz RecyclerView, aby wyświetlał nagłówek na początku listy. W tym przypadku aplikacja będzie używać innego nagłówka ViewHolder niż elementu danych. Aplikacja sprawdzi indeks listy, aby określić, którego ViewHolder ma używać.

Krok 1. Utwórz klasę DataItem

Aby abstraktować typ elementu, a adapter po prostu radzić sobie z &;quot;items", możesz utworzyć klasę właściciela danych, która reprezentuje SleepNight lub Header. Zbiór danych będzie wówczas zawierał listę elementów należących do właściciela danych.

Możesz pobrać aplikację startową z GitHuba lub dalej używać aplikacji SleepTracker utworzonej w ramach ćwiczeń z programowania.

  1. Pobierz kod RecyclerViewHeaders-Starter z GitHuba. Katalog RecyclerViewHeaders-Starter zawiera początkową wersję aplikacji SleepTracker niezbędną do udziału w tym ćwiczeniu. Jeśli wolisz, możesz też ukończyć gotową aplikację z poprzedniego ćwiczenia z programowania.
  2. Otwórz SleepNightAdapter.kt.
  3. Pod klasą SleepNightListener na najwyższym poziomie zdefiniuj klasę sealed o nazwie DataItem, która reprezentuje element danych.

    Klasa sealed definiuje typ zamknięty, co oznacza, że w tym pliku muszą być zdefiniowane wszystkie podkategorie DataItem. W rezultacie liczba podklas jest znana kompilatorowi. W innej części kodu nie można określić nowego typu atrybutu DataItem, który może spowodować uszkodzenie adaptera.
sealed class DataItem {

 }
  1. W treści klasy DataItem określ dwie klasy, które reprezentują różne typy elementów danych. Pierwsze to SleepNightItem, czyli kod, który łączy ok. SleepNight i pobiera pojedynczą wartość o nazwie sleepNight. Aby stać się częścią przedmiotu klasy szczelności, przedłuż go: DataItem.
data class SleepNightItem(val sleepNight: SleepNight): DataItem()
  1. Druga klasa to Header, która reprezentuje nagłówek. Nagłówek nie zawiera danych, więc możesz go zadeklarować jako object. Oznacza to, że element Header nigdy nie wystąpi. Przedłuż go o DataItem.
object Header: DataItem()
  1. W klasie DataItem określ na poziomie klasy właściwość abstract Long o nazwie id. Gdy adapter używa atrybutu DiffUtil do określenia, czy i w jaki sposób nastąpiła zmiana, DiffItemCallback musi znać identyfikator każdego elementu. Zobaczysz błąd, ponieważ SleepNightItem i Header muszą zastąpić abstrakcyjną właściwość id.
abstract val id: Long
  1. W SleepNightItem zastąp id, aby zwrócić nightId.
override val id = sleepNight.nightId
  1. W Header zastąp id, aby zwrócić Long.MIN_VALUE, co jest bardzo małą (czyli -2 do potęgi 63). Oznacza to, że nigdy nie będą sprzeczne z istniejącym elementem nightId.
override val id = Long.MIN_VALUE
  1. Gotowy kod powinien wyglądać podobnie do tego, a aplikacja powinna kompilować się bez błędów.
sealed class DataItem {
    abstract val id: Long
    data class SleepNightItem(val sleepNight: SleepNight): DataItem()      {
        override val id = sleepNight.nightId
    }

    object Header: DataItem() {
        override val id = Long.MIN_VALUE
    }
}

Krok 2. Utwórz nagłówek ViewHolder dla nagłówka

  1. Utwórz układ nagłówka w nowym pliku zasobów o nazwie header.xml, który wyświetla TextView. Nie ma tu nic ekscytującego, więc oto kod.
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textAppearance="?android:attr/textAppearanceLarge"
    android:text="Sleep Results"
    android:padding="8dp" />
  1. Wyodrębnij "Sleep Results" do zasobu ciągu znaków i nazwij go header_text.
<string name="header_text">Sleep Results</string>
  1. W pliku SleepNightAdapter.kt w obrębie SleepNightAdapter, nad klasą ViewHolder, utwórz nową klasę TextViewHolder. Ta klasa zwiększa układ textview.xml i zwraca instancję TextViewHolder. Ponieważ masz to już za sobą, oto kod, który musisz zaimportować: View i R:
    class TextViewHolder(view: View): RecyclerView.ViewHolder(view) {
        companion object {
            fun from(parent: ViewGroup): TextViewHolder {
                val layoutInflater = LayoutInflater.from(parent.context)
                val view = layoutInflater.inflate(R.layout.header, parent, false)
                return TextViewHolder(view)
            }
        }
    }

Krok 3. Zaktualizuj SleepNightAdapter

Następnie musisz zaktualizować deklarację SleepNightAdapter. Zamiast obsługiwać tylko jeden typ ViewHolder, musi on mieć możliwość użycia dowolnego typu właściciela widoku danych.

Zdefiniuj typy elementów

  1. W SleepNightAdapter.kt na najwyższym poziomie, poniżej instrukcji import i powyżej SleepNightAdapter, zdefiniuj dwie stałe dla widoków danych.

    Aby elementy zamówienia mogły prawidłowo przypisywać do nich udział w wyświetleniach, obiekt RecyclerView musi rozróżniać każdy typ widoku danych.
    private val ITEM_VIEW_TYPE_HEADER = 0
    private val ITEM_VIEW_TYPE_ITEM = 1
  1. W sekcji SleepNightAdapter utwórz funkcję zastępowania getItemViewType() w celu zwracania stałego nagłówka lub stałej pozycji w zależności od typu bieżącego elementu.
override fun getItemViewType(position: Int): Int {
        return when (getItem(position)) {
            is DataItem.Header -> ITEM_VIEW_TYPE_HEADER
            is DataItem.SleepNightItem -> ITEM_VIEW_TYPE_ITEM
        }
    }

Zaktualizuj definicję SleepNightAdapter

  1. W definicji SleepNightAdapter zaktualizuj pierwszy argument ListAdapter z SleepNight na DataItem.
  2. W definicji SleepNightAdapter zmień drugi argument ogólny dla ListAdapter z SleepNightAdapter.ViewHolder na RecyclerView.ViewHolder. Podczas dokonywania niezbędnych aktualizacji zobaczysz błędy, a nagłówek klasy powinien wyglądać jak poniżej.
class SleepNightAdapter(val clickListener: SleepNightListener):
       ListAdapter<DataItem, RecyclerView.ViewHolder>(SleepNightDiffCallback()) {

Aktualizacja onCreateViewHolder()

  1. Zmień podpis onCreateViewHolder(), by zwracał RecyclerView.ViewHolder.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder
  1. Rozwiń implementację metody onCreateViewHolder(), by przetestować w przypadku każdego typu elementu identyfikator użytkownika odpowiedni do wyświetlania. Zaktualizowana metoda powinna wyglądać jak poniżej.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            ITEM_VIEW_TYPE_HEADER -> TextViewHolder.from(parent)
            ITEM_VIEW_TYPE_ITEM -> ViewHolder.from(parent)
            else -> throw ClassCastException("Unknown viewType ${viewType}")
        }
    }

Aktualizacja onBindViewHolder()

  1. Zmień typ parametru onBindViewHolder() z ViewHolder na RecyclerView.ViewHolder.
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int)
  1. Dodaj warunek, by przypisywać dane do użytkownika tylko wtedy, gdy ma on typ ViewHolder.
        when (holder) {
            is ViewHolder -> {...}
  1. Prześlij typ obiektu zwracany przez getItem() na DataItem.SleepNightItem. Twoja gotowa funkcja onBindViewHolder() powinna wyglądać tak.
  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is ViewHolder -> {
                val nightItem = getItem(position) as DataItem.SleepNightItem
                holder.bind(nightItem.sleepNight, clickListener)
            }
        }
    }

Aktualizacja wywołań zwrotnych diffUtil

  1. Zmień metody w SleepNightDiffCallback, aby używać nowych zajęć DataItem, a nie SleepNight. Pomiń ostrzeżenie o linczach, jak widać w poniższym kodzie.
class SleepNightDiffCallback : DiffUtil.ItemCallback<DataItem>() {
    override fun areItemsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
        return oldItem.id == newItem.id
    }
    @SuppressLint("DiffUtilEquals")
    override fun areContentsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
        return oldItem == newItem
    }
}

Dodawanie i przesyłanie nagłówka

  1. W obrębie funkcji SleepNightAdapter, poniżej onCreateViewHolder(), zdefiniuj funkcję addHeaderAndSubmitList() w podany niżej sposób. Ta funkcja pobiera listę SleepNight. Zamiast przesyłać tekst submitList() podany przez ListAdapter, do przesłania listy możesz użyć tej funkcji, by dodać nagłówek, a następnie przesłać listę.
fun addHeaderAndSubmitList(list: List<SleepNight>?) {}
  1. Jeśli na liście addHeaderAndSubmitList() podana jest wartość null, zwróć tylko nagłówek, a w przeciwnym razie dołącz nagłówek do nagłówka listy i prześlij ją.
val items = when (list) {
                null -> listOf(DataItem.Header)
                else -> listOf(DataItem.Header) + list.map { DataItem.SleepNightItem(it) }
            }
submitList(items)
  1. Otwórz plik SleepTrackerFragment.kt i zmień wywołanie na submitList() do addHeaderAndSubmitList().
  1. Uruchom aplikację i sprawdź, jak nagłówek wyświetla się jako pierwszy element na liście.

Musisz rozwiązać dwie problemy dotyczące tej aplikacji. Jedna jest widoczna, a druga nie.

  • Nagłówek wyświetla się w lewym górnym rogu i jest trudny do odróżnienia.
  • W przypadku krótkiej listy z 1 nagłówkiem nie ma to znaczenia, ale w wątku interfejsu nie należy wykonywać operacji na liście w języku addHeaderAndSubmitList(). Wyobraź sobie listę z setkami elementów, wieloma nagłówkami i logiką, by zdecydować, gdzie wstawić elementy. To zadanie należy do współprogramu.

Aby używać współprogramów, zmień addHeaderAndSubmitList():

  1. Na najwyższym poziomie klasy SleepNightAdapter określ właściwość CoroutineScope za pomocą właściwości Dispatchers.Default.
private val adapterScope = CoroutineScope(Dispatchers.Default)
  1. W addHeaderAndSubmitList() uruchom grupę w adapterScope, aby manipulować listą. Następnie przełącz się na kontekst Dispatchers.Main, aby przesłać listę, jak pokazano w poniższym kodzie.
 fun addHeaderAndSubmitList(list: List<SleepNight>?) {
        adapterScope.launch {
            val items = when (list) {
                null -> listOf(DataItem.Header)
                else -> listOf(DataItem.Header) + list.map { DataItem.SleepNightItem(it) }
            }
            withContext(Dispatchers.Main) {
                submitList(items)
            }
        }
    }
  1. Kod powinien być tworzony i uruchomiony, więc nie zobaczysz żadnych różnic.

Obecnie nagłówek ma taką samą szerokość jak inne elementy siatki, zajmując jedną rozpiętość w poziomie i w pionie. Cała siatka mieści się w trzech elementach o szerokości po jednej szerokości, więc nagłówek powinien mieć trzy rozpiętości w poziomie.

Aby poprawić szerokość nagłówka, musisz określić w polu GridLayoutManager zakres danych we wszystkich kolumnach. Aby to zrobić, skonfiguruj SpanSizeLookup na urządzeniu GridLayoutManager. To jest obiekt konfiguracji, którego GridLayoutManager używa do określenia liczby rozpiętości dla każdego elementu na liście.

  1. Otwórz plik SleepTrackerFragment.kt.
  2. Znajdź kod, w którym zdefiniujesz kod manager, który znajduje się pod koniec tego adresu: onCreateView().
val manager = GridLayoutManager(activity, 3)
  1. Poniżej manager określ manager.spanSizeLookup tak, jak to pokazano. Musisz zrobić object, bo setSpanSizeLookup nie bierze lambdy. Aby utworzyć object w Kotlinie, wpisz object : classname, w tym przypadku GridLayoutManager.SpanSizeLookup.
manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
}
  1. Możesz wywołać błąd kompilatora wywołujący konstruktor. Jeśli tak, otwórz menu intencji, używając Option+Enter (Mac) lub Alt+Enter (Windows) i zastosuj wywołanie konstruktora.
  1. Następnie na stronie object pojawi się komunikat o konieczności zastąpienia metod. Umieść kursor na ikonie object, naciśnij Option+Enter (Mac) lub Alt+Enter (Windows), aby otworzyć menu intencji, a następnie zastąp metodę getSpanSize().
  1. W treści getSpanSize() zwracaj odpowiedni rozmiar rozpiętości dla każdej pozycji. Pozycja 0 ma rozpiętość 3, a drugie – rozpiętość 1. Gotowy kod powinien wyglądać tak jak poniżej:
    manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
            override fun getSpanSize(position: Int) =  when (position) {
                0 -> 3
                else -> 1
            }
        }
  1. Aby ulepszyć wygląd nagłówka, otwórz plik header.xml i dodaj ten kod do pliku układu header.xml.
android:textColor="@color/white_text_color"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:background="@color/colorAccent"
  1. Uruchom aplikację. Powinno wyglądać na zrzucie ekranu poniżej.

Gratulacje! To już koniec.

Projekt Android Studio: RecyclerViewHeaders

  • Nagłówek to zwykle element, który rozciąga się i znajduje się na liście lub pełni funkcję tytułu lub separatora. Lista może mieć jeden nagłówek opisujący treść elementu lub wiele nagłówków do grupowania elementów i oddzielania się od siebie.
  • RecyclerView może wykorzystywać wiele elementów widoku danych, by uwzględnić heterogeniczny zbiór elementów, np. nagłówki i elementy listy.
  • Jednym ze sposobów dodania nagłówków jest zmodyfikowanie adaptera tak, by używał innego atrybutu ViewHolder, sprawdzając indeksy w miejscu, w którym ma być wyświetlany nagłówek. Adapter odpowiada za śledzenie nagłówka.
  • Innym sposobem dodawania nagłówków jest zmodyfikowanie backendu zbioru danych (listy) w siatce danych, co zostało zrobione w tym ćwiczeniu z programowania.

Oto główne etapy dodawania nagłówka:

  • Streszczenia danych na liście: utwórz DataItem, który może zawierać nagłówek lub dane.
  • Utwórz ładowarkę z układem nagłówka w adapterze.
  • Zaktualizuj adapter i metody, aby używać dowolnego typu znaczników RecyclerView.ViewHolder.
  • W onCreateViewHolder() zwracaj prawidłowy typ właściciela danych dla elementu danych.
  • Aby pracować z klasą DataItem, zaktualizuj SleepNightDiffCallback.
  • Utwórz funkcję addHeaderAndSubmitList(), która za pomocą algorytmów dodaje nagłówek do zbioru danych i wywołuje metodę submitList().
  • Zaimplementuj GridLayoutManager.SpanSizeLookup(), aby szeroki nagłówek miał tylko trzy pasy.

Kurs Udacity:

Dokumentacja dla programistów Androida:

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 tych stwierdzeń na temat miejsca ViewHolder jest prawdziwe?

▢ Adapter może używać nagłówków klasy ViewHolder do przechowywania nagłówków i różnych typów danych.

▢ Możesz mieć tylko jeden widok danych dla danych i jeden nagłówek dla nagłówka.

RecyclerView obsługuje wiele typów nagłówków, ale dane muszą być jednolite.

▢ Dodając nagłówek, wybierasz podkategorię RecyclerView, aby wstawić go w odpowiednim miejscu.

Pytanie 2

Kiedy należy używać współprac z RecyclerView? Zaznacz wszystkie stwierdzenia, które są prawdziwe.

▢ Nigdy. Element RecyclerView jest elementem interfejsu i nie powinien używać strategii.

▢ Używaj algorytmów do wykonywania długotrwałych zadań, które mogą spowolnić interfejs użytkownika.

▢ Manipulowanie listami może zająć dużo czasu, więc zawsze należy je wykonywać przy użyciu algorytmów.

▢ Używaj reguł z funkcjami zawieszania, by uniknąć zablokowania głównego wątku.

Pytanie 3

Które z tych czynności NIE musisz wykonywać, gdy używasz więcej niż jednej usługi ViewHolder?

ViewHolder: dołącz wiele plików układu, by w razie potrzeby zwiększyć rozmiar pliku.

onCreateViewHolder() – zwracaj właściwy typ właściciela elementu danych.

▢ W onBindViewHolder() wiązaj dane tylko wtedy, gdy są one właściwym typem widoku danych w przypadku tego elementu.

▢ Ogólnie podpis klasy adaptera, aby zaakceptować dowolny RecyclerView.ViewHolder.

Rozpocznij następną lekcję: 8.1 Pobieranie danych z internetu

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