Tworzenie aplikacji na Androida w języku Kotlin

O Kotlinie

Idąc za definicją z Wikipedii, Kotlin to statycznie typowany język programowania działający na maszynie wirtualnej Javy (JVM) i zaprojektowany z myślą o pełnej interoperacyjności z językiem Java. Dla nas oznacza to przede wszystkim tyle, że możemy użyć Kotlina wszędzie tam, gdzie dotychczas króluje Java, a to otwiera całkiem dużo możliwości, wliczając tworzenie aplikacji na system Android.
Kotlin jest stosunkowo nowym i ciągle intensywnie rozwijanym językiem – ukazał się w 2011, ale wersja 1.0 jest dostępna dopiero od lutego 2016. Za stworzenie i rozwój Kotlina odpowiada firma JetBrains – twórcy m.in IntellIJ IDE – środowiska, na którym oparte jest Android Studio.

Czy potrzebujemy nowych języków programowania?

Java jest językiem już z ponad 20-letnią historią. Była tworzona z myślą o wyeliminowaniu wad języka C++, jednak z czasem okazało się, że i w tym przypadku nie ustrzeżono się wielu niedoskonałości. Część z nich eliminowano w kolejnych wersjach, jednak nadal w wielu aspektach jest to język odbiegający od języków nowoczesnych. Aby połączyć korzyści płynące z korzystania z nowszych języków programowania i zalet korzystania z JVM, powstają implementacje istniejących języków na JVM, a także zupełnie nowe, takie jak Kotlin.

Podstawy Kotlina

Jak zostało wspomniane wcześniej, Kotlin opiera się na języku Java. W szybszym zrozumieniu jego składni może pomóc także znajomość języków takich jak Scala lub Swift. W celu zaznajomienia się z podstawami przeanalizujmy poniższą funkcję:

fun sum(a: Int, b: Int): Int {
  return a + b
}

Jak możemy się domyślić, funkcja ta oblicza sumę dwóch liczb typu Int i zwraca wynik tego samego typu. Warto zwrócić na odwrotną (w stosunku do Javy) definicję zmiennych – najpierw nazwa, a później typ. Brak średnika na końcu linii nie jest błędem – ich stosowanie w Kotlinie jest opcjonalne.
Kolejną ważną koncepcją jest sposób definiowania zmiennych lokalnych. Spójrzmy na przykład:

val a: Int = 1
val b = 2
var c: Int = 2
c *= 2

Definicję rozpoczynamy od jednego z słów kluczowych: val lub var. Pierwsze z nich jest odpowiednikiem znanego z Javy modyfikatora final i poprzedzać będzie zmienną, której wartości nie możemy zmienić. Aby zdefiniować klasyczną zmienną korzystamy z słówka var. Kotlin zapewnia również zaawansowaną inferencje typów, dzięki której nie musimy podawać typu zmiennej w miejscu, gdzie kompilator jest w stanie ten typ odgadnąć – przykładem jest definicja zmiennej b, która automatycznie będzie miała typ Int.

Te podstawowe koncepcje powinny umożliwić zrozumienie przykładów znajdujących się w dalszej części artykułu. Czytelników zainteresowanych głębszym poznaniem składni Kotlina zachęcam do odwiedzenia oficjalnego przewodnika znajdującego się pod adresem https://kotlinlang.org/docs/reference/basic-syntax.html. W dalszej części artykułu znajdą się nieco bardziej zaawansowane konstrukcje, jednak będą one wyjaśnione za każdym razem, gdy będą istotnie odbiegać od tych znanych z Javy.

Dlaczego warto pisać w Kotlinie – przykłady

Poniższy rozdział składać się będzie ze zbioru przykładów, mających na celu zaprezentowanie zalet i możliwości związanych z tworzeniem aplikacji w Kotlinie – zarówno w jego ogólnym zastosowaniu, jak i przy tworzeniu aplikacji na Androida.

1. Bezpieczny kod

Każdy programista Javy prędzej czy później spotka się z wyjątkiem NullPointerException. Czasem wynika on z prostego błędu i jest również prosty do naprawienia, jednak w bardziej złożonych aplikacjach może on wystąpić w najmniej oczekiwanym czasie i miejscu, powodując utratę czasu na debugowanie. Twórcy Kotlina postanowili temu zaradzić i domyślnie wszystkie typy nie mogą przyjmować wartości null (z języka angielskiego są to typy non-nullable). Dzięki temu problemy z wartościami null mogą zostać wykryte już na etapie kompilacji lub przynajmniej w miejscu przypisania nieprawidłowej wartości do zmiennej. Jeśli jednak chcemy mieć możliwość przypisania wartości null do zmiennej, musimy dodać do jej typu znak zapytania. Przykładowo typ całkowitoliczbowy dopuszczający wartości null to Int?. W przypadku korzystania z takich typów, Kotlin daje nam możliwości pisania czytelnego i bezpiecznego kodu. Przeanalizujmy przykład:

var s : String?
s = null
println(s?.length)
val len = s?.length ?: -1
println(len)

którego uruchomienie da następujący wynik:

null
-1

s jest zmienną typu String?, co oznacza, że może przyjmować wartość null (jest to odpowiednik typu String z Javy). Operator ?. pozwala na bezpieczne odwołanie do metod danego obiektu. Gdy będzie on miał wartość null, całe wywołanie również zwróci wartość null zamiast rzucać wyjątek. Drugim zaprezentowanym operatorem jest ?: , którego użycie zastępuje często występujące instrukcje if (s == null) {...} else {...}.

Kolejną kwestią zwiększającą zarówno bezpieczeństwo, jak i czytelność kodu jest automatyczne rzutowanie typów. Każdy z nas pewnie nieraz spotkał się z tego typu kodem w Javie:

if (obj instanceof Activity) {
    Activity activity = (Activity) obj;
    activity.finish();
}

Analogiczny kod w Kotlinie możemy napisać znacznie krócej:

if (obj is Activity) {
    obj.finish()
}

2. Operacje na kolekcjach

Ósma wersja Javy wprowadziła do tego języka wyrażenia Lambda oraz API Strumieni (ang. Streams API). Kotlin zapewnia obsługę analogicznych mechanizmów w jeszcze szerszym zakresie, m.in. poprzez obsługę funkcji wyższego rzędu. To jednak dość zaawansowane zagadnienie, tymczasem na potrzeby zaznajomienia się z podstawami Kotlina wystarczy nam wiedzieć, że praca na kolekcjach będzie co najmniej równie wygodna, jak w Javie 8:

val names = listOf("test", "ddddddddd", "abc", "ab")
names
    .filter { it -> it.length < 5 }
    .sortedBy { it }
    .map { it.toUpperCase() }
    .forEach { print(it) }

Krótkie wyjaśnienie dla osób nieznających API strumieni i wyrażeń Lambda: w powyższym przykładzie names jest dowolną kolekcją zawierającą elementy typu String. Istnieje szereg funkcji pozwalających wykonać określone działanie na całej kolekcji, np. przefiltrowanie jej, posortowanie lub mapowanie każdego elementu na inny wg podanej funkcji.

Możesz przetestować działanie powyższego kodu na stronie try.kotlinlang.org

3. Funkcje rozszerzeń

Jako programiści często stajemy przed koniecznością rozszerzenia funkcjonalności istniejących klas – w Javie taką możliwość daje nam dziedziczenie lub stosowanie wzorców projektowych, takich jak Dekorator, ale nie w każdym przypadku te mechanizmy dobrze się sprawdzą. Kotlin pozwala na tworzenie tzw. funkcji rozszerzeń, które zachowują się tak jakby należały do klasy którą rozszerzają – niezależnie od tego czy jest to klasa z naszego projektu czy też zupełnie inna. W ten sposób możemy dodać interesujące nas funkcje np. do klas String czy Date, zamiast tworzyć znane z Javy klasy typu Utils:

fun String.removeWhitespaces(): String {
    return this.replace(" ", "").replace("\t","")
}

println("dowolny string".removeWhitespaces())

fun Date.isWeekend() = day == 6 || day == 7

println(if(Date().isWeekend()) "Trwa weekend!" else "Niestety, trzeba poczekac na weekend")

Powyższy przykład zawiera dwie funkcje rozszerzeń oraz przykłady ich użycia. Warto zwrócić uwagę na skrócony zapis drugiej funkcji – możemy go zastosować, gdy ciało funkcji zawiera się w jednej linijce. Ostatnia linijka wykorzystująca funkcję isWeekend() również zawiera dwie nowe cechy Kotlina – zrezygnowano ze znanego operatora new, a także wprowadzono możliwość korzystania z instrukcji warunkowych if w sposób podobny do znanego z Javy operatora ?:. Innymi słowy, instrukcje if mogą nie tylko sterować przepływem programu, ale także zwracać wartość.

Jeszcze jednym elementem który może być niejasny w powyższym kodzie jest wyrażenie day == 6 || day == 7. Czym jest zmienna day? Otóż Kotlin obsługuje właściwości (ang. properties), znane m.in. z języka C#. W związku z tym odpowiednik tego kodu w Javie mógłby wyglądać następująco: getDay() == 6 || getDay() == 7. Podobnie moglibyśmy użyć właściwości zamiast jawnie wywoływać setter, np. Javowy kod setTime(3) możemy w Kotlinie zastąpić wyrażeniem time = 3, które zostanie zamienione na wywołanie odpowiedniego settera.

Jeśli zainteresowały Cię powyższe przykłady, zachęcam do odwiedzenia strony http://try.kotlinlang.org/. Możesz wkleić cały kod z ostatniego przykładu do funkcji main(), dodać na początku pliku niezbędny import import java.util.Date i przetestować jego działanie (tak – w Kotlinie można również definiować funkcje we wnętrzu innych funkcji). Dla leniwych – gotowy plik znajduje się pod tym adresem. Zachęcam do eksperymentowania!

4. Kotlin Android Extensions

Wsparcie Kotlina dla tworzenia aplikacji na Androida wykracza poza samą zmianę języka programowania – tworzy się również tzw. rozszerzenia, które mają na celu ułatwić pracę programistom. Najważniejszym z nich jest zmiana sposobu, w jaki obsługuje się widoki (Views). Korzystanie z metody findViewById() potrafi przysporzyć wielu kłopotów i błędów, ale przede wszystkim generuje nadmiarowy kod. Istnieją biblioteki rozwiązujące ten problem (jedna z nich została opisana w artykule o usuwaniu boilerplate), natomiast Kotlin sprawia, że korzystanie z widoków jest jeszcze prostsze. Załóżmy, że mamy w projekcie zdefiniowany układ następujący układ activity_main:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content"
              xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical">
    <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="Text"
            android:id="@+id/textView"/>
    <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="First Button"
            android:id="@+id/firstButton"/>
    <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Second Button"
            android:id="@+id/secondButton"/>

</LinearLayout>

Który w formie graficznej wygląda następująco:
16-06-23_23-30-22

Załóżmy, że chcemy osiągnąć następujące działanie aplikacji: kliknięcie pierwszego przycisku spowoduje wyświetlenie komunikatu typu Toast, a kliknięcie drugiego podmieni tekst w polu typu TextView. Możemy to uzyskać przez następującą implementację klasy MainActivity:

package example.com.myapplication

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
//importujemy wszystkie widoki z layoutu R.layout.activity_main
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // mamy dostep do widoków znajdujących sie w ukladzie activity_main
        firstButton.setOnClickListener { firstAction() }
        secondButton.setOnClickListener { secondAction() }
    }

    fun firstAction() = Toast.makeText(this, "First Button clicked", Toast.LENGTH_LONG).show()

    fun secondAction() {
        textView.text = "Second Button clicked"
    }
}

W powyższym kodzie możemy zauważyć kilka usprawnień w stosunku do analogicznego kodu który moglibyśmy napisać w Javie. Najważniejszą z nich jest fakt, że nie widzimy nigdzie deklaracji zmiennych firstButton, secondButton oraz textView. Ich widoczność zapewnia odpowiedni import – import kotlinx.android.synthetic.main.activity_main.*. Jest to specjalna konstrukcja tworząca tzw. rozszerzone właściwości (ang. extension property). Mechanizm ten pozwala zaimportować wszystkie (lub wybrane) widoki znajdujące się w układzie danej aktywności. Po zastosowaniu takiego importu, widoki te będą dostępne w całej klasie. Z punktu widzenia programisty, efekt jest taki, jak gdyby dla tych widoków stworzyć zmienne odpowiednich typów, o nazwach takich samych jak ich identyfikatory w układzie aktywności, a następnie zainicjalizować je wywołaniami metody findViewById(). Dzięki zastosowaniu tego rozszerzenia, pozbywamy się zbędnego kodu, a także ułatwiamy sobie jego refaktoryzację: po zmianie nazwy danego widoku, Android Studio podmieni również nazwy odpowiadających im pól w kodzie źródłowym. Więcej na temat tego rozszerzenia można poczytać na stronie Kotlina.

Jak zacząć pracę z Kotlinem w Android Studio

Niewątpliwym plusem używania Kotlina do tworzenia aplikacji na Androida jest wsparcie tego języka przez środowiska programistyczne IntellIJ IDEA oraz Android Studio. Łatwe jest zarówno tworzenie nowych projektów, jak i migracja istniejących. Co więcej, w związku z pełną interoperacyjnością Kotlina i Javy nie musimy przepisywać całego projektu – możemy z powodzeniem zacząć od pojedynczych klas.

Poniższe kroki przedstawiają sposób, w jaki można zacząć pracę z Kotlinem w Android Studio:

  1. Projekt: Stwórz nowy projekt lub otwórz jeden z projektów, w którym chcesz dodać wsparcie dla Kotlina. Ważne jest aby projekt miał co najmniej jedną aktywność. Jeśli tworzysz nowy projekt, możesz skorzystać z opcji File -> New Project, a następnie klikać Next i ostatecznie Finish, pozostawiając wszystkie wartości domyślne. Otwórz aktywność którą chcesz skonwertować do języka Kotlin. W przypadku domyślnego projektu będzie to MainActivity.
    projekt
  2. Instalacja Kotlin Plugin: możesz pominąć ten punkt, jeśli posiadasz Android Studio 2.0 lub nowsze.
    Zainstaluj Kotlin Plugin. W tym celu otwórz File -> Settings -> Plugins. Wybierz Install JetBrains plugin…, wyszukaj plugin o nazwie Kotlin i zainstaluj go.
    kotlin_plugin
  3. Konwersja kodu do Kotlina: Mając otwartą odpowiednią aktywność, kliknij dwukrotnie Shift i wyszukaj opcję Convert Java File to Kotlin File. Możesz znaleźć ją też w menu Code.
    konwersja_kotlin
  4. Konfiguracja Projektu: Nad kodem źródłowym aktywności powinna pojawić się informacja o nieskonfigurowanym projekcie Kotlina. Wybierz Configure. Możesz także manualnie wywołać tę akcje poprzez dwukrotne wciśnięcie Shift i wyszukanie opcji Configure Kotlin in Projekt
    kotlin_konfiguracja
    Otrzymasz informację o skonfigurowaniu Kotlina i zmodyfikowaniu odpowiednich plików build.gradle:
    kotlin_konfiguracja2
  5. Synchronizacja projektu: Otwórz plik build.gradle dla modułu Twojej aplikacji. Zaobserwuj zmiany wprowadzone przez konfigurację Kotlina. Zgodnie z propozycją wybierz opcję synchronizacji projektu.
    kotlin_synchronizacja
    Poczekaj, aż Android Studio ściągnie wszystkie potrzebne pliki, takie jak np. kompilator Kotlina.
  6. Gotowe! Możesz teraz uruchomić aplikację i dodawać kolejne pliki w języku Kotlin.

O interoperacyjności

Na wstępie wspomniałem, że Kotlin z założenia jest w pełni kompatybilny z Javą. Możemy tę kwestię rozpatrywać na dwóch płaszczyznach:

1. Kompatybilność na poziomie kodu źródłowego

Wiemy już, że w kodzie źródłowym napisanym w Kotlinie możemy odwoływać się do klas Javy. Przykładem jest kod prezentujący funkcje rozszerzeń, w którym korzystaliśmy z klasy java.util.Date. W ten sam sposób możemy korzystać z dowolnych innych bibliotek dla języka Java, jak również z własnych klas.
Nieco bardziej złożona może być interoperacyjność w drugą stronę, czyli korzystanie z klas Kotlina w klasach napisanych w Javie. Kotlin obsługuje konstrukcje niewystępujące w Javie, czego przykładem mogą być wspomniane funkcje rozszerzeń. Jeśli chcemy odwoływać się do takich funkcji w kodzie Javy, musimy zastosować odpowiednie adnotacje. W tym przypadku będzie to adnotacja @JvmName, dzięki której dana funkcja będzie widoczna jako metoda statyczna klasy o podanej nazwie. Oczywiście to nie jedyna różnica, więcej o interoperacyjności Kotlina i Javy można przeczytać w dokumentacji.

2. Kompatybilność z maszyną wirtualną Javy (JVM)

W jaki sposób kod napisany w Kotlinie może być stosowany wszędzie tam, gdzie Java? Sprawa jest bardzo prosta – kompilator Kotlina generuje na wyjściu kod bajtowy Javy. W związku z tym, skompilowany kod Kotlina może być używany wszędzie gdziekolwiek wspierana jest Java w wersji co najmniej 6, czyli np. w systemie Android.

Podsumowanie

Kotlin jest stosunkowo nowym językiem, ale szybko rozwijającym się i zyskującym nowych sympatyków. Według statystyk z serwisu GitHub, w maju 2016 było już ponad 1000 aktywnych repozytoriów z kodem w tym języku. To czyni Kotlina jednym z 5 najpopularniejszych języków działających na JVM, a pamiętać musimy, że od wydania pierwszej stabilnej wersji minęło zaledwie kilka miesięcy. Dodajmy do tego fakt, iż JetBrains jest znaczącą firmą, która wydała już wiele innowacyjnych produktów. Zmieniły one sposób, w jaki pracują programiści, zatem możemy domyślać się, że również Kotlin wiele wniesie do świata Javy i Androida.

Jedna uwaga do wpisu “Tworzenie aplikacji na Androida w języku Kotlin

Dodaj komentarz