Używanie popularnych wzorców Kotlin w Androidzie

Ta część koncentruje się na najbardziej przydatnych aspektach języka Kotlin. przy tworzeniu aplikacji na Androida.

Praca z fragmentami

W poniższych sekcjach wykorzystano Fragment przykładów do wyróżnienia niektórych z języków Kotlin z najlepszymi funkcjami.

Dziedziczenie

klasę w Kotlin można zadeklarować za pomocą słowa kluczowego class. W następujących na przykład LoginFragment jest podklasą klasy Fragment. Możesz wskazać, dziedziczenie, używając operatora : między podklasą a jej klasą nadrzędną:

class LoginFragment : Fragment()

W tej deklaracji klasy LoginFragment odpowiada za wywoływanie funkcji konstruktora swojej klasy nadrzędnej Fragment.

W LoginFragment możesz zastąpić liczbę wywołań zwrotnych cyklu życia, aby reagować na zmiany stanu w elemencie Fragment. Aby zastąpić funkcję, użyj funkcji override słowo kluczowe, tak jak w tym przykładzie:

override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
): View? {
    return inflater.inflate(R.layout.login_fragment, container, false)
}

Aby odwołać się do funkcji w klasie nadrzędnej, użyj słowa kluczowego super, jak pokazano na ilustracji w tym przykładzie:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
}

Wartość null i inicjowanie

W poprzednich przykładach niektóre z parametrów zastąpionych metod miały typy z sufiksem znaku zapytania ?. Wskazuje to, że argumenty przekazywane dla tych parametrów mogą mieć wartość null. Koniecznie bezpiecznie obsługiwać wartości null.

W Kotlin podczas deklarowania obiektu musisz zainicjować jego właściwości. Oznacza to, że po uzyskaniu instancji klasy można natychmiast odwołują się do dowolnej z dostępnych właściwości. Obiekty View w argumencie Fragment, nie są jednak gotowe do powiększenia przed wywołaniem funkcji Fragment#onCreateView, więc potrzebujesz sposobu na odroczenie inicjalizacji właściwości View.

Pole lateinit umożliwia opóźnienie zainicjowania usługi. Jeśli używasz lateinit, zainicjuj swoją usługę jak najszybciej.

Ten przykład pokazuje, jak za pomocą lateinit przypisać obiekty View w onViewCreated:

class LoginFragment : Fragment() {

    private lateinit var usernameEditText: EditText
    private lateinit var passwordEditText: EditText
    private lateinit var loginButton: Button
    private lateinit var statusTextView: TextView

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        usernameEditText = view.findViewById(R.id.username_edit_text)
        passwordEditText = view.findViewById(R.id.password_edit_text)
        loginButton = view.findViewById(R.id.login_button)
        statusTextView = view.findViewById(R.id.status_text_view)
    }

    ...
}

Konwersja SAM

Możesz rejestrować zdarzenia kliknięcia na Androidzie przez zaimplementowanie tagu Interfejs OnClickListener. Button obiektów zawiera setOnClickListener() funkcja, która przyjmuje implementację OnClickListener.

OnClickListener ma jedną metodę abstrakcyjną, onClick(), którą musisz wykonać. do wdrożenia. Ponieważ setOnClickListener() zawsze pobiera OnClickListener jako argument, a ponieważ OnClickListener zawsze ma ten sam pojedynczy argument tę implementację można przedstawić za pomocą funkcji anonimowej w argumencie Kotlin. Ten proces określa się jako Konwersja pojedynczej metody abstrakcyjnej, lub konwersji SAM.

Konwersja SAM może znacznie zwiększyć czytelność kodu. Przykład poniżej pokazuje, jak za pomocą konwersji SAM wdrożyć OnClickListener w przypadku Button:

loginButton.setOnClickListener {
    val authSuccessful: Boolean = viewModel.authenticate(
            usernameEditText.text.toString(),
            passwordEditText.text.toString()
    )
    if (authSuccessful) {
        // Navigate to next screen
    } else {
        statusTextView.text = requireContext().getString(R.string.auth_failed)
    }
}

Kod w funkcji anonimowej przekazany do funkcji setOnClickListener() uruchamia się, gdy użytkownik kliknie loginButton.

Obiekty towarzyszące

Obiekty towarzyszące umożliwia definiowanie zmiennych lub połączonych funkcji. koncepcyjnie związane z typem, ale nie są z nim powiązane. Towarzysz są podobne do użycia słowa kluczowego static w Javie na potrzeby zmiennych i metod.

W tym przykładzie TAG to stała String. Nie potrzebujesz unikalnego identyfikatora wystąpienia String dla każdego wystąpienia LoginFragment, więc zdefiniuj go w obiekcie towarzyszącym:

class LoginFragment : Fragment() {

    ...

    companion object {
        private const val TAG = "LoginFragment"
    }
}

Możesz zdefiniować TAG na najwyższym poziomie pliku, ale może też zawierać dużą liczbę zmiennych, funkcji i klas które są również zdefiniowane na najwyższym poziomie. Obiekty towarzyszące pomagają nawiązać połączenie zmiennych, funkcji i definicji klasy bez odnoszenia się do żadnych danej klasy.

Przekazywanie dostępu do usługi

Inicjując właściwości, możesz powtórzyć niektóre z typowych funkcji takich jak dostęp do ViewModel w Fragment. Aby uniknąć nadmiaru duplikatu kodu, możesz użyć składni przekazywania właściwości w Kotlin.

private val viewModel: LoginViewModel by viewModels()

Przekazywanie usług to typowa implementacja, której możesz używać ponownie w całej aplikacji. Android KTX udostępnia Ci dostęp do wybranych usług. viewModels na przykład pobiera zasób ViewModel o zakresie ograniczonym do zakresu obecna wartość Fragment.

Przekazywanie usług korzysta z refleksji, co zwiększa wydajność. Rozwiązaniem jest zwięzła składnia, która pozwala zaoszczędzić czas przy tworzeniu aplikacji.

Dopuszczalność wartości null

Kotlin zapewnia rygorystyczne reguły dopuszczania wartości null, które zapewniają bezpieczeństwo typu przez cały okres do aplikacji. W Kotlin odwołania do obiektów nie mogą zawierać wartości null przez wartość domyślną. Aby przypisać do zmiennej wartość null, musisz zadeklarować wartość nullable typu zmiennej, dodając ? na końcu typu podstawowego.

Na przykład poniższe wyrażenie jest niedozwolone w Kotlin. name jest typu String i nie ma wartości null:

val name: String = null

Aby zezwolić na wartość null, musisz użyć typu String dopuszczającego wartości null (String?) jako w tym przykładzie:

val name: String? = null

Interoperacyjność

Ścisłe reguły Kotlin sprawiają, że kod jest bezpieczniejszy i bardziej zwięzły. Te reguły niższe ryzyko wystąpienia zdarzenia NullPointerException, które spowodowałoby . Zmniejszają one też liczbę weryfikacji zerowych, które trzeba przeprowadzić w w kodzie.

Często przy pisaniu aplikacji na Androida konieczne jest również wywołanie kodu innego niż Kotlin, większość interfejsów API Androida jest napisanych w języku programowania Java.

Zgodność z wartościami null to kluczowy obszar, w którym Java i Kotlin różnią się działaniem. Javy jest mniej rygorystycznie ze składnią dopuszczania wartości null.

Na przykład klasa Account ma kilka właściwości, np. String usłudze o nazwie name. W Javie nie ma reguł Kotlina dotyczących dopuszczalności wartości null, zamiast polegać na opcjonalnych adnotacjach dopuszczających wartości null, które umożliwiają czy możesz przypisać wartość null.

Platforma Androida jest napisana głównie w języku Java, więc znalezienie w tym scenariuszu przy wywoływaniu interfejsów API bez adnotacji o dopuszczaniu wartości null.

Typy platform

Jeśli używasz Kotlina, aby odwołać się do elementu name bez adnotacji, który jest zdefiniowany w klasy Java Account, kompilator nie wie, czy String jest mapowany na String lub String? w Kotlin. Ta niejasność jest reprezentowana przez typ platformy, String!.

Funkcja String! nie ma specjalnego znaczenia dla kompilatora Kotlin. String! może reprezentować to String lub String?, a kompilator umożliwia przypisanie wartości obu typów. Pamiętaj, że możesz rzucić NullPointerException, jeśli reprezentują typ jako String i przypisują wartość null.

Aby rozwiązać ten problem, używaj adnotacji dopuszczających wartości null podczas pisania w Javie. Te adnotacje są pomocne zarówno dla programistów Java, jak i Kotlin.

Na przykład oto klasa Account zdefiniowana w Javie:

public class Account implements Parcelable {
    public final String name;
    public final String type;
    private final @Nullable String accessId;

    ...
}

Jedna ze zmiennych składowych (accessId) jest oznaczona adnotacją @Nullable, co wskazuje, że może zawierać wartość null. Kotlin leczyłby accessId jako String?.

Aby wskazać, że zmienna nie może mieć wartości null, użyj adnotacji @NonNull:

public class Account implements Parcelable {
    public final @NonNull String name;
    ...
}

W tym scenariuszu wartość name jest uważana w Kotlin za wartość String niedopuszczającą wartości nu.

Adnotacje dotyczące wartości null są uwzględniane we wszystkich nowych interfejsach API Androida, a wiele istniejących Interfejsy API dla Androida. Wiele bibliotek Java dodało adnotacje o dopuszczaniu wartości null, aby ulepszyć obsługuje zarówno programistę Kotlin, jak i język Java.

Obsługa dopuszczalności wartości null

Jeśli nie masz pewności co do typu Javy, zastanów się, czy ma on wartość null. Na przykład element name klasy Account nie jest opatrzony adnotacjami, więc powinien zakładać, że String to dopuszczalna wartość null.

Jeśli chcesz przyciąć name tak, by jego wartość nie zawierała początku ani końca na końcu odstępu, można użyć funkcji trim systemu Kotlin. Możesz bezpiecznie przyciąć String? na kilka różnych sposobów. Jednym z tych sposobów jest użycie parametru not-null operatora asercji, !! zgodnie z tym przykładem:

val account = Account("name", "type")
val accountName = account.name!!.trim()

Operator !! traktuje wszystko po lewej stronie jako niepuste, więc w W tym przypadku traktujesz name jako niepustą wartość String. Jeśli w wyniku działania funkcji wyrażenie po lewej stronie ma wartość null, aplikacja zgłasza NullPointerException. To proste i szybkie rozwiązanie, ale należy z niego korzystać oszczędnie, ponownie wprowadź wystąpienia NullPointerException do swojego kodu.

Bezpieczniej jest użyć operatora bezpiecznego połączenia (?.), jak pokazano w następujący przykład:

val account = Account("name", "type")
val accountName = account.name?.trim()

Jeśli przy użyciu operatora bezpiecznego połączenia name ma wartość różną od null, wynik funkcji name?.trim() to wartość nazwy bez spacji na początku i na końcu. Jeśli name ma wartość null, a wynik funkcji name?.trim() to null. Oznacza to, że aplikacja nigdy nie może zgłosić NullPointerException podczas wykonywania tej instrukcji.

Operator bezpiecznego połączenia eliminuje potencjalne NullPointerException, przekazuje ona wartość zerową do następnej instrukcji. Zamiast tego możesz obsługiwać wartość null natychmiast, używając operatora Elvisa (?:), jak pokazano w tym przykładzie: przykład:

val account = Account("name", "type")
val accountName = account.name?.trim() ?: "Default name"

Jeśli wynikiem wyrażenia po lewej stronie operatora Elvisa jest null, to wartość po prawej stronie jest przypisana do accountName. Ten jest przydatna do podania wartości domyślnej, która w innym przypadku miałaby wartość null.

Możesz też użyć operatora Elvisa, aby wcześniej wrócić z funkcji, jak pokazano na ilustracji. w tym przykładzie:

fun validateAccount(account: Account?) {
    val accountName = account?.name?.trim() ?: "Default name"

    // account cannot be null beyond this point
    account ?: return

    ...
}

Zmiany w interfejsie Android API

Interfejsy API Androida są coraz bardziej przyjazne Kotlin. Wiele funkcji najpopularniejsze interfejsy API, w tym AppCompatActivity i Fragment, zawierają adnotacje dotyczące wartości null, a niektóre wywołania, np. Fragment#getContext, mają więcej alternatyw, które są przyjazne dla Kotlin.

Na przykład dostęp do pola Context elementu Fragment niemal zawsze ma wartość inną niż null, bo większość połączeń w Fragment ma miejsce, gdy Fragment jest dołączony do Activity (podklasy Context). Mając to na uwadze, Fragment#getContext nie zawsze zwraca wartość niepustą, ponieważ w sytuacjach, gdy Fragment nie jest połączony z tabelą Activity. Dlatego też zwrot Typ Fragment#getContext dopuszcza wartość null.

Ponieważ funkcja Context zwrócona z metody Fragment#getContext ma wartość null (i jest oznaczone jako @Nullable), musisz traktować je jako Context? w kodzie Kotlin. Oznacza to zastosowanie jednego z wymienionych wcześniej operatorów w celu do wartości null przed uzyskaniem dostępu do jej właściwości i funkcji. W przypadku niektórych z tych w przypadku różnych scenariuszy Android zawiera alternatywne interfejsy API, które zapewniają tę wygodę. Fragment#requireContext na przykład zwraca niepustą wartość Context i zwraca IllegalStateException, jeśli funkcja Context ma wartość null, zostanie wywołana. W ten sposób możesz traktować wynikowy Context jako niepusty bez konieczności stosowania operatory zabezpieczeń telefonii komórkowej i sposoby obejścia tego problemu.

Inicjowanie usługi

Właściwości w Kotlin nie są domyślnie inicjowane. Muszą zostać zainicjowane gdy inicjowana jest klasa zamykająca.

Właściwości można inicjować na kilka różnych sposobów. Przykład poniżej pokazuje, jak zainicjować zmienną index przez przypisanie do niej wartości w parametrze deklaracja klasy:

class LoginFragment : Fragment() {
    val index: Int = 12
}

To inicjowanie można również zdefiniować w bloku inicjującym:

class LoginFragment : Fragment() {
    val index: Int

    init {
        index = 12
    }
}

W powyższych przykładach zdarzenie index jest inicjowane, gdy LoginFragment jest i całego świata.

Możesz jednak mieć kilka właściwości, których nie można zainicjować podczas obiektu budowy. Możesz na przykład odwołać się do View z poziomu Fragment, co oznacza, że najpierw należy powiększyć szablon. Inflacja nie występują, gdy jest utworzony Fragment. Podczas połączenia zostanie ona zawyżona. Fragment#onCreateView

Jednym ze sposobów na rozwiązanie tego problemu jest zadeklarowanie widoku jako możliwego do null i zainicjuj go jak najszybciej, jak w poniższym przykładzie:

class LoginFragment : Fragment() {
    private var statusTextView: TextView? = null

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)

            statusTextView = view.findViewById(R.id.status_text_view)
            statusTextView?.setText(R.string.auth_failed)
    }
}

Mimo że to działa zgodnie z oczekiwaniami, musisz teraz zarządzać dopuszczaniem wartości null pola View za każdym razem, gdy się do niego odwołasz. Lepszym rozwiązaniem jest użycie parametru lateinit dla atrybutu View zgodnie z poniższym przykładem:

class LoginFragment : Fragment() {
    private lateinit var statusTextView: TextView

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)

            statusTextView = view.findViewById(R.id.status_text_view)
            statusTextView.setText(R.string.auth_failed)
    }
}

Słowo kluczowe lateinit pozwala uniknąć inicjowania właściwości, gdy gdy powstaje obiekt. Jeśli do Twojej usługi odwołują się odwołania przed jej zainicjowaniem, Kotlin rzuca UninitializedPropertyAccessException. zainicjuj swoją usługę tak szybko, jak to możliwe.