Wprowadzenie do Kotlina: programowanie na Androida dla ludzi

Opublikowany: 2022-03-11

W idealnym świecie Androida główny język Java jest naprawdę nowoczesny, przejrzysty i elegancki. Możesz pisać mniej, robiąc więcej, a za każdym razem, gdy pojawia się nowa funkcja, programiści mogą z niej korzystać, po prostu zwiększając wersję w Gradle. Następnie podczas tworzenia bardzo ładnej aplikacji wydaje się w pełni testowalna, rozszerzalna i możliwa do utrzymania. Nasze działania nie są zbyt duże i skomplikowane, możemy zmieniać źródła danych z bazy danych na sieć bez mnóstwa różnic i tak dalej. Brzmi świetnie, prawda? Niestety świat Androida nie jest aż tak idealny. Google wciąż dąży do perfekcji, ale wszyscy wiemy, że idealne światy nie istnieją. Dlatego musimy pomóc sobie w tej wspaniałej podróży po świecie Androida.

Co to jest Kotlin i dlaczego warto go używać?

A więc pierwszy język. Myślę, że Java nie jest mistrzem elegancji ani przejrzystości, nie jest ani nowoczesna, ani ekspresyjna (i chyba się zgadzasz). Wadą jest to, że poniżej Androida N nadal ograniczamy się do Javy 6 (w tym niektórych małych części Javy 7). Deweloperzy mogą również dołączyć RetroLambda, aby używać wyrażeń lambda w swoim kodzie, co jest bardzo przydatne podczas korzystania z RxJava. Powyżej Androida N możemy korzystać z niektórych nowych funkcji Javy 8, ale wciąż jest to stara, ciężka Jawa. Bardzo często słyszę, jak deweloperzy Androida mówią „Chciałbym, żeby Android obsługiwał ładniejszy język, tak jak iOS z Swiftem”. A gdybym ci powiedział, że możesz używać bardzo ładnego, prostego języka, z zerowym bezpieczeństwem, lambdami i wieloma innymi fajnymi nowymi funkcjami? Witamy w Kotlinie.

Co to jest Kotlin?

Kotlin to nowy język (czasami określany jako Swift dla Androida), opracowany przez zespół JetBrains i jest teraz w wersji 1.0.2. To, co czyni go przydatnym w programowaniu Androida, to fakt, że kompiluje się do kodu bajtowego JVM, a także może być skompilowany do JavaScript. Jest w pełni kompatybilny z Javą, a kod Kotlina można po prostu przekonwertować na kod Java i odwrotnie (jest wtyczka od JetBrains). Oznacza to, że Kotlin może korzystać z dowolnego frameworka, biblioteki itp. napisanych w Javie. Na Androidzie integruje się przez Gradle. Jeśli masz istniejącą aplikację na Androida i chcesz zaimplementować nową funkcję w Kotlinie bez przepisywania całej aplikacji, po prostu zacznij pisać w Kotlinie, a to zadziała.

Ale jakie są „świetne nowe funkcje”? Pozwolę sobie wymienić kilka:

Opcjonalne i nazwane parametry funkcji

 fun createDate(day: Int, month: Int, year: Int, hour: Int = 0, minute: Int = 0, second: Int = 0) { print("TEST", "$day-$month-$year $hour:$minute:$second") }

Możemy nazwać metodę createDate na różne sposoby

 createDate(1,2,2016) prints: '1-2-2016 0:0:0' createDate(1,2,2016, 12) prints: '1-2-2016 12:0:0' createDate(1,2,2016, minute = 30) prints: '1-2-2016 0:30:0'

Brak bezpieczeństwa

Jeśli zmienna może mieć wartość null, kod nie skompiluje się, chyba że zmusimy go do tego. Poniższy kod będzie zawierał błąd — nullableVar może mieć wartość null:

 var nullableVar: String? = “”; nullableVar.length;

Aby skompilować, musimy sprawdzić, czy nie jest null:

 if(nullableVar){ nullableVar.length }

Lub krócej:

 nullableVar?.length

W ten sposób, jeśli nullableVar ma wartość null, nic się nie dzieje. W przeciwnym razie możemy oznaczyć zmienną jako nie dopuszczającą wartości null, bez znaku zapytania po typie:

 var nonNullableVar: String = “”; nonNullableVar.length;

Ten kod się kompiluje i jeśli chcemy przypisać wartość null do nonNullableVar, kompilator pokaże błąd.

Jest też bardzo przydatny operator Elvisa:

 var stringLength = nullableVar?.length ?: 0

Następnie, gdy nullableVar ma wartość null (więc nullableVar?.length zwraca null), stringLength będzie miał wartość 0.

Zmienne i niezmienne

W powyższym przykładzie używam var podczas definiowania zmiennej. Jest to zmienne, możemy je zmienić, kiedy tylko chcemy. Jeśli chcemy, aby ta zmienna była niezmienna (w wielu przypadkach powinniśmy), używamy val (jako wartość, a nie zmienną):

 val immutable: Int = 1

Po tym kompilator nie pozwoli nam ponownie przypisać do immutable.

Lambdy

Wszyscy wiemy, czym jest lambda, więc tutaj pokażę tylko, jak możemy ją wykorzystać w Kotlinie:

 button.setOnClickListener({ view -> Log.d("Kotlin","Click")})

Lub jeśli funkcja jest jedynym lub ostatnim argumentem:

 button.setOnClickListener { Log.d("Kotlin","Click")}

Rozszerzenia

Rozszerzenia to bardzo pomocna funkcja językowa, dzięki której możemy „rozszerzać” istniejące klasy, nawet gdy są ostateczne lub nie mamy dostępu do ich kodu źródłowego.

Na przykład, aby uzyskać wartość ciągu z edit text, zamiast za każdym razem pisać editText.text.toString(), możemy napisać funkcję:

 fun EditText.textValue(): String{ return text.toString() }

Lub krócej:

 fun EditText.textValue() = text.toString()

A teraz z każdą instancją EditText:

 editText.textValue()

Lub możemy dodać właściwość zwracającą to samo:

 var EditText.textValue: String get() = text.toString() set(v) {setText(v)}

Przeciążenie operatora

Czasem przydatne, jeśli chcemy dodawać, mnożyć lub porównywać obiekty. Kotlin umożliwia przeciążanie operatorów binarnych (plus, minus, plusAssign, range itp.), operatorów tablicowych (get, set, get range, set range) oraz operacji równych i jednoargumentowych (+a, -a itp.)

Klasa danych

Ile wierszy kodu potrzebujesz, aby zaimplementować klasę User w Javie z trzema właściwościami: copy, equals, hashCode i toString? W Kaotlin potrzebujesz tylko jednej linii:

 data class User(val name: String, val surname: String, val age: Int)

Ta klasa danych udostępnia metody equals(), hashCode() i copy(), a także toString(), która wyświetla User jako:

 User(name=John, surname=Doe, age=23)

Klasy danych zapewniają również kilka innych przydatnych funkcji i właściwości, które można zobaczyć w dokumentacji Kotlina.

Rozszerzenia Anko

Używasz rozszerzenia Butterknife lub Androida, prawda? Co jeśli nie musisz nawet korzystać z tej biblioteki, a po zadeklarowaniu widoków w XML po prostu użyj jej z kodu po jego identyfikatorze (jak w XAML w C#):

 <Button android: android:layout_width="match_parent" android:layout_height="wrap_content" />
 loginBtn.setOnClickListener{}

Kotlin ma bardzo przydatne rozszerzenia Anko, a dzięki temu nie musisz mówić swojej aktywności, czym jest loginBtn, wie to po prostu „importując” xml:

 import kotlinx.android.synthetic.main.activity_main.*

W Anko jest wiele innych przydatnych rzeczy, w tym rozpoczynanie zajęć, pokazywanie toastów i tak dalej. Nie to jest głównym celem Anko - jest przeznaczone do łatwego tworzenia układów z kodu. Więc jeśli potrzebujesz stworzyć układ programowo, jest to najlepszy sposób.

To tylko krótkie spojrzenie na Kotlina. Polecam lekturę bloga Antonio Leivy i jego książki - Kotlin for Android Developers oraz oczywiście oficjalną stronę Kotlin.

Co to jest MVP i dlaczego?

Nie wystarczy ładny, mocny i jasny język. Bardzo łatwo jest pisać niechlujne aplikacje w każdym języku bez dobrej architektury. Deweloperzy Androida (głównie ci, którzy dopiero zaczynają, ale także bardziej zaawansowani) często dają Aktywność odpowiedzialność za wszystko wokół nich. Aktywność (lub fragment lub inny widok) pobiera dane, wysyła do zapisania, prezentuje je, reaguje na interakcje użytkownika, edytuje dane, zarządza wszystkimi widokami podrzędnymi . . . a często znacznie więcej. To za dużo dla tak niestabilnych obiektów, jak Aktywności czy Fragmenty (wystarczy obrócić ekran, a Aktywność powie 'Do widzenia...').

Bardzo dobrym pomysłem jest oddzielenie odpowiedzialności od poglądów i uczynienie ich tak głupimi, jak to tylko możliwe. Widoki (działania, fragmenty, widoki niestandardowe lub cokolwiek, co przedstawia dane na ekranie) powinny być odpowiedzialne wyłącznie za zarządzanie ich widokami podrzędnymi. Widoki powinni mieć prezenterzy, którzy będą komunikować się z modelem i mówić im, co powinni zrobić. Krótko mówiąc, jest to wzorzec Model-View-Presenter (dla mnie powinien mieć nazwę Model-Presenter-View, aby pokazać połączenia między warstwami).

MVC vs MVP

„Hej, znam coś takiego i nazywa się to MVC!” - nie myślałeś? Nie, MVP to nie to samo co MVC. We wzorcu MVC Twój widok może komunikować się z modelem. Korzystając z MVP, nie zezwalasz na żadną komunikację między tymi dwiema warstwami - jedyny sposób, w jaki View może komunikować się z modelem, to przez Presenter. Jedyną rzeczą, jaką View wie o modelu, może być struktura danych. View wie jak np. wyświetlić Użytkownika, ale nie wie kiedy. Oto prosty przykład:

View wie „Jestem Activity, mam dwa EditTexts i jeden Button. Kiedy ktoś kliknie przycisk, powinienem powiedzieć o tym mojemu prezenterowi i przekazać mu wartości EditTexts. I to wszystko, mogę spać, aż następne kliknięcie lub prezenter powie mi, co mam robić.”

Prezenter wie, że gdzieś jest widok i wie, jakie operacje może wykonać ten widok. Wie również, że gdy otrzyma dwa ciągi, powinien utworzyć użytkownika z tych dwóch ciągów i wysłać dane do modelu w celu zapisania, a jeśli zapisanie się powiedzie, powiedzieć widokowi „Pokaż informacje o sukcesie”.

Model po prostu wie, gdzie są dane, gdzie należy je zapisać i jakie operacje wykonać na danych.

Aplikacje napisane w MVP są łatwe do testowania, utrzymania i ponownego wykorzystania. Czysty prezenter nie powinien nic wiedzieć o platformie Android. Powinna to być czysta klasa Java (lub w naszym przypadku Kotlin). Dzięki temu możemy ponownie wykorzystać naszego prezentera w innych projektach. Możemy również łatwo pisać testy jednostkowe, testując osobno Model, Widok i Prezenter.

Wzorzec MVP prowadzi do lepszej, mniej złożonej bazy kodu, zachowując prawdziwie oddzielony interfejs użytkownika i logikę biznesową.

Mała dygresja: MVP powinno być częścią Czystej Architektury Wujka Boba, aby aplikacje były jeszcze bardziej elastyczne i ładnie zaprojektowane. Spróbuję o tym napisać następnym razem.

Przykładowa aplikacja z MVP i Kotlin

Tyle teorii, zobaczmy trochę kodu! Dobra, spróbujmy stworzyć prostą aplikację. Głównym celem tej aplikacji jest stworzenie użytkownika. Pierwszy ekran będzie miał dwa EditTexts (imię i nazwisko) oraz jeden przycisk (Save). Po wpisaniu imienia i nazwiska oraz kliknięciu „Zapisz”, aplikacja powinna wyświetlić komunikat „Użytkownik jest zapisany” i przejść do kolejnego ekranu, na którym prezentowane jest zapisane imię i nazwisko. Gdy imię lub nazwisko jest puste, aplikacja nie powinna zapisywać użytkownika i wyświetlać błąd wskazujący, co jest nie tak.

Pierwszą rzeczą po stworzeniu projektu Android Studio jest konfiguracja Kotlina. Powinieneś zainstalować wtyczkę Kotlin, a po restarcie w Narzędzia > Kotlin możesz kliknąć „Konfiguruj Kotlina w Projekcie”. IDE doda zależności Kotlina do Gradle. Jeśli masz jakiś istniejący kod, możesz go łatwo przekonwertować na Kotlina (Ctrl+Shift+Alt+K lub Kod> Konwertuj plik Java na Kotlin). Jeśli coś jest nie tak i projekt się nie kompiluje lub Gradle nie widzi Kotlina, możesz sprawdzić kod aplikacji dostępnej na GitHub.

Kotlin nie tylko dobrze współpracuje z frameworkami i bibliotekami Java, ale pozwala nadal korzystać z większości tych samych narzędzi, które już znasz.

Teraz, gdy mamy już projekt, zacznijmy od stworzenia naszego pierwszego widoku - CreateUserView. Widok ten powinien mieć wspomniane wcześniej funkcjonalności, więc możemy napisać do niego interfejs:

 interface CreateUserView : View { fun showEmptyNameError() /* show error when name is empty */ fun showEmptySurnameError() /* show error when surname is empty */ fun showUserSaved() /* show user saved info */ fun showUserDetails(user: User) /* show user details */ }

Jak widać, Kotlin jest podobny do Javy w deklarowaniu funkcji. Wszystkie są funkcjami, które nic nie zwracają, a te ostatnie mają jeden parametr. To jest różnica, typ parametru występuje po nazwie. Interfejs View nie pochodzi z Androida - to nasz prosty, pusty interfejs:

 interface View

Interfejs Basic Presenter powinien mieć właściwość typu View, a przynajmniej metodę (na przykład onDestroy), gdzie ta właściwość zostanie ustawiona na null:

 interface Presenter<T : View> { var view: T? fun onDestroy(){ view = null } }

Tutaj możesz zobaczyć kolejną funkcję Kotlina - możesz deklarować właściwości w interfejsach, a także implementować tam metody.

Nasz CreateUserView musi komunikować się z CreateUserPresenter. Jedyną dodatkową funkcją, której ten prezenter potrzebuje, jest saveUser z dwoma argumentami łańcuchowymi:

 interface CreateUserPresenter<T : View>: Presenter<T> { fun saveUser(name: String, surname: String) }

Potrzebujemy również definicji modelu - jest to wspomniana wcześniej klasa danych:

 data class User(val name: String, val surname: String)

Po zadeklarowaniu wszystkich interfejsów możemy przystąpić do implementacji.

CreateUserPresenter zostanie zaimplementowany w CreateUserPresenterImpl:

 class CreateUserPresenterImpl(override var view: CreateUserView?): CreateUserPresenter<CreateUserView> { override fun saveUser(name: String, surname: String) { } }

Pierwsza linia, z definicją klasy:

 CreateUserPresenterImpl(override var view: CreateUserView?)

Jest konstruktorem, używamy go do przypisania właściwości widoku, zdefiniowanej w interfejsie.

MainActivity, czyli nasza implementacja CreateUserView, wymaga odniesienia do CreateUserPresenter:

 class MainActivity : AppCompatActivity(), CreateUserView { private val presenter: CreateUserPresenter<CreateUserView> by lazy { CreateUserPresenterImpl(this) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) saveUserBtn.setOnClickListener{ presenter.saveUser(userName.textValue(), userSurname.textValue()) /*use of textValue() extension, mentioned earlier */ } } override fun showEmptyNameError() { userName.error = getString(R.string.name_empty_error) /* it's equal to userName.setError() - Kotlin allows us to use property */ } override fun showEmptySurnameError() { userSurname.error = getString(R.string.surname_empty_error) } override fun showUserSaved() { toast(R.string.user_saved) /* anko extension - equal to Toast.makeText(this, R.string.user_saved, Toast.LENGTH_LONG) */ } override fun showUserDetails(user: User) { } override fun onDestroy() { presenter.onDestroy() } }

Na początku zajęć określiliśmy naszego prezentera:

 private val presenter: CreateUserPresenter<CreateUserView> by lazy { CreateUserPresenterImpl(this) }

Jest zdefiniowany jako niezmienny (val) i jest tworzony przez leniwego delegata, który zostanie przydzielony, gdy będzie potrzebny po raz pierwszy. Co więcej, mamy pewność, że nie będzie null (brak znaku zapytania po definicji).

Gdy Użytkownik kliknie przycisk Zapisz, Widok wysyła informacje do Prezentera z wartościami EditTexts. Gdy tak się stanie, Użytkownik powinien zostać zapisany, więc musimy zaimplementować metodę saveUser w Presenterze (i niektórych funkcjach Modelu):

 override fun saveUser(name: String, surname: String) { val user = User(name, surname) when(UserValidator.validateUser(user)){ UserError.EMPTY_NAME -> view?.showEmptyNameError() UserError.EMPTY_SURNAME -> view?.showEmptySurnameError() UserError.NO_ERROR -> { UserStore.saveUser(user) view?.showUserSaved() view?.showUserDetails(user) } } }

Po utworzeniu Użytkownika jest on wysyłany do UserValidator w celu sprawdzenia poprawności. Następnie, zgodnie z wynikiem walidacji, wywoływana jest odpowiednia metoda. Konstrukcja when() {} jest taka sama jak switch/case w Javie. Ale jest potężniejszy - Kotlin pozwala na użycie nie tylko enum czy int w 'case', ale także zakresów, stringów czy typów obiektów. Musi zawierać wszystkie możliwości lub mieć wyrażenie else. Tutaj obejmuje wszystkie wartości UserError.

Używając view?.showEmptyNameError() (ze znakiem zapytania po wyświetleniu) jesteśmy chronieni przed NullPointer. Widok może zostać zerowany w metodzie onDestroy, a przy takiej konstrukcji nic się nie stanie.

Gdy model użytkownika nie zawiera błędów, informuje UserStore, aby go zapisał, a następnie instruuje View, aby pokazał sukces i szczegóły.

Jak wspomniano wcześniej, musimy zaimplementować kilka rzeczy modelowych:

 enum class UserError { EMPTY_NAME, EMPTY_SURNAME, NO_ERROR } object UserStore { fun saveUser(user: User){ //Save user somewhere: Database, SharedPreferences, send to web... } } object UserValidator { fun validateUser(user: User): UserError { with(user){ if(name.isNullOrEmpty()) return UserError.EMPTY_NAME if(surname.isNullOrEmpty()) return UserError.EMPTY_SURNAME } return UserError.NO_ERROR } }

Najciekawszą rzeczą jest tutaj UserValidator. Używając słowa obiektowego, możemy stworzyć klasę singletona, bez obaw o wątki, prywatne konstruktory i tak dalej.

Kolejna rzecz - w metodzie validateUser(user) występuje wyrażenie with(user) {}. Kod w ramach takiego bloku jest wykonywany w kontekście obiektu, przekazany wraz z imieniem i nazwiskiem są właściwościami Użytkownika.

Jest jeszcze jedna mała rzecz. Cały powyższy kod, od enum do UserValidator, definicja jest umieszczona w jednym pliku (definicja klasy User też jest tutaj). Kotlin nie zmusza cię do posiadania każdej klasy publicznej w pojedynczym pliku (lub nazywania klasy dokładnie tak samo jak plik). Tak więc, jeśli masz kilka krótkich fragmentów powiązanego kodu (klasy danych, rozszerzenia, funkcje, stałe - Kotlin nie wymaga klasy dla funkcji ani stałej), możesz umieścić go w jednym pliku zamiast rozprzestrzeniać się po wszystkich plikach w projekcie.

Po zapisaniu użytkownika nasza aplikacja powinna to wyświetlić. Potrzebujemy innego widoku - może to być dowolny widok Androida, widok niestandardowy, fragment lub aktywność. Wybrałem Aktywność.

Zdefiniujmy więc interfejs UserDetailsView. Może pokazywać użytkownika, ale powinien również pokazywać błąd, gdy użytkownik nie jest obecny:

 interface UserDetailsView { fun showUserDetails(user: User) fun showNoUserError() }

Następnie UserDetailsPresenter. Powinien mieć właściwość użytkownika:

 interface UserDetailsPresenter<T: View>: Presenter<T> { var user: User? }

Ten interfejs zostanie zaimplementowany w UserDetailsPresenterImpl. Musi zastąpić właściwość użytkownika. Za każdym razem, gdy ta właściwość jest przypisywana, użytkownik powinien zostać odświeżony w widoku. W tym celu możemy użyć narzędzia do ustawiania właściwości:

 class UserDetailsPresenterImpl(override var view: UserDetailsView?): UserDetailsPresenter<UserDetailsView> { override var user: User? = null set(value) { field = value if(field != null){ view?.showUserDetails(field!!) } else { view?.showNoUserError() } } }

Implementacja UserDetailsView, UserDetailsActivity, jest bardzo prosta. Tak jak poprzednio, mamy obiekt prezentera stworzony przez lazy loading. Użytkownik do wyświetlenia należy przekazać intencjonalnie. Na razie jest z tym jeden mały problem i za chwilę go rozwiążemy. Gdy mamy użytkownika z intencji, View musi przypisać go do swojego prezentera. Następnie użytkownik zostanie odświeżony na ekranie lub, jeśli jest pusty, pojawi się błąd (i aktywność zakończy się - ale prezenter o tym nie wie):

 class UserDetailsActivity: AppCompatActivity(), UserDetailsView { private val presenter: UserDetailsPresenter<UserDetailsView> by lazy { UserDetailsPresenterImpl(this) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_user_details) val user = intent.getParcelableExtra<User>(USER_KEY) presenter.user = user } override fun showUserDetails(user: User) { userFullName.text = "${user.name} ${user.surname}" } override fun showNoUserError() { toast(R.string.no_user_error) finish() } override fun onDestroy() { presenter.onDestroy() } }

Przekazywanie obiektów przez intencje wymaga, aby ten obiekt implementował interfejs Parcelable. To bardzo „brudna” praca. Osobiście nienawidzę tego robić z powodu wszystkich TWÓRCÓW, właściwości, zapisywania, przywracania i tak dalej. Na szczęście istnieje odpowiednia wtyczka Parcelable dla Kotlina. Po zainstalowaniu możemy jednym kliknięciem wygenerować Paczkę.

Ostatnią rzeczą do zrobienia jest zaimplementowanie showUserDetails(user: User) w naszym MainActivity:

 override fun showUserDetails(user: User) { startActivity<UserDetailsActivity>(USER_KEY to user) /* anko extension - starts UserDetailsActivity and pass user as USER_KEY in intent */ }

I to wszystko.

Demo aplikacji na Androida w Kotlin

Mamy prostą aplikację, która zapisuje Użytkownika (właściwie nie jest on zapisany, ale możemy dodać tę funkcjonalność bez dotykania prezentera lub podglądu) i prezentuje go na ekranie. W przyszłości, jeśli będziemy chcieli zmienić sposób prezentacji użytkownika na ekranie, np. z dwóch aktywności na dwa fragmenty w jednej aktywności lub dwa niestandardowe widoki, zmiany będą dotyczyły tylko klas View. Oczywiście, jeśli nie zmienimy funkcjonalności lub struktury modelu. Prezenter, który nie wie, czym dokładnie jest Widok, nie będzie potrzebował żadnych zmian.

Masz problemy z wydajnością w swoich aplikacjach na Androida? Zapoznaj się z tymi wskazówkami i technikami optymalizacji.

Co dalej?

W naszej aplikacji Prezenter jest tworzony za każdym razem, gdy tworzona jest aktywność. To podejście lub jego przeciwieństwo, jeśli prezenter powinien być kontynuowany w różnych instancjach aktywności, jest przedmiotem wielu dyskusji w Internecie. Dla mnie zależy to od aplikacji, jej potrzeb i dewelopera. Czasem lepiej zniszczyć prezentera, czasem nie. Jeśli zdecydujesz się go utrzymać, bardzo interesującą techniką jest użycie do tego LoaderManagera.

Jak wspomniano wcześniej, MVP powinien być częścią architektury Uncle Bob's Clean. Co więcej, dobrzy programiści powinni używać Daggera do wstrzykiwania zależności prezenterów do działań. Pomaga również w utrzymaniu, testowaniu i ponownym użyciu kodu w przyszłości. Obecnie Kotlin bardzo dobrze współpracuje z Daggerem (przed oficjalną premierą nie było to takie proste), a także z innymi pomocnymi bibliotekami Androida.

Zakończyć

Dla mnie Kotlin to świetny język. Jest nowoczesny, przejrzysty i wyrazisty, a jednocześnie jest rozwijany przez wspaniałych ludzi. I możemy używać każdej nowej wersji na dowolnym urządzeniu i wersji Androida. Cokolwiek mnie denerwuje na Javę, Kotlin się poprawia.

Oczywiście, jak powiedziałem, nic nie jest idealne. Kotlin ma też pewne wady. Najnowsze wersje wtyczek Gradle (głównie z wersji alfa lub beta) nie działają dobrze z tym językiem. Wiele osób narzeka, że ​​czas budowania jest nieco dłuższy niż w czystej Javie, a apki mają dodatkowe MB. Ale Android Studio i Gradle wciąż się poprawiają, a telefony mają coraz więcej miejsca na aplikacje. Dlatego uważam, że Kotlin może być bardzo przyjemnym językiem dla każdego programisty Androida. Po prostu spróbuj i podziel się w sekcji komentarzy poniżej tym, co myślisz.

Kod źródłowy przykładowej aplikacji jest dostępny na Github: github.com/tomaszczura/AndroidMVPKotlin