We use cookies on this page. View our Privacy Policy for more information.

Sbohem Javo, vítej Kotline aneb co máme v SYNETECHu rádi na Kotlinu

V tomto článku bych se rád podělil o naše více než roční zkušenosti s Kotlinem na Androidu.

Sbohem Javo, vítej Kotline aneb co máme v SYNETECHu rádi na Kotlinu

Úvod 👋

V tomto článku bych se rád podělil o naše více než roční zkušenosti s Kotlinem na Androidu. V SYNETECHu vyvíjíme všechny Android projekty právě v Kotlinu a jelikož naše společnost tvoří mobilní aplikace pro klienty, máme zodpovědnost je dodat v nejlepší možné kvalitě, stabilní a zároveň v co nejkratším čase.

Pokud se chcete dozvědět více o tom, proč a jak používat Kotlin pro tvorbu mobilních aplikací, čtěte dál.

Co máme rádi na Kotlinu? ❤️

Jelikož si všichni v SYNETECHu uživáme programování, oblíbili jsme si Kotlin hlavně díky jeho výstižnosti, kontrole nullových referencí a 100% interoperabilitě s Javou. Přestože to je pouze špička ledovce výhod, které Kotlin přináší, povězme si o těchto hlavních výhodách něco více.

Interoperabilita s Java

Důvodem, proč se Kotlin tak rychle rozšířil v komunitě Android vývojářů (a vlastně i v Googlu samotném), je jeho 100% interoperabilita s Javou. To znamená, že v Kotlinu můžete použít libovolný Java kód/knihovnu. Díky tomu je možné zároveň používat Kotlin a Javu v jednom projektu a nemusíte přepisovat Java projekt znovu od začátku.

Kotlin a kontrola nullových referencí

 


Zdroj:[The Top 10 Exception Types in Production Java Applications — Based on 1B Events | OverOps Blog]

Kotlin obsahuje kontrolu nulových referencí na úrovni syntaxe, která zvyšuje stabilitu aplikace tím, že kontroluje, zda není přistupováno k nullové referenci, a to již při samotném buildu aplikace a ne až při jejím běhu. Proto, pokud správně používáte Kotlin, se nemusíte se obávat _NullPointerException_ výjimek. Kotlin používá stejný způsob definice "nullových" datových typů jako ostatní programovací jazyky (např.Swift) - buď je datový typ optional (volitelný, může tedy být nullový) nebo required (vyžadovaný, nesmí být nullový).

Optional (volitelné) datové typy jsou rozšířeny o znak ? a před přístupem k jejich hodnotě musejí být zkontrolovány, zda nejsou nullové. Pokud je datový typ required (vyžadovaný), pak není nutné kontrolu provádět. Pokud byste se ale pokusili přistoupit k hodnotě optional typu bez kontroly, pak by se na chybu přišlo už během buildu aplikace a proces by selhal dřív než v produkčním kódu u samotných uživatelů.

Výstižnost a přehlednost Kotlinu

Výstižnost syntaxe Kotlinu je hlavní důvod, proč jsme si jej tak oblíbili ❤️. Téměř většina součástí zmíněných níže činí kód výstižnějším a čitelnějším, tudíž výsledné funkcionality jsou stabilnější a odolnější vůči chybám v zápisu kódu.

Datové třídy (data classes)

buď mapování zůstane funkční i při změně daných modelů nebo build aplikace selže. // Example of data class (saved more than 10 lines in comparison to Java)
data class User(val name: String, val surname: String, val age: Int)

// Example of custom getters and setters on a property
class MrUser {
   var name: String = ""
       get() {
           return "Mr. $field" // Get name with "Mr." prefix
       }
       set(newValue) {
           println("old: '$field', new: '$newValue'") // Print change
           field = newValue // Store new value
       }
}

Více o data classes: Data Classes — Kotlin Programming Language

Zapouzdřené třídy (sealed classes)

užitečným modifikátorem je klíčové slovo sealed, které limituje hierarchii dědění třídy. Ukázkový kód definuje datový typ UIState , který může obsahovat pouze potomka jednoho z následujících datových typů: LoadingState, DataState, ErrorState. sealed class UIState
object LoadingState : UIState() // Data is being loaded
class DataState(val data: Data) : UIState() // Data loaded successfully
class ErrorState(val error: Throwable) : UIState() // In case of failure

) můžeme reagovat na změnu stavu celkem snadno a přehledně: fun handleStateChange(newState: UIState) {
   when (newState) {
       is LoadingState -> println("Loading ...")
       is DataState -> println("Got data: ${newState.data}")
       is ErrorState -> println("Error: ${newState.error.message}")
   }
}

Více o sealed classes: Sealed Classes — Kotlin Programming Language

Extensions

Jak jsem již zmínil výše, extensions mohou být použity pro rozšíření existující třídy o další metody mimo definici dané třídy. Bez extensions by bylo nutné definovat např. Util třídu, která není tak čitelná jako syntaxe při použití extension.

Jako příklad níže je uveden kód, který “přidává” metodu makeInvisible() pro třídu typu View a pro všechny její potomky: fun View.makeInvisible() {
   this.visibility = View.INVISIBLE
}

val button: Button = //...
button.makeInvisible()

Více o extension functions: Extensions — Kotlin Programming Language

Single-expression funkce

Metody v Kotlinu mohou vracet hodnotu přímo — bez nutnosti definice těla metody. To znamená, že toto: fun answerToEverything() = 42

je stejné jako toto: fun answerToEverything(): Int {
   return 42
}

Ale mnohem kratší a přehlednější. A při použití s bloky if,when, let, apply, apod. (které také umožňují vracet hodnotu) ještě více praktické. Například: fun parseColor(color: String) = when (color.toLowerCase()) {
   "red" -> "#f00"
   "green" -> "#0f0"
   "blue" -> "#00f"
   else -> "#fff"
}

Výchozí hodnoty

Kotlin, oproti Javě, umožňuje parametrům metod použít výchozí hodnoty, takže již nemusíte definovat několik metod s různými variacemi parametrů.

Například: fun logException(e: Throwable, level = Log.ERROR) {
   // implementation
}

// Then you can call the method without providing the second parameter
logException(IllegalStateException("Test exception"))
logException(IllegalStateException("Test exception"), Log.WARN)

Kotlin také podporuje výchozí implementaci metod definovaných v rozhraních. Například: interface Greeter {
   fun greet() { println("Hi!") }
}

Ale pamatujte, s velkou mocí přichází velká zodpovědnost.

Scope metody

Kotlin obsahuje několik scope metod. Zejména let, apply,run,with,also. Tyto metody snižují potřebu psaní boilerplate kódu tím, že mění scope v bloku těla dané scope metody.

Jeden příklad řekne víc než tisíc slov: private const val DURATION_IN_MS = 3000L

val fadeInAnimation = AlphaAnimation(0f, 1f).apply {
   duration = DURATION_IN_MS
   interpolator = AccelerateDecelerateInterpolator()
   fillAfter = true
}

Výše uvedený kód vytvoří instanci AlphaAnimation , na které modifikuje část proměnných a vrátí finální (již modifikovanou) instanci. V tomto konkrétním případě apply mění referenci this na instanci AlphaAnimation(0f, 1f) a poté je stejná instance vrácena a uložena do fadeInAnimation proměnné.

Pokud se chcete dozvědět více o scope metodách, doporučuji si přečíst článek Mastering Kotlin standard functions: run, with, let, also and apply.

Delegát proměnné (property delegate)

Delegát je třída, která reaguje na přístup k proměnné — na její čtení a/nebo zápis. Tudíž pokaždé, kdy je přistupováno k proměnné, je provolána příslušná metoda delegáta.

je lazy, který vyhodnotí tělo funkce poprvé, kdy je daná proměnná čtena, a návratovou hodnotu uchová pro další čtení. Poté při každém čtení proměnné je vrácena původně uložená hodnota. Níže uvedený příklad vyhodnotí metodu computeAnswer() při prvním čtení proměnné answer a poté se již metoda computeAnswer() nevyhodnocuje a vrací se předchozí hodnota. val answer: Int by lazy {
   computeAnswer()
}

fun computeAnswer() = 42

Více o delegátech: Delegated Properties — Kotlin Programming Language

Funkce a lambdy

Jelikož je Kotlin moderní programovací jazyk, jeho syntaxe podporuje funkce vyššího řádu a lambda výrazy. Pokud tedy chcete psát reaktivní kód, lambdy a callback funkce mohou být užitečné. Jejich deklarace je jednoduchá a přehledná. Následující příklad prezentuje metodu pro “získání všech uživatelů”, která se skládá ze dvou callbacků. Callback onSuccess by měl být provolán v případě úspěšného získání dat. V opačném případě je zavolán callback onError , který obsahuje samotnou výjimku. data class User(val name)

interface UserDataSource {
   fun getAllUsers(
           onSuccess: (users: List) -> Unit,
           onError: (err: Throwable) -> Unit
   )
}

// class UpdateDataSourceImpl: UserDataSource { .... }

UpdateDataSourceImpl().getAllUsers(
   onSuccess = { users -> print(users) }, // here you consume success state
   onError = { err -> print(err.message) } // here you consume error state
)

Více o lambdách a funkcích: Functions: infix, vararg, tailrec — Kotlin Programming Language

Poznámka: Existuje i další způsob, jak definovat asynchronní operace, kterým jsou coroutines. Více na Coroutines Guide — Kotlin Programming Language.

Multiplatformnost Kotlinu

Dobrou zprávou je, že Kotlin je multiplatformní jazyk! To znamená, že můžete kód používat na více platformách. Rádi bychom psali business logiku pouze jednou a poté bychom ji použili i na ostatních platformách — například jak na Androidu tak na iOS. Více o podpoře více platforem je zmíněno na oficiálním webu Multiplatform Projects — Kotlin Programming Language.

…a mnohem více

Tato sekce pokryla pouze pár vlastností Kotlinu, kterých si opravdu ceníme, ale můžete z něj dostat mnohem více! K tomu vám může pomoci dokumentace syntaxe Kotlinu — Reference — Kotlin Programming Language.

Gotchas — Na co si u Kotlinu dát pozor? 🙀

V této sekci zmíníme pár věcí, na které si dát pozor, a o kterých je dobré vědět. Pár z nich nás překvapilo, ale ne mile…

Nullové reference a Java kód

Jak jsem již zmínil výše, Kotlin kontroluje na úrovni syntaxe, zda proměnné jsou datového typu, který může obsahovat nullovou referenci, či ne. Proto bychom již neměli dostat NullPointerException. To zní hezky. Ale kód napsaný v Javě neobsahuje informaci, zda proměnná/parametr může nabývat nullové reference nebo ne. Ano, některé proměnné nebo parametry mohou být označeny @NotNull či @Nullable anotacemi (Calling Java from Kotlin - Kotlin Programming Language), které Kotlin kompilátor bere v potaz a získá tak informaci o tom, zda reference může být nullová. Bohužel ale většina Java kódu není takto anotována. Proto je lepší předpokládat, že při přístupu k proměnné může být reference nullová (či naopak nesmí být nullová při volání metody s parametry) než čelit NullPointerException výjimce.

Řízení výjimek Java vs. Kotlin

V Javě existuje klíčové slovo throws, které indikuje, že metoda může vyhodit specifickou výjimku a tudíž přistupující část kódu musí tuto výjimku odchytit či propagovat dále, aby nedošlo k pádu aplikace. Kotlin nepodporuje kontrolu výjimek Calling Java from Kotlin - Kotlin Programming Language, a tudíž nevyžaduje, aby se dané vyjímky musely odchytit/propagovat dále. Pokud tedy přistupujete k logice psané v Javě, je dobré si zkontrolovat, zda daná metoda záměrně nevyhazuje konkrétní výjimku. V případě že ano, musíme ošetřit volání dané metody try-catch blokem jako bychom to udělali v Javě.

Rozsahy ve smyčkách

-smyčku definovat následovně: var max = 10
for (i in 1..max) {
 // ...
}

jako toto: var max = 10;
for (int i = 1 ; i

Výše uvedený kód odpovídá tomuto kódu: var max = 10
val range = IntRange(1, max)
for (i in range) {
 // ...
}

To znamená, že pokud byste změnili hodnotu proměnné max v těle smyčky, nemělo by to žádný vliv na její rozsah. Není špatně, že kód je de-facto optimalizovaný a podmínka je vyhodnocena pouze jednou, ale pokud takovéto chování neočekáváte, může vás nemile překvapit a mohli byste strávit hodně času debuggováním, protože byste očekávali, že se podmínka vyhodnocuje v každém kroku. Pro takové situace se hodí spíše použít smyčku while.

Priorita Elvis operatoru

Představte si, že máte následující kus kódu: val a: Int? = 10
val b: Int = 10
val sum = a ?: 0 + b

Čekali byste, že hodnota sum bude 20, že? Ale ve skutečnosti je 10! Proč? Protože dle priorit operátorů (dle:Grammar) má operátor sčítání vyšší prioritu než Elvis operátor. Tudíž Kotlin kompilátor vyhodnotí výraz následovně: val sum = a ?: (0 + b)

Není to úplně patrné na první pohled, proto je dobré být opatrný při používání Elvis operátoru.

Konstruktory and inicializéry

): class InitOrderDemo(name: String) {
   val firstProperty = "First property: $name".also(::println)

   init {
       println("First initializer block that prints ${name}")
   }

   val secondProperty = "Second property: ${name.length}".also(::println)

   init {
       println("Second initializer block that prints ${name.length}")
   }
}

InitOrderDemo("hello")

je následující:

First property: hello
First initializer block that prints hello
Second property: 5
Second initializer block that prints 5

Dejte si tedy pozor na inicializační bloky, protože záleží na jejich pořadí.

Shrnutí aneb proč tedy Kotlin?

Pamatuji si radost, jakou jsme měli, když Apple představil Swift. Bylo super psát bezpečný a výstižný kód a ušetřilo to celkem dost času a výsledné aplikace byly mnohem stabilnější. Myslím si, že adaptace Kotlinu pro Android platformu je ta nejlepší věc, co se mohla Android komunitě stát. A při použití Android Kotlin knihovny GitHub — Kotlin/anko: Pleasant Android application development , je vývoj mnohem rychlejší, jednodušší a hlavně méně “bolestivý”.

Nebojte se ve vašem projektu dát Kotlinu šanci, je opravdu snadné se Kotlin naučit. A pokud se obáváte nasadit Kotlin na váš projekt, můžete jej vyzkoušet na stránce Try Kotlin. Komunita okolo Kotlinu společně s JetBrains odvedla opravdu kus dobré práce! 👏

Díky za přečtení a dejte nám vědět vaše zkušenosti s Kotlinem formou komentářů níže 🙂👇.

AppCast

České appky, které dobývají svět

Poslouchejte recepty na úspěšné aplikace. CEO Megumethod Vráťa Zima si povídá s tvůrci českých aplikací o jejich businessu.

Gradient noisy shapeMegumethod CEO Vratislav ZimaMegumethod Scribble