Page 1
Univerzita Hradec Králové
Fakulta informatiky a managementu
Katedra informatiky a kvantitativních metod
Aplikace pro podporu dne otevřených dveří na FIM
Diplomová práce
Autor: Bc. David Richter Studijní obor: Aplikovaná informatika - 2
Vedoucí práce: doc. Ing. Filip Malý, Ph.D.
Hradec Králové Duben 2016
Page 2
Prohlášení:
Prohlašuji, že jsem diplomovou práci zpracoval samostatně a s použitím
uvedené literatury.
V Hradci Králové dne 26.4.2016 Bc. David Richter
Page 3
Poděkování:
Děkuji vedoucímu diplomové práce doc. Ing. Filipovi Malému, Ph.D. za
metodické vedení práce a pomoc se zpracováním.
Page 5
Anotace
V této práci je popsán programovací jazyk Kotlin a jeho použití pro vytvoření
aplikace pro podporu dne otevřených dveří na FIM. V první části je vysvětlena
syntaxe Kotlinu s ukázkami zdrojového kódu včetně popisu dané ukázky. Dále jsou
popsány některé pokročilejší funkce Kotlinu, část standardní knihovny a porovnání
Kotlinu a Javy ve formě kompilace do bytekódu a dekompilace do Javy. V další části
je uveden popis funkcionality aplikace pro podporu dne otevřených dveří. Nejdříve
je popsán model aplikace, kde je uvedena struktura, části aplikace včetně vysvětlení
jejich funkcí, a poté je popsána implementace aplikace a použitých frameworků,
uvedení jejich základní funkčnosti, a ukázky částí zdrojového kódu aplikace.
Annotation
Title: Application to support the Open days at FIM
The subject of this thesis is the Kotlin programming language and its use in creating
an application to support the Open days at FIM. In the first part, Kotlin’s syntax is
explained and source code examples are given and analyzed. In addition, selected
advanced functions of the Kotlin language are examined, in particular part of the
standard library. This is followed by a comparison of the Kotlin and Java
programming languages made in the form of compilation to bytecode and
decompilation to Java. The subsequent part of the thesis focuses on the application’s
functionality. Firstly, a model of the application is introduced. This includes
a description of the application model’s overall structure and a more detailed
explanation of its individual parts. Secondly, the application’s implementation is
discussed in relation to the used frameworks and their basic description. The thesis
is concluded by providing sample parts of the application’s source code.
Page 6
Obsah
1 Úvod................................................................................................................................................. 1
2 Kotlin ............................................................................................................................................... 3
2.1 Základní prvky syntaxe .................................................................................................... 3
2.1.1 Třídy................................................................................................................................ 3
2.1.2 Konstruktory ............................................................................................................... 4
2.1.3 Funkce ............................................................................................................................ 5
2.1.4 Proměnné...................................................................................................................... 6
2.1.5 Řízení běhu programu ............................................................................................. 7
2.1.6 Modifikátory viditelnosti ........................................................................................ 9
2.2 Pokročilé vlastnosti .........................................................................................................10
2.2.1 Null reference ............................................................................................................10
2.2.2 Data třídy ....................................................................................................................12
2.2.3 Generické typy ..........................................................................................................12
2.2.4 Lambda výraz ............................................................................................................14
2.2.5 Delegates .....................................................................................................................15
2.3 Standardní knihovna .......................................................................................................16
2.3.1 Kolekce .........................................................................................................................16
2.3.2 Obecné funkce ...........................................................................................................18
2.4 Dekompilace do Javy .......................................................................................................22
2.4.1 Top level funkce .......................................................................................................22
2.4.2 Lambda výrazy..........................................................................................................23
2.4.3 Přiřazení konstruktu pro řízení běhu programu .........................................25
2.4.4 Defaultní hodnoty parametrů .............................................................................26
3 Aplikace pro podporu dne otevřených dveří .................................................................28
3.1 Model .....................................................................................................................................28
Page 7
3.1.1 Serverová část ...........................................................................................................28
3.1.2 Mobilní část ................................................................................................................28
3.2 Implementace ....................................................................................................................29
3.2.1 Serverová část ...........................................................................................................29
3.2.2 Mobilní část ................................................................................................................37
4 Shrnutí výsledků .......................................................................................................................47
5 Závěry a doporučení................................................................................................................48
6 Seznam použité literatury .....................................................................................................50
7 Přílohy ..........................................................................................................................................52
Page 8
Seznam obrázků
Obr. 1 Stránka pro úpravu informací. ........................................................................................36
Obr. 2 Stránka pro úpravu událostí. ...........................................................................................37
Obr. 3 Hlavní aktivita a detail události. .....................................................................................46
Seznam tabulek
Tabulka 1 modifikátory viditelnosti uvnitř třídy. .................................................................10
Tabulka 2 modifikátory viditelnosti v rámci balíčku. ..........................................................10
Page 9
1
1 Úvod
Tato práce se zabývá programovacím jazykem Kotlin, konkrétně verzí 1.0, jeho
syntaxí, funkcemi a srovnáním s programovacím jazykem Java, se kterým je
kompatibilní. A také praktickým využitím jazyka Kotlin pro implementaci aplikace
pro podporu dne otevřených dveří na Fakultě informatiky a managementu,
Univerzity Hradec Králové.
Cílem této práce je představit jazyk Kotlin a jeho praktické využití při vývoji reálné
aplikace, se zaměřením na porovnání s programovacím jazykem Java. Zabývá se
rozdíly mezi těmito jazyky a úskalími, které mohou vzniknout při přechodu z Javy.
Popis jazyka Kotlin čerpá z dokumentace výrobce na oficiálních stránkách jazyka,
kde je také možné najít další rozšiřující informace a aktuální novinky o vývoji tohoto
jazyka, včetně plánovaných nových funkcí. Existuje zde také možnost se zapojit do
diskuze a ovlivnit tak budoucí funkce jazyka.
V první části práce je popsán jazyk Kotlin, jeho syntaxe a funkce s krátkými příklady
jeho použití, včetně podrobného vysvětlení daných funkcí, například pomocí
referencí na funkcionalitu v Javě.
Nejdříve jsou popsány základní syntaktické prvky Kotlinu, jako je zápis tříd, funkcí
a proměnných. Dále jsou popsány konstrukty pro řízení běhu programu, včetně
srovnání s jejich obdobami v Javě a jaké funkce mají v Kotlinu navíc.
V další části jsou uvedeny pokročilé funkce a vlastnosti jazyka Kotlin, například
speciální syntaxe pro typy, které mohou obsahovat null referenci. Data třídy, které
zkracují zápis POJO tříd a generují implementaci některých funkcí. Generické typy,
jejich specifika a rozdíly oproti Javě. Lambda výrazy a jejich možnosti použití.
Také se zde nachází popis některých užitečných tříd ze standardní knihovny Kotlinu
a popis, jak mohou být použity pro ulehčení řešení často opakovaných vzorů při
programování. V této kapitole jsou popsány kolekce a jejich specifika při použití
v Kotlinu a také některé funkce, které lze použít pro práci s různými objekty
i některé více specializované používané například pro operace se soubory.
V další kapitole je ukázka jak vypadá Kotlin po kompilaci a zpětné dekompilaci do
Javy, pro lepší pochopení, jak fungují některé funkce Kotlinu, například lambda
Page 10
2
výrazy, i když jsou zkompilovány pomocí Javy verze 1.6, která nemá jejich přímou
podporu.
V druhé části je popsána aplikace pro podporu dne otevřených dveří na FIM. Nejprve
jsou rozebrány její funkce a její přínos. V další kapitole je popsán model, zejména že
je aplikace rozdělena na serverovou a mobilní část, a popis jak spolu vzájemně
komunikují a další informace o designu chování aplikace.
A v poslední části se nachází konkrétní popis implementace obou částí, včetně
použitých frameworků, stručného popisu jejich funkce a jak jsou použity.
Page 11
3
2 Kotlin
Kotlin je staticky typovaný programovací jazyk určený pro Java Virtual Machine
(JVM) a JavaScript. Kotlin je vyvíjen společností JetBrains s.r.o., která vyvinula mimo
jiné vývojové prostředí pro programovací jazyk Java - IntelliJ IDEA a používá Kotlin
pro vývoj plug-inů a nových produktů.
Protože mohou být zdrojové soubory kompilovány do bytekódu, je tento jazyk
kompatibilní s knihovnami pro programovací jazyk Java a ostatní JVM jazyky, jako
je například Groovy.
2.1 Základní prvky syntaxe
Syntaxe Kotlinu je mezistupněm mezi Groovy a Javou. Groovy jako dynamický jazyk
umožňuje velmi rozsáhlou škálu použití, ale na úkor typové bezpečnosti při
kompilaci – kompilátor nemůže odhalit, zda daná funkce nebo třída nebude
dostupná za běhu. Naproti tomu Kotlin je statický jazyk, takže zajišťuje stejnou
typovou bezpečnost při kompilaci jako Java, ale zároveň disponuje už v základu
některými pokročilými konstrukty, které v Javě chybí, například přetěžování
operátorů, rozšířenou podporu pro takzvané Ranges nebo pokročilejšími
konstrukty pro řízení běhu programu – if, switch, atd. Kotlin se také soustředí na
některé neduhy Javy, například: null safety nebo checked exceptions. Mnoho funkcí
jazyka je přizpůsobeno radám uvedeným v knize Effective Java. [1]
Kotlin stejně jako v Java používá pro zapouzdření souvisejícího kódu balíčky
(package) a třídy. Na rozdíl od Javy umožňuje Kotlin mít funkce i mimo třídu. Jednou
z výhod oproti Javě je stručnější zápis kódu, tzn., že pro napsání stejné funkčnosti
nevyžaduje Kotlin takové množství nadbytečného kódu. Další výhodu lze spatřit
v tom, že v Kotlinu není nutné ukončovat příkazy středníkem, pokud to není
nezbytné pro oddělení více příkazů na jednom řádku.
2.1.1 Třídy
Podle webových stránek Classes and inheritance, definice třídy obsahuje klíčové
slovo class a umožňuje vytvořit konstruktor přímo v definici. Pro zapsání
dědičnosti se namísto extends nebo implements používá dvojtečka pro třídy
i rozhraní.
Page 12
4
class Person
class City(name: String)
open class Parent
class Child : Parent()
V předchozím příkladu kódu je vidět, že v Kotlinu jsou složené závorky nepovinné,
pokud není potřeba uvedení více příkazů v bloku. Třída Person je příkladem
nejjednoduššího možného zápisu třídy. Třída City obsahuje deklaraci konstruktoru
s jedním parametrem, který je možné použít pro inicializaci atributů třídy. Třída
Parent slouží jako předek pro třídu Child, definice musí obsahovat klíčové slovo
open, protože třídy v Kotlinu jsou implicitně final. Třída Child je potomkem třídy
Parent, jejíž konstruktor musí v deklaraci dědičnosti volat. [2]
2.1.2 Konstruktory
Konstruktory se v Kotlinu umísťují do definice třídy, ale mohou být stejně jako
v Javě definovány uvnitř třídy pomocí klíčového slova constructor, v definici
konstruktoru je možné deklarovat atributy třídy pomocí klíčových slov var/val,
které jsou pak inicializovány hodnotami předanými do konstruktoru.
class Point(val x: Int = 0, val y: Int = 0)
class AppControlledInstantiation internal constructor()
class Company {
constructor() {
}
}
class Employee(val name: String, var age: Int)
Konstruktor třídy Point ukazuje možnost definice defaultních hodnot pro
parametry konstruktoru, takže při volání konstruktoru je pak definice těchto
parametrů volitelná. Třída AppControlledInstantiation představuje příklad
definice konstruktoru, který je přístupný pouze ze tříd daného modulu.
Třída Company ukazuje definici konstruktoru uvnitř třídy.
Anotace konstruktoru je možné použít v případě zápisu jako u tříd
AppControlledInstantiation a Company.
Page 13
5
Kód třídy Employee odpovídá následujícímu kódu v Javě:
public class Employee {
private final String name;
private int age;
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Na předchozím kódu je vidět, o kolik méně kódu, oproti Javě, je nutné napsat
v Kotlinu oproti Javě při zachování stejné funkcionality, což přispívá k lepší
čitelnosti kódu. Více informací a příkladů je uvedeno na webových stránkách Kotlin
Programming Language. [2]
2.1.3 Funkce
Jak je uvedeno na webových stránkách Functions, funkce je obdoba metody v Javě,
její definice obsahuje klíčové slovo fun, stejně jako třídy jsou i funkce implicitně
final, takže pro jejich překrytí je nutné použít klíčové slovo open. Syntaxe funkce,
stejně jako mnoho ostatních věcí v Kotlinu, umožňuje vynechat nepotřebné prvky
syntaxe. Například návratový typ v případě, kdy má funkce návratový typ Unit
(obdoba void) nebo je použita syntaxe návratové hodnoty s = (jednořádkové
funkce). Dalším rozdílem oproti Javě je, že funkce nemusí být definovány uvnitř
třídy, ale mohou být definovány v rámci balíčku. Stejně jako u konstruktorů je i pro
parametry metod možné použít defaultní hodnoty. Kotlinu obsahuje podporu pro
takzvané extension funkce, díky kterým lze přidávat funkcionalitu do existujících
tříd, i když není možné přímo upravovat zdrojový kód těchto tříd. [3]
Page 14
6
fun sum(a: Int, b: Int): Int {
return a + b;
}
fun times(a: Int, b: Int) = a * b
fun String.isLongerThan10() = this.length > 10
fun testIsLongerThan10() {
"hello".isLongerThan10() == false
}
Funkce sum představuje standardní zápis funkce včetně návratové hodnoty,
zatímco funkce times ukazuje, jak je možné využít kratší zápis pro jednořádkové
funkce.
String.isLongerThan10 představuje zápis extension funkce. Pokud je funkce
definována tímto způsobem, je pak možné ji volat nad objekty dané třídy. V tomto
případě bude možné volat funkci isLongerThan10 nad jakýmkoliv Stringem.
Zároveň ukazuje, že tělo funkce se definuje, jako by daná třída byla opravdu v dané
třídě, tedy klíčové slovo this odkazuje na současnou instanci třídy String, nad
kterou je funkce zavolána. V tomto případě by bylo možné identifikátor this
vynechat, protože název atributu length nekoliduje s žádnou lokální proměnnou.
Funkce testIsLongerThan10 ukazuje použití extension funkce a také možnost
vynechat návratový typ, pokud funkce nic nevrací.
2.1.4 Proměnné
Proměnné jsou základem každého programovacího jazyka, Kotlin obsahuje
stručnou a jednoduchou syntaxi pro jejich používání. Deklarace proměnné obsahuje
jedno z klíčových slov var/val pro odlišení, zda bude proměnná konstantní val
nebo variabilní var. Typ proměnné je možné vynechat v případě okamžitého
přiřazení hodnoty. Další informace na webových stránkách Properties and Fields.
[4]
Page 15
7
var a: Int = 0
var b = 0
var c: Int
c = 0
a++
val d = 0
val e: Int
e = 0
/*
a = "string"
e++
*/
V předchozím příkladu je ukázka různých kombinací deklaraci a definicí
proměnných s uvedením typu proměnné a přiřazením hodnoty. A také
zakomentované příklady statické kontroly typu při kompilaci a nemožnost změny
hodnoty konstantní proměnné.
2.1.5 Řízení běhu programu
Další běžnou součástí většiny programovacích jazyků jsou konstrukty pro řízení
běhu programu, mezi které v Kotlinu patří if - else, when, while, for, return, break
a continue.
Podmínka if-else
if (input.isLongerThan10()) {
processLongString(input)
} else {
processShortString(input)
}
val result = if (input.isLongerThan10()) "long" else "short"
val list: List<Int> = createSomeList()
if (list is LinkedList<Int>) {
list.push(3)
}
První podmínka v příkladu ukazuje klasické užití konstruktu if – else. Druhý if
ukazuje funkci Kotlinu, která umožňuje „vrátit“ z podmínky hodnotu posledního
příkazu v daném bloku, zde tedy bude proměnná result obsahovat řetězec „long“
nebo „short“ v závislosti na délce vstupního řetězce. Poslední podmínka ukazuje
možnost automatického přetypovaní při ověřování. Obdobou v Javě je:
final List<Integer> list = createSomeList();
if (list instanceof LinkedList<Integer>) {
((LinkedList<Integer>) list).push(3);
}
Page 16
8
Přepínač when
When je rozšířenou obdobou přepínače switch z Javy. Jeho možnosti jsou patrné
z následujícího příkladu:
val apologiesList: List<String> = listOf("sorry", "my apologies")
when (input) {
"hi" -> sayHello()
"bye", "goodbye" -> sayGoodbye()
in apologiesList -> apologize()
}
val list: List<Int> = createSomeList()
when (list) {
is LinkedList<Int> -> list.push(3)
is Stack<Int> -> list.search(3)
}
val result = when {
input.isLongerThan10() -> "long"
input.isEmpty() -> "empty"
else -> "short"
}
První when ukazuje možnosti použití pro zavolání různých funkcí na základě vstupu,
je možné použít konkrétní hodnotu, více hodnot nebo vyhledání dané hodnoty
v kolekci. Druhý when představuje možnosti automatického přetypování na daný
typ. A ve třetím příkladu je vidět vlastnost when pro přiřazení hodnoty proměnné na
základě výsledku funkcí volaných nad objektem vstupní proměnné.
Cyklus while a do-while
While a do-while vypadají a fungují stejně jako odpovídající konstrukty v Javě,
s výjimkou toho, že v do-while lze deklarovat řídící proměnnou cyklu uvnitř bloku.
var i = 10
while (i > 0) {
i--
}
do {
val y = readLineFromFile()
} while (y != null)
Cyklus for
Základní podoba for cyklu v Kotlinu je podobná for-each cyklu v Javě. Lze ho použít
pro iteraci přes jakýkoliv objekt, který je schopen vrátit iterátor. Klasický for cyklus,
který iteruje pomocí indexu v Kotlinu není, ale je možné ho nahradit použitím
takzvané Range.
Page 17
9
val list = createSomeList()
for (i in list) {
doSomethingWithItem(i)
}
for ((index, value) in list.withIndex()) {
println("list contains $value at index $index")
}
for (i in 1..10 step 2) {
println("writing range 1-10 inclusive with step 2: $i")
}
První for představuje použití jako for-each cyklus s tím, že uvedení typu prvku
kolekce je volitelné. Druhý cyklus ukazuje možnost převedení prvků kolekce na
indexované položky, takže je možné v bloku for cyklu přistupovat jak k indexu, tak
i k prvku kolekce. Poslední for iteruje přes objekt Range a je obdobou klasického
for cyklu z Javy, v tomto případě objekt Range bude obsahovat čísla 1, 3, 5, 7 a 9.
V Kotlinu Range může obsahovat i jiné objekty než jen čísla, např.: data nebo je
možné vytvořit si vlastní implementaci Range a iterovat přes ni.
Operátory return, break a continue
Operátory return, break a continue fungují stejně jako v Javě se syntaktickou
výjimkou pro skok na label.
val list: List<List<Int>> = createSomeListOfLists()
loop@ for (i in list) {
for (j in i) {
if (j == 3) {
break@loop
} else if (j == -1) {
continue@loop
}
}
}
2.1.6 Modifikátory viditelnosti
Podle webových stránek Visibility Modifiers jsou v Kotlinu 4 modifikátory
viditelnosti, a to private, protected, internal a public. Platí, že pokud není
uveden žádný identifikátor, je použit public.
Page 18
10
Tabulka 1 modifikátory viditelnosti uvnitř třídy.
Uvnitř třídy Třída potomek modul kdekoliv
private x
protected x x
internal x x
public x x x x
Zdroj: data pro tabulku [5]
Tabulka 2 modifikátory viditelnosti v rámci balíčku.
Uvnitř balíčku Soubor modul kdekoliv
private x
internal x x
public X x x
Zdroj: data pro tabulku [5]
Na rozdíl od Javy vnější třída nemůže přistupovat k private prvkům vnitřní třídy.
Dalším rozdílem je, že k internal prvkům nelze přistupovat z potomků v jiných
modulech.
Modul, který určuje viditelnost internal je modul v rámci IDE IntelliJ IDEA, Maven,
Gradle projekt nebo množina souborů zkompilovaná v rámci jednoho volání Ant
tasku. [5]
2.2 Pokročilé vlastnosti
V kapitole 2.1 byly představeny základní prvky Kotlinu, které jsou podobné funkcím
Javy, ve většině případů rozšířené o některé užitečné funkce. V této kapitole budou
ukázány prvky, které patří mezi pokročilé funkce Javy nebo v Javě vůbec nejsou.
Také zde budou popsány podrobněji funkce, které byly stručně uvedeny v kapitole
2.1, ale mají další rozšiřující funkcionalitu, která nebyla vysvětlena v rámci základní
funkcionality.
2.2.1 Null reference
Jak je uvedeno na webových stránkách Kotlin Programming Language, Kotlin se
snaží vyhýbat používání null reference tím, že standardně všechny proměnné
a parametry jsou ne-nullové, to znamená, že do nich nelze uložit null. Kvůli
kompatibilitě s Javou bylo ovšem nutné nějak zajistit, aby do metod a proměnných
Page 19
11
bylo možné null uložit, proto pro takzvané nullable proměnné existuje odlišná
syntaxe. Také je možné z nullable proměnné udělat normální proměnnou, např.:
automatickým přetypováním takzvaným null-checkem - klasický if ověřující, že
proměnná neobsahuje null. [6]
var nullableVariable: String? = null
var classicVariable: String = ""
/*
classicVariable = null
classicVariable = nullableVariable
*/
if (nullableVariable != null) {
classicVariable = nullableVariable
}
classicVariable = nullableVariable ?: ""
classicVariable = nullableVariable!!
val nullableObject: NullableObject? = getNullableObject()
var nullableResult: String =
nullableObject
?.returnNullableService()
?.returnNullableResult() ?: "unable to get result"
var codeThrowingNPE: String? =
nullableObject!!
.returnNullableService()!!
.returnNullableResult()
V tomto příkladu je vidět, že nullable typy se označují pomocí otazníku a že není
možné přiřadit null, ani nullable proměnnou přímo do klasické proměnné, takový
kód nebude možné zkompilovat. První možnost jak to udělat je pomocí null-checku,
který automaticky přetypuje String? na typ String. Druhou možností je použít
takzvaný elvis operátor „?:“, který v případě, že proměnná obsahuje null, přiřadí
do classicVariable pravý operand tohoto operátoru. Třetí možností je použít
non-null aserci v podobě operátoru „!!“, který zajistí vyhození
NullPointerException v případě, že proměnná obsahuje null.
Další příklad ukazuje null-safe volání, které zajišťuje, že pokud některá metoda
v řetězci volání vrátí null, kód nezpůsobí vyhození NullPointerException, ale
místo toho vrátí null a při použití elvis operátoru se do proměnné uloží pravý
operand tohoto operátoru.
A poslední příklad ukazuje kód, který se bude v případě nullové návratové hodnoty
chovat stejně jako Java – vyhodí NullPointerException.
Page 20
12
2.2.2 Data třídy
Při programování v Javě jsou velmi často vytvářeny třídy, které mají jedinou funkci
– přenášet data. Většina kódu těchto tříd je často generována pomocí IDE, jedná se
především o gettery, settery, konstruktory a metody equals, hashCode a toString.
V Kotlinu lze tyto třídy označit klíčovým slovem data. Při kompilaci jsou všechny
tyto funkce a konstruktory vygenerovány, navíc jsou generovány další funkce,
například componentN, kde N odpovídá pořadí definovaných vlastností. Tyto metody
umožňují rozložení objektu na množinu proměnných.
Další z generovaných funkcí je copy, která může být využita pro zkopírování objektu
a změnu některých jeho vlastností. Pokud některá z těchto generovaných metod je
už ve třídě definována nebo zděděna, nebude generována. Více na webových
stránkách Kotlin Programming Language. [7]
2.2.3 Generické typy
Jak je psáno na webových stránkách Kotlin Programming Language, generické typy
jsou používány pro psaní tříd a funkcí, které mohou pracovat s předem
nespecifikovaným typem a přitom nemusel být použit typ Object nebo Any
v případě Kotlinu, a tím obětovat statickou typovou bezpečnost. Typickým
příkladem a nejčastěji používaným generickým typem jsou kolekce. Ty umožňují
stejné operace nad jakýmkoliv typem a pomocí generických typů je možné zajistit,
aby kolekce mohly obsahovat pouze prvky daného typu, což by v případě použití
Object nebo Any nebylo možné. Při používání generických typů je někdy nutné
specifikovat dodatečné podmínky pro dané typy, například, že je možné použít jen
typy implementující určitý interface. V Javě je toto zajištěno pomocí klíčového slova
extends. V Javě je možné napsat <T extends Closeable> což znamená, že typem
musí být třída implementující interface Closeable, potom je možné na proměnné
typu T volat metodu close. V Kotlinu je klíčové slovo extends nahrazeno za
dvojtečku a více typů je možné specifikovat pomocí klauzule where. [8]
Page 21
13
fun <T> multiplyByTwoAndClose(items: T)
where T: Iterable<Int>,
T: Closeable {
for (item in items) {
doSomethingWithItem(item * 2)
}
items.close()
}
Jak píše Bloch, jednou z neočekávaných vlastností tříd a metod používajících
generické typy je, že nejsou vzájemně kompatibilní, i když jejich generické typy
kompatibilní jsou [1: s. 134]. Například následující zakomentovaný Java kód by
nešel zkompilovat, i když je Integer potomkem Number.
public static void main(String[] args) {
Stack<Number> stack = new Stack<>();
List<Integer> integers = Arrays.asList(1, 2);
// stack.pushAll(integers);
}
public static class Stack<E> {
public void push(E e) {}
public E pop() {return null;}
public boolean isEmpty() {return false;}
public void pushAll(Iterable<E> src) {
for (E e : src)
push(e);
}
}
Řešením je použití takzvaného „bounded wildcardu“. Ten umožní, že je možné do
metody předat parametr jakéhokoliv typu, který je typově kompatibilní s daným
typem E. Takto by vypadala výsledná metoda pushAll.
public void pushAll(Iterable<? extends E> src) {
for (E e : src)
push(e);
}
Při použití tohoto parametru, bude možné zakomentovaný kód v předchozím
příkladu zkompilovat. Opačným případem je metoda popAll v následujícím
příkladu.
Stack<Integer> stack = new Stack<>();
List<Number> integers = new ArrayList<>();
// stack.popAll(integers);
public void popAll(Collection<E> dst) {
while (!isEmpty())
dst.add(pop());
}
Zde je pro kompilaci zakomentovaného kódu nutné změnit parametr dst na typ
Collection<? super E>. Bloch tyto typy pojmenovává jako consumers respektive
Page 22
14
producers a navrhuje mnemotechnickou pomůcku, jak si zapamatovat, kdy jaké
klíčové slovo použít „PECS stands for producer-extends, consumer-super“ [1: s. 136].
Tuto mnemotechnickou pomůcku není nutné při používání Kotlinu znát, protože
typy označované jako producers používají klíčové slovo out a typy consumers
používají klíčové slovo in. Pro porovnání by funkce pushAll a popAll v Kotlinu
vypadaly následovně:
fun pushAll(src: Iterable<E>) {
for (e in src)
push(e)
}
fun popAll(dst: MutableCollection<in E>) {
while (!isEmpty)
dst.add(pop())
}
Protože rozhraní Iterable v Kotlinu už má generický parametr označen jako out,
není nutné jej specifikovat v použité funkci.
Kotlin navíc podporuje použití generických typů v extension funkcích, takže je
možné „přidat“ funkce do předem nespecifikovaných tříd.
fun <T, E> T.assertArraySize(array: Array<E>, expectedSize: Number) {
if (array.size != expectedSize) {
Assert.fail()
}
}
Takto lze přidat další typy assert funkce do testů, aniž by musely dědit od společné
třídy, ve které by tyto funkce byly definovány, nebo používat pomocnou třídu se
statickými metodami.
2.2.4 Lambda výraz
Lambda výraz v Kotlinu je obdobou closure z Groovy a lambda výrazu z Javy 8. Lze
ho využít pro předávání funkcionality do funkcí jako parametr, ukládat do
proměnných nebo použít jako návratovou hodnotu funkce.
Page 23
15
fun lambdaSyntax() {
val plus = { x: Int, y: Int -> x + y }
val minus: (Int, Int) -> Int = { x, y -> x - y }
val isOdd = { x: Int -> x % 2 == 1 }
val isEven: (Int) -> Boolean = { it % 2 == 0 }
val ints = createSomeList()
val oddIntsSquared = ints.filter(isOdd).map { it * it }
}
Předchozí příklad ukazuje různé zápisy lambda výrazů i jejich použití pro práci
s kolekcemi.
2.2.5 Delegates
Jak uvádí webová stránka Kotlin Programming Language, pro definici vlastnosti
třídy v Kotlinu je možné delegovat její inicializaci na tzv. delegates. Delegate je
objekt, který obsahuje funkce getValue a pro nekonstantní vlastnosti i setValue,
které jsou volány při přístupu k delegované vlastnosti. Mezi delegates, které jsou
obsaženy přímo v knihovně Kotlinu, patří: lazy, observable, vetoable, notNull
a map. Lazy delegate lze použít pro odloženou inicializaci hodnoty proměnné, takže
její hodnota je vypočítána při prvním přístupu k vlastnosti a je inicializována pomocí
lambda výrazu předaného do tohoto delegate jako parametr. Pravidla pro
synchronizaci při použití tohoto delegate lze nastavit pomocí volitelného
parametru. Observable delegate umožňuje definovat akci při čtení a modifikaci
vlastnosti, definovanou pomocí lambda výrazu předaného jako parametr. Tento
delegate je implementací návrhového vzoru observer pro přístup k vlastnosti. Jak
píše Pecinovský, „Zavádí vztah mezi objekty (pozorovateli) reagujícími na změnu
(pozorovaného) objektu nebo jím sledované události. Pozorovatelé se u pozorovaného
objektu přihlásí a ten je pak na každou změnu svého stavu či výskyt události upozorní.“
[8: s 375] Tento delegate neumožňuje zabránění modifikace vlastnosti. Vetoable
delegate je podobný observable s tím rozdílem, že umožňuje zabránit modifikaci
vlastnosti. NotNull delegate je vhodný pro vlastnosti, jejichž typ nemá být nullable,
ale při konstrukci objektu ještě není známa jejich hodnota. Pokud je zavolána
metoda get nad NotNull delegate, je vyhozena výjimka informující o přístupu
k neinicializované vlastnosti. Map delegate umožňuje delegovat hodnotu proměnné
na určenou instanci třídy Map, respektive MutableMap pro variabilní proměnné.
Page 24
16
V této mapě musí být hodnota uložena pod klíčem, který odpovídá názvu vlastnosti
[9].
class DelegatesExample(map: MutableMap<String, String>) {
val lazyProperty: ExpensiveComputation by lazy {
ExpensiveComputation()
}
var observedProperty: String by Delegates.observable("initial",
{property, old, new -> saveToDB(property, new)}
)
var vetoableProperty: Int by Delegates.vetoable(3, {
property, old, new ->
if (new >= 0) {
saveToDB(property, new)
return@vetoable true
}
return@vetoable false
})
var notNullProperty: String by Delegates.notNull<String>()
var mapProperty: String by map
}
2.3 Standardní knihovna
Standardní knihovna Kotlinu obsahuje třídy, funkce a extension funkce pro třídy ze
standardní knihovny Javy, pro usnadnění a zefektivnění práce s Kotlinem. V této
kapitole budou popsány kolekce a jejich použití v Kotlinu. A také zde budou uvedeny
některé z velkého množství obecných pomocných funkcí a extension funkcí ve
standardní knihovně Kotlinu, které zjednodušují použití často používaných vzorů.
V této kapitole bude ukázáno použití několika z nich.
2.3.1 Kolekce
Podle webových stránek Kotlin Programming language, jsou v Kotlinu použity
standardní Java kolekce nacházející se v balíčku java.util, ale používá na ně 2
základní náhledy, měnné (mutable) a neměnné (immutable). Zároveň k těmto
kolekcím přidává řadu funkcí pro lepší práci s daty v kolekcích, mnoho těchto funkcí
používá funkcionální přístup k programování a mají obdobu v Groovy nebo ve
Streaming API přidaném v Javě 8. Více informací o Streaming API je na webové
stránce Processing Data with Java SE 8 Streams [10].
Page 25
17
Kotlin také přidává metody pro intuitivnější vytváření a práci s kolekcemi.
Například přístup k prvkům listu nebo i mapy je stejný jako přístup k prvkům pole
v Javě [11].
val immutableMap = mapOf(
"Key" to "Value",
"SecondKey" to "SecondValue"
)
immutableMap["Key"] == "Value"
// immutableMap["SecondKey"] = "DifferentValue"
val mutableMap = mutableMapOf("Key" to "Value")
mutableMap["Key"] = "DifferentValue"
mutableMap += "ThirdKey" to "ThirdValue"
val list = listOf(1)
val arrayList = arrayListOf(0)
val listCreatedByConstructor = ArrayList<Int>()
val mutableList = mutableListOf(0)
list[0] == 1
val newList = list + arrayList
newList !== list && newList !== arrayList
newList == listOf(1, 0)
mutableList[0] = 1
mutableList += 2
mutableList == mutableListOf(1, 2)
val set = setOf(0)
val hashSet = hashSetOf(0)
val mutable: MutableList<Int> = mutableListOf(1, 2)
var immutable: List<Int> = mutable
mutable += 3
mutable == mutableListOf(1, 2, 3)
mutable.add(4)
// immutable.add(5)
// val otherMutable: MutableList<Int> = immutable
V předchozím příkladu je vidět, jak lze vytvářet kolekce a nějaké základní operace
s prvky kolekcí, včetně několika příkladů přepisování operátorů. Příklad zároveň
ukazuje, že immutable kolekce je jen pohled na kolekci skrze interface, který
neobsahuje funkce pro modifikaci kolekcí. Ale přesto je možné do nich stále přidávat
prvky, pokud k nim lze přistupovat i přes mutable pohled. Zároveň je zde ukázáno,
že na mutable kolekci lze pohlížet přes immutable interface, ale naopak to nelze,
protože by pak bylo jednoduché pouze uložit immutable kolekci do mutable
proměnné a upravovat ji, a tím by se popřel celý smysl existence immutable kolekce.
Page 26
18
val list = (1..5).toList()
val listWithEvenNumbers = list.filter { it % 2 == 0 }
listWithEvenNumbers == listOf(2, 4)
val grouped = list.groupBy { if (it % 2 == 0) "even" else "odd" }
grouped == mapOf(
"even" to listOf(2, 4),
"odd" to listOf(1, 3, 5)
)
val doubledList = list.map { it * 2 }
doubledList == listOf(2, 4, 6, 8, 10)
V tomto příkladu je vidět použití několika metod, které umožňu jí snadnou
transformaci kolekcí dle různých charakteristik jejich prvků.
2.3.2 Obecné funkce
V standardní knihovně je velké množství obecných pomocných funkcí a extension
funkcí, které zjednodušují použití často používaných vzorů. V této kapitole bude
ukázáno použití několika z nich.
Funkce apply, let, run, with a use
Funkce apply, let a run jsou extension funkce objektu Any, tedy všech objektů,
funkce apply po vykonání daného bloku vrací instanci, na které byla zavolána
(reciever) a daný blok je definován jako extension funkce recieveru, takže příkazy
v bloku jsou vykonány v rámci objektu reciever. Není tedy nutné specifikovat název
proměnné pro volání funkce objektu, stejně jako uvnitř všech extension funkcí.
Funkce let předává reciever jako parametr do daného bloku a vrací výsledek bloku.
Lze ji použít zejména pro omezení platnosti proměnné.
Funkce run je velmi podobná funkci let s tím rozdílem, že blok je v tomto případě
extension funkcí recieveru, takže do něj není předáván jako parametr, ale kód bloku
je vykonán v jeho rámci.
Funkce with je top-level funkce, která mění rámec uvnitř bloku na objekt, který
přijme jako parametr, je tedy obdobou funkce run, jen je možné objekt specifikovat
parametrem, místo voláním run nad daným objektem.
Funkce use je extension funkce objektů implementujících rozhraní Closeable a má
podobnou funkci jako try with resources z Javy 7. Objekt je předán jako parametr
a po dokončení bloku je nad ním zavolána metoda close.
Page 27
19
fun useDelegateExtensions(nullableConnection: DbConnectionProvider) {
val panel = JPanel().apply {
add(JScrollPane().apply {
add(JLabel("Fill content:"))
add(JTextArea().apply {
lineWrap = true
wrapStyleWord = true
columns = 20
rows = 5
})
})
}
nullableConnection.createConnection()?.let { conn ->
conn.beginTransaction()
conn.changeVersion()
conn.createTable("TEST")
conn.commitTransaction()
}
nullableConnection.createConnection()?.run {
beginTransaction()
changeVersion()
createTable("TEST2")
commitTransaction()
}
with(panel) {
layout = BorderLayout()
border = BorderFactory.createEtchedBorder()
background = Color.GREEN
isOpaque = true
}
File(".").bufferedReader().use {
var counter = 0
it.forEachLine {
counter++
}
println(counter)
}
}
V předchozím příkladu jsou ukázky použití metod apply, let, run, with a use.
Funkci apply je možné použit pro celkem přehledné skládání objektů a nastavení
jejich vlastností.
Funkce let je zde použita pro omezení rámce vytvořené connection, ve kterém lze
k této proměnné přistupovat. Dalším přínosem je ošetření případu, kdy funkce
createConnection vrátí null, místo vyhození NullPointerException daný blok
nebude proveden.
Funkce run je použita stejně jako funkce let, jen s rozdílem, že není nutné funkce
volat na proměnné conn.
Funkce with je užitečná zejména v případě, kdy je potřeba nastavit více hodnot na
jednom objektu, takže není nutné pokaždé specifikovat proměnnou.
Page 28
20
A funkce use ukazuje jak spočítat řádky v souboru s automatickým zavřením
readeru.
Funkce pro práci s konzolí
Kotlin obsahuje několik top-level funkcí, které zkracují zápis práce s konzolí. Tyto
metody jsou: print, println a readLine, které delegují práci na System.out resp.
System.in – obalený do BufferedReaderu, aby bylo možné číst řádek místo znaku.
Funkce print a println jsou přetíženy stejně jako metody v System.out.
Funkce lazy a lazyOf
Funkce lazy a lazyOf převádějí inicializační blok na instanci třídy Lazy, která
umožňuje inicializovat objekt až při prvním použití. V Kotlinu lze použít několik
typů lazy, defaultní používá synchronizaci pro zajištění, správně inicializace i ve
vícevláknovém prostředí. Další možností je publication, používá atomickou
referenci pro uložení hodnoty, ale přístup není synchronizován, při vícevláknové
inicializaci, bude použita první vrácená hodnota. Poslední možností je none, která
neobsahuje ani synchronizaci, ani atomický přístup, není tedy bezpečné ji používat
ve vícevláknovém prostředí.
Funkce lazyOf vytvoří z dané instance už inicializovanou instanci Lazy.
fun lazyFunctions(): Unit {
val noneLazy = lazy(LazyThreadSafetyMode.NONE) {
DbConnectionProvider().createConnection()
}
val publicationLazy = lazy(LazyThreadSafetyMode.PUBLICATION) {
DbConnectionProvider().createConnection()
}
val syncLazy = lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
DbConnectionProvider().createConnection()
}
val initializedLazy = lazyOf(DbConnectionProvider()
.createConnection())
}
Funkce synchronized a zámky
Funkce synchronized nahrazuje synchronized konstrukt z Javy a má stejnou
syntaxi a funkci jako její obdoba z Javy, včetně zobrazení zámků v thread-dumpu. Do
tříd zámků v Javě bylo přidáno několik extension funkcí, které usnadňují jejich
správu. Tyto funkce jsou withLock pro ReentrantLock a read, write pro
Page 29
21
ReeantrantReadWriteLock, tyto funkce získají daný zámek, potom provedou daný
blok a zámek zase odemknou.
fun synchronization() {
synchronized(Any()) {
println("Print guarded by synchronized")
}
}
fun locks() {
val lock = ReentrantLock()
lock.withLock {
println("Print guarded by reentrant lock")
}
val rwlock = ReentrantReadWriteLock()
rwlock.read {
val value = someSharedProperty
save(value)
}
rwlock.write {
someSharedProperty = "new value"
}
}
Funkce pro práci s třídou File
Kotlin přidává mnoho extension funkcí pro práci s třídou File. Tyto funkce
usnadňují práci s obsahem souboru i s procházením adresářové struktury.
V následujícím příkladu je vidět několik funkcí, které jsou přidány.
fun fileFunctions() {
val file = File("")
file.bufferedReader()
file.bufferedWriter()
file.inputStream()
file.outputStream()
file.useLines { println("Number of Lines: ${it.count()}") }
file.forEachLine { println(it) }
val linesList = file.readLines()
file.deleteRecursively()
file.copyRecursively(File("target"))
file.walkTopDown().forEach {
println(it.absolutePath)
}
}
Funkce bufferedReader, bufferedWriter, inputStream a outputStream vytvářejí
instance odpovídajících objektů, které jsou napojeny na daný File. Funkce useLines
umožňuje definovat blok, do kterého je jako parametr předána Sequence obsahující
řádky souboru. Funkce forEachLine umožňuje definovat akci s každým řádkem
souboru. Funkce readLines vrací List obsahující všechny řádky souboru.
Page 30
22
Z funkcí, které pracují se soubory, nikoliv s jejich obsahem jsou v příkladu uvedeny
tyto:
deleteRecursively - rekurzivně maže adresářovou strukturu od daného
adresáře.
copyRecursively - rekurzivně kopíruje daný adresář do daného cílového
adresáře.
walkTopDown - vrací objekt Sequence, který prochází adresáře a soubory
pomocí algoritmu prohledávání do hloubky.
Funkce measureTimeMillis a measureNanoTime
Funkce measureTimeMillis a measureNanoTime umožňují vytvářet velmi
jednoduché benchmarky, pomocí těchto funkcí je možné změřit, kolik času trvalo
vykonání předaného bloku s přesností na milisekundy respektive nanosekundy.
fun benchmarking() {
val millis = measureTimeMillis {
doSomeComputation()
}
val nanos = measureNanoTime {
doSomeComputation()
}
}
2.4 Dekompilace do Javy
Obsahem této kapitoly je popis a ukázky toho, jak vypadá Kotlin po dekompilaci do
Javy, bude tedy vidět, jak je dosaženo dané funkcionality v Kotlinu.
2.4.1 Top level funkce
Top level funkce jsou funkce, které nejsou součástí žádné třídy a jsou definovány
přímo v souboru .kt. Nahrazují tak nutnost vytváření Utils tříd s množinou
statických metod.
Page 31
23
Následující kód ukazuje, jak vypadá zápis top level funkce a top level extension
funkce a jejich použití v Kotlinu.
fun someTopLevelFunction(first: Int, second: Int): Boolean {
return false
}
fun String.extensionFunction(first: Int): Boolean {
return length > first
}
fun useFunctions() {
val topLevel = someTopLevelFunction(1, 2)
val extension = "someString".extensionFunction(3)
}
A po dekompilaci do Javy:
public final class FunctionsKt
{
public static final boolean someTopLevelFunction(
int first, int second)
{
return false;
}
public static final boolean extensionFunction(
String $receiver, int first)
{
Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
return $receiver.length() > first;
}
public static final void useFunctions()
{
boolean topLevel = someTopLevelFunction(1, 2);
boolean extension = extensionFunction("someString", 3);
}
}
V uvedeném kódu je vidět, že top level funkce jsou kompilovány do vygenerované
třídy, která přebírá název od .kt souboru a dané funkce jsou zkompilovány na
statické metody. Také je vidět, jak fungují extension funkce. Objekt, který rozšiřují
je předán jako první parametr dané funkce s názvem $reciever a na tom jsou
volány metody, případně přístup k veřejným atributům třídy.
2.4.2 Lambda výrazy
Lambda výrazy byly přidány do Javy až ve verzi 1.8, přesto je možné je v Kotlinu
používat i s použitím Javy 1.6.
Page 32
24
V následujícím příkladu bude vidět jak je to možné.
fun lambdas(): Unit {
val concatTwice = { param: String -> param + param }
val stringstring = useLambda(concatTwice, "string")
}
fun useLambda(lambda: (String) -> String, param: String)
= lambda(param)
po dekompilaci: (výpis byl upraven, kvůli přehlednosti a stručnosti)
public final class LambdaKt {
public static final void lambdas() {
Function1 concatTwice =
(Function1) lambdas.concatTwice._cls1.INSTANCE;
String stringstring = useLambda(concatTwice, "string");
}
public static final String useLambda(Function1 lambda,
String param) {
Intrinsics.checkParameterIsNotNull(lambda, "lambda");
Intrinsics.checkParameterIsNotNull(param, "param");
return (String) lambda.invoke(param);
}
public static final class lambdas.concatTwice._cls1
extends Lambda implements Function1 {
public volatile Object invoke(Object obj) {
return invoke((String) obj);
}
public final String invoke(String param) {
Intrinsics.checkParameterIsNotNull(param, "param");
return (new StringBuilder())
.append(param).append(param).toString();
}
public static final lambdas.concatTwice._cls1 INSTANCE
= new lambdas.concatTwice._cls1();
}
}
Z předchozího výpisu je vidět, že lambda výraz je zkompilován jako třída, která dědí
od třídy Lambda a implementuje interface Function1, které reprezentuje funkci
s jedním parametrem a jednou metodou invoke, která v tomto případě nemá žádné
generické parametry, takže typ parametru i návratové hodnoty je Object. Třída
obsahuje i druhou metodu invoke, která obsahuje tělo lambda výrazu a je volána při
volání lambda výrazu prostřednictvím metody invoke z interface Function1, která
převádí parametry na odpovídající typy a volá danou implementaci lambda výrazu.
Page 33
25
2.4.3 Přiřazení konstruktu pro řízení běhu programu
V následujícím příkladu bude vidět, jak funguje přiřazení hodnoty proměnné na
základě vyhodnocení if else podmínky nebo when přepínače.
fun statementAssignment(input: String, intInput: Int) {
val value = if (input.length > 5) "long" else "short"
val complex = if (input.length > 5) {
shortenAndLog(input)
} else {
log(input)
input
}
val whenStatement = when(intInput) {
2 -> "two"
3 -> "three"
5 -> "five"
else -> "unknown"
}
save(value)
save(complex)
save(whenStatement)
}
Takto vypadá kód po dekompilaci:
public static final void statementAssignment(
String input, int intInput) {
Intrinsics.checkParameterIsNotNull(input, "input");
String value = input.length() <= 5 ? "short" : "long";
String complex;
if(input.length() > 5) {
complex = shortenAndLog(input);
} else {
log(input);
complex = input;
}
String whenStatement;
String s;
switch(intInput) {
case 2: // '\002'
s = "two";
break;
case 3: // '\003'
s = "three";
break;
case 5: // '\005'
s = "five";
break;
case 4: // '\004'
default:
s = "unknown";
break;
}
whenStatement = s;
save(value);
save(complex);
save(whenStatement);
}
Page 34
26
Jak je vidět, jedná se o jednoduché převedení s malou výjimkou v podobě nahrazení
jednoduchého if-u ternárním operátorem. V ostatních případech se definuje
proměnná před konstruktem a uvnitř podmínky se naplní danou hodnotou.
V případě when se ještě použije další pomocná proměnná.
2.4.4 Defaultní hodnoty parametrů
V Kotlinu je možné pro parametry konstruktorů a funkcí definovat defaultní
hodnoty, takže pak tyto parametry není nutné specifikovat při volání dané funkce
nebo konstruktoru. V následujícím příkladu je ukázáno, jak je toho dosaženo.
fun useDefaultParams() {
tryConnection("jdbc:hsqldb:file:embeddeddb")
tryConnection("jdbc:hsqldb:file:embeddeddb", 300)
tryConnection("jdbc:hsqldb:file:embeddeddb",
useTransactions = false)
tryConnection("jdbc:hsqldb:file:embeddeddb", 300, false)
}
fun tryConnection(url: String,
timeout: Int = 120,
useTransactions: Boolean = true) {
connectToDB(url, timeout, useTransactions)
}
Při použití defaultních hodnot je vytvořena nová metoda, která přidává další
parametry. Jeden určuje, které hodnoty mají být nastaveny na defaultní hodnotu
a druhý slouží pro ověření, zda metoda není volána z potomka bez specifikace všech
hodnot pomocí identifikátoru super.
Page 35
27
Po dekompilaci:
public static final void useDefaultParams()
{
defaultParameters$default(
"jdbc:hsql://embeddeddb", 0, false, 6, null);
defaultParameters$default(
"jdbc:hsql://embeddeddb", 300, false, 4, null);
defaultParameters$default(
"jdbc:hsql://embeddeddb", 0, false, 2, null);
defaultParameters("jdbc:hsql://embeddeddb", 300, false);
}
public static final void defaultParameters(
String url, int timeout, boolean useTransactions)
{
Intrinsics.checkParameterIsNotNull(url, "url");
connectToDB(url, timeout, useTransactions);
}
public static volatile void defaultParameters$default(
String s, int i, boolean flag, int j, Object obj)
{
if(obj != null)
throw new UnsupportedOperationException(
"Super calls with default arguments not supported
in this target, function: defaultParameters");
if((j & 2) != 0)
i = 120;
if((j & 4) != 0)
flag = true;
defaultParameters(s, i, flag);
}
Page 36
28
3 Aplikace pro podporu dne otevřených dveří
Aplikace pro podporu dne otevřených dveří má 2 základní části: mobilní aplikaci
a serverovou část pro uložení informací. Obě části jsou psané v programovacím
jazyku Kotlin popsaném ve 2. kapitole.
Mobilní aplikace obsahuje funkce pro podporu účastníka dne otevřených dveří. Je
možné si zobrazit informace o škole, fakultě a o dni otevřených dveří. Také
poskytuje možnost přejít na mapu s polohou fakulty informatiky a managementu
pomocí Google Maps, takže lze zapnout navigaci k fakultě. Dále aplikace nabízí
sestavení plánu dne otevřených dveří s upozorňováním na naplánované akce.
3.1 Model
Obě části aplikace mají dva základní prvky, které jsou stejné. Část umožňující práci
s informacemi o dni otevřených dveří, fakultě a univerzitě a část určenou pro práci
s událostmi dne otevřených dveří.
3.1.1 Serverová část
Serverová část slouží pro ukládání a obsluhu informací, které jsou zobrazeny
v mobilní aplikaci. Jedná se o webovou aplikaci, obsahující RESTful API, jednoduché
webové rozhraní a databázovou část. Pomocí webového rozhraní lze pracovat
s informacemi uloženými v databázové části.
Metody měnící data jsou zabezpečeny autentizací pro zabránění neoprávněné
změně informací. Serverová část obsahuje přiloženou databázi i webový kontejner,
takže není nutné tyto části nastavovat zvlášť. Pro spuštění aplikace tedy stačí
nainstalovaná Java.
Serverová část má dvě základní části. Část obsahující informace a REST metody pro
práci s nimi a část určenou pro práci a ukládání událostí dne otevřených dveří.
3.1.2 Mobilní část
Mobilní část umožňuje uživateli čtení informací zadaných v serverové části. Včetně
cachování dat, takže informace jednou stažené jsou dostupné, i pokud se v dané
chvíli nelze připojit k serverové části. Dále se lze z aplikace přepnout do Google
Maps, kde bude zobrazena poloha Fakulty informatiky a managementu, s možností
Page 37
29
zapnutí navigace. Také je možné vybírat si ze seznamu nabízených akcí ty, které chce
daný uživatel navštívit a jejich export do Google Calendar.
Přihlášené události jsou ukládány do databáze s nastaveným časem. A pokud si je
uživatel odhlásí, jsou z databáze tyto záznamy odebrány, takže v databázi jsou vždy
uloženy jen přihlášené akce.
3.2 Implementace
V této kapitole bude uveden popis implementace obou části aplikace, včetně
použitých frameworků, stručného popisu jejich funkcí a popsané zdrojové kódy
aplikace.
Jak je uvedeno v úvodu 3. kapitoly, obě části jsou implementovány v jazyce Kotlin.
Pro správu závislostí serverové i mobilní části byl použit nástroj Gradle. A pro
správu závislostí ve webovém rozhraní serverové části byl použit balíčkovací
systém Node.js. Zdrojové kódy byly spravovány pomocí verzovacího systému
Mercurial.
3.2.1 Serverová část
Serverová část je centrální část, ke které se připojují všechny mobilní části
a získávají odtud data. Tato část využívá možnosti Spring frameworku, konkrétně
jeho části Spring Boot v kombinaci s ORM frameworkem Hibernate pro práci
s databází, včetně nastavené přiložené HSQLDB. Spring Framework také slouží pro
správnou inicializaci a nastavení přiloženého webového kontejneru Tomcat, včetně
správného namapování REST metod pro práci s uloženými daty. Pro vytvoření
webového rozhraní byl použit framework Bobril, umožňující vytváření rozhraní
pomocí TypeScriptu, s následným generováním HTML z virtuálního DOMu.
Spring Framework
Jak je uvedeno v Professional Java Development with the Spring Framework, Spring
Framework byl původně primárně určen pro podporu architektury MVC,
dependency injection v Javě [12], ale postupným vylepšováním se z něj stala
platforma, která podporuje širokou škálu funkcí, které výrazně ulehčují
Page 38
30
implementaci standardních vzorů, používaných v aplikacích různých velikostí, od
malých jednoúčelových aplikací, až po velké enterprise aplikace.
Funkce, které využívá aplikace vyvíjená v rámci této práce, jsou:
Spring Boot – automatická konfigurace používaných částí Spring
Frameworku.
Spring Data – práce s databází a daty v ní uloženými, včetně mapování
databázových tabulek na objekty, pomocí frameworku Hibernate
Spring Web – přiložený webový kontejner a konfigurace kontrolerů REST
API
Spring Security – zabezpečení REST API pomocí uživatelského jména a hesla
Spring Context – dependency injection potřebných částí aplikace
Spring Framework obsahuje mnoho dalších funkcí, které zde nebyly popsány,
protože nejsou součástí aplikace.
Hibernate
Jak je uvedeno v Hibernate in action, Hibernate je takzvaný ORM (object/relational
mapping) framework. Jehož hlavní funkcí je vyřešení problému různé interpretace
dat v relační databázi a objektově orientovaném programovacím jazyce. [13]
Umožňuje práci s databází prostřednictvím standardních Java objektů a jejich
převod do databázových řádků a zpět. Takže je možné odstínit aplikaci od práce
s připojením k databázi, SQL příkazy a transakcemi a jejich manuálním
zpracováním.
Bobril
Jak je psáno na Bobril – I – Getting Started, Bobril je komponentově orientovaný
Framework inspirovaný ReactJs a Mithril. Bobril je zaměřený na automatické
generování kódu s důrazem na rychlost a malou velikost [14].
Aplikace pro podporu dne otevřených dveří
V této části bude popsáno jak je vlastní serverová část implementována
s poznámkami k implementaci.
Page 39
31
Vstupním bodem Serverové části je soubor Application.kt obsahující main funkci,
která deleguje spuštění aplikace na třídu SpringApplicationBuilder
z frameworku Spring Boot, která odpovídá za automatickou konfiguraci Spring
a Hibernate frameworků. Dále je v tomto souboru definována třída Application
s anotací SpringBootApplication, podle které Spring Boot hledá konfigurační třídy.
Tato třída také implementuje rozhraní CommandLineRunner, které obsahuje metodu
run a slouží pro definici vlastní akce po nastartování a nakonfigurování aplikace.
V metodě run jsou do databáze vloženy nějaké základní testovací informace
a události. Tato třída obsahuje dvě privátní vlastnosti, do kterých jsou pomocí
dependency injection ve Spring frameworku vloženy odpovídající instance.
@SpringBootApplication
open class Application() : CommandLineRunner {
@Autowired
private lateinit var infoRepository: InfoRepository
@Autowired
private lateinit var eventRepository: EventRepository
override fun run(vararg args: String) {
infoRepository.save(Info("FIM", "Some info about FIM"))
val sdf = SimpleDateFormat("dd.MM.yyyy HH:mm")
eventRepository.save(Event(-1, "First",
sdf.parse("24.06.2016 08:00"),
sdf.parse("24.06.2016 08:30"),
"Description first", "J17"))
eventRepository.save(Event(-1, "Second",
sdf.parse("24.06.2016 08:15"),
sdf.parse("24.06.2016 08:45"),
"Description second", "J16"))
eventRepository.save(Event(-1, "Third",
sdf.parse("24.06.2016 09:00"),
sdf.parse("24.06.2016 09:30"),
"Description third", "J17"))
eventRepository.save(Event(-1, "Fourth",
sdf.parse("24.06.2016 09:15"),
sdf.parse("24.06.2016 09:45"),
"Description fourth", "J16"))
}
}
fun main(args: Array<String>) {
SpringApplicationBuilder(Application::class.java)
.registerShutdownHook(true)
.build()
.run(*args)
}
Další důležitou částí aplikace jsou třídy reprezentující informace a události.
Page 40
32
V serverové části jsou to třídy Info a Event. Obě třídy mají anotaci Entity, pomocí
které Spring a Hibernate poznají třídy určené pro mapování do relační databáze.
Třída Info je jednoduchá data třída, která obsahuje 2 vlastnosti: name a value, kde
name obsahuje jméno popisující konkrétní část informací a value samotné
informace. Obě vlastnosti jsou anotovány pomocí anotací, které upřesňují, jak bude
vypadat při uložení do databáze. Anotace Id určuje primární klíč.
@Entity
data class Info(
@Id
@Column(name = "INFO_NAME")
var name: String = "",
@Column(name = "INFO_VALUE", length = Short.MAX_VALUE.toInt())
@Lob
var value: String = ""
)
Data třída Event má vlastnosti id, name, start, end, description a room, pro uložení
id v databázi, názvu, začátku, konce, popisku a místnosti, kde se událost koná.
Anotace GeneratedValue umožňuje automatické generování hodnoty ve sloupci.
@Entity
data class Event(
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "ID")
var id: Int = -1,
@Column(name = "NAME", unique = true)
var name: String = "",
@Column(name = "START")
var start: Date = Date(),
@Column(name = "END")
var end: Date = Date(),
@Lob
@Column(name = "DESCRIPTION", length = Short.MAX_VALUE.toInt())
var description: String = "",
@Column(name = "ROOM")
var room: String = ""
)
Další důležitou částí jsou repository, které pracují s informacemi v databázi
a umožňují ukládat, měnit a vyhledávat data. Jak je psáno v dokumentaci Spring
Data frameworku, cílem Spring Data je snížit množství nadbytečného kódu při
implementaci přístupu k databázi [15]. Framework umožňuje vytvářet repository
Page 41
33
pomocí rozhraní, kde odpovídající implementaci vygeneruje za běhu programu,
včetně rozpoznání funkce na základě pojmenování funkcí podle určitého vzoru.
Rozhraní InfoRepository je potomkem rozhraní CrudRepository. Které jak název
napovídá, definuje některé základní metody pro práci s ukládanými objekty.
Tyto metody jsou:
save – uloží objekt do databáze
findOne – vyhledá objekt podle zadaného primárního klíče
exists – vyhledá, jestli už je objekt uložen v databázi
findAll – vrátí všechny objekty
count – vrátí počet záznamů v databázi
delete – vymaže objekt
deleteAll – vymaže všechny objekty
Rozhraní InfoRepository tedy nemusí definovat žádné další funkce, protože vše
potřebné už je zděděno z CrudRepository.
Rozhraní EventRepository také dědí od CrudRepository, ale navíc definuje funkce
findByName a findByRoom, který v databázi vyhledá událost s daným názvem,
respektive události v dané místnosti.
interface InfoRepository : CrudRepository<Info, String>
interface EventRepository : CrudRepository<Event, Int> {
fun findByName(name: String): Event
fun findByRoom(room: String): List<Event>
}
Výše byly popsány třídy týkající se ukládání dat. V další části budou ukázány třídy
zajišťující práci REST API a jeho zabezpečení. Třída InfoController definuje REST
rozhraní k informacím. Tato třída má anotaci RestController, podle které Spring
rozpozná, že v této třídě budou definovány funkce, které budou obsluhovat http
requesty jim určené. Jsou zde definovány dvě vlastnosti, infoRepository
a infoTypes. Do vlastnosti infoRepository bude pomocí dependency injection
vložena instance třídy implementující rozhraní InfoRepository, tedy instance
vygenerované třídy implementující toto rozhraní. Definované funkce mají anotace
Page 42
34
RequestMapping, které definují vlastnosti http requestů, které zpracovávají.
Zejména tedy adresu, http metodu a případně i typ těla requestu. Parametry těchto
metod mají anotace RequestParam, respektive RequestBody, podle kterých Spring
rozpozná, kterou část requestu má předat do parametru. Z uvedeného kódu lze také
vidět, že Spring provádí automatickou konverzi mezi objektem Info a jeho
reprezentací pomocí JSONu.
@RestController
class InfoController {
@Autowired
private lateinit var infoRepository: InfoRepository
private val infoTypes = arrayOf("OpenDays", "FIM", "UHK")
@RequestMapping(value = "/info",
method = arrayOf(RequestMethod.GET))
fun getInfo(@RequestParam(name = "name", required = true)
name: String) =
infoRepository.findOne(name)
?: Info(name, "info not found")
@RequestMapping(value = "/info",
method = arrayOf(RequestMethod.PUT),
consumes = arrayOf("application/json"))
fun putInfo(@RequestBody info: Info) {
infoRepository.save(info)
}
@RequestMapping(value = "/info/types",
method = arrayOf(RequestMethod.GET))
fun getInfoTypes() = infoTypes
}
Page 43
35
Třída EventController definuje funkce obsluhující zpracování requestů, které
pracují s událostmi dne otevřených dveří a má následující kód.
@RestController
class EventController {
@Autowired
private lateinit var eventRepository: EventRepository
@RequestMapping(value = "/event",
method = arrayOf(RequestMethod.GET))
fun getEvents(): List<Event> {
return eventRepository.findAll().toList()
}
@RequestMapping(value = "/event",
method = arrayOf(RequestMethod.PUT),
consumes = arrayOf("application/json"))
fun putEvent(@RequestBody event: Event) {
eventRepository.save(event)
}
@RequestMapping(value = "/event",
method = arrayOf(RequestMethod.DELETE))
fun deleteEvent(
@RequestParam(value = "id", required = true)
id: Int) {
eventRepository.delete(id)
}
}
Zabezpečení REST API je implementováno pomocí Spring Security frameworku,
jehož nastavení je ve třídě SecurityConfig. V této třídě je definováno, které http
requesty budou chráněny před neoprávněným přístupem. Toto je definováno
pomocí autentizace uživatelským jménem a heslem. Requesty, které budou
zabezpečené, jsou definovány pomocí volání metody antMatchers, která umožňuje
specifikovat http metodu a adresu, která bude požadována autentizace.
Page 44
36
@Configuration
@EnableWebSecurity
open class SecurityConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http
.authorizeRequests()
.antMatchers(HttpMethod.PUT, "/info")
.fullyAuthenticated()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.PUT, "/event")
.fullyAuthenticated()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.DELETE, "/event")
.fullyAuthenticated()
.and()
.authorizeRequests()
.anyRequest()
.permitAll()
.and()
.httpBasic()
.and()
.csrf().disable()
}
override fun configure(auth: AuthenticationManagerBuilder) {
auth.inMemoryAuthentication()
.withUser("**********")
.roles("***********")
.password("***********")
}
}
Obr. 1 Stránka pro úpravu informací.
Zdroj: vlastní zpracování
Page 45
37
Obr. 2 Stránka pro úpravu událostí.
Zdroj: vlastní zpracování
3.2.2 Mobilní část
Mobilní část je aplikace určená pro mobilní zařízení se systémem Android. Pro její
implementaci bylo použito standardní SDK Androidu, konkrétně API verze 17.
Aplikace je tedy kompatibilní se zřízeními se systémem Android 4.2 a vyšší. Jak bylo
uvedeno na začátku kapitoly 3, samotná aplikace je naprogramována v jazyce Kotlin.
Pro připojení k REST API serverové části byla využita mobilní verze knihovny Spring
android rest template, která pomocí jednoduchého rozhraní zprostředkovává volání
REST API, včetně převodu z formátu JSON na objekty.
Android SDK
Jak je popsáno v dokumentaci k Android SDK, Základem Android aplikací jsou
takzvané Aktivity, které představují jedno okno, se kterým může uživatel pracovat
a vykonat tak nějakou akci, například volat, fotit, poslat email nebo zobrazit mapu.
Okno obvykle vyplňuje celou obrazovku, ale může být i menší a být zobrazeno nad
ostatními okny. [16]
Page 46
38
Aktivita se skládá z třídy, která je potomkem třídy Activity a View, které
reprezentuje viditelnou část aktivity. View je obvykle implementováno pomocí xml,
které obsahuje komponenty, na které je možné se odkazovat pomocí id
a přistupovat k nim tak ze třídy, ve které je implementována logika s nimi
související.
Aktivity mají životní cyklus a různé akce mohou být prováděny v různých cyklech.
Metody životního cyklu jsou volány takto:
onCreate – při vytváření aktivity
onStart – před zobrazením
onResume – po zobrazení (i po návratu z jiné aktivity)
onPause – před přepnutím na jinou aktivitu
onStop – po skrytí aktivity
onDestroy – před ukončením aktivity
Více informací o životním cyklu lze najít v dokumentaci k Android SDK [16]
Mobilní část
Vstupním bodem mobilní části je třída MainActivity, která reprezentuje rozcestník,
pomocí kterého lze zobrazovat ostatní části aplikace. Tato aktivita obsahuje tlačítka
pro přechod na ostatní aktivity. Je zde využito několika funkcí jazyka Kotlin,
například lazy delegate, pomocí kterého je definována inicializace atributů danými
tlačítky, které jsou dostupné až po inicializaci View ve funkci onCreate a není tak
nutné jejich inicializaci provádět přímo v této metodě po inicializaci View a snižovat
tak přehlednost kódu.
Druhou funkcí je použití lambda výrazů pro definici akcí po kliknutí na dané tlačítko.
V této funkci jsou také volány funkce createDbHelpers a cacheInfoAndEvents.
Funkce createDbHelpers vytváří instance pomocných tříd pro práci s databází
a registruje je v objektu DbHelpers, aby byly instance dostupné z celé aplikace
a nemusely se vytvářet při každém použití. Funkce cacheInfoAndEvents se
v případě dostupného internetového připojení pokusí připojit k serveru, odkud
stáhne a uloží do cache informace a dostupné události. Ukázka kódu této třídy je
Page 47
39
zkrácena o části společné nebo podobné pro ostatní prvky, například tlačítka nebo
třídy starající se o cachování dat.
class MainActivity : Activity() {
private val btnShowMap by lazy {
findViewById(R.id.btnShowMap) as Button
}
…
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnShowMap.setOnClickListener {
val fimMapQuery = Uri.parse(
"geo:0,0?q=Fakulta informatiky a managementu, " +
"Hradec Králové")
val mapIntent = Intent(Intent.ACTION_VIEW, fimMapQuery)
@Suppress("UsePropertyAccessSyntax")
mapIntent.setPackage("com.google.android.apps.maps")
startActivity(mapIntent)
}
…
createDbHelpers()
cacheInfoAndEvents()
}
private fun createDbHelpers() {
DbHelpers.register(EventsDbHelper::class,
EventsDbHelper(applicationContext))
…
}
private fun cacheInfoAndEvents() {
if (isNetworkAvailable(this@MainActivity)) {
GetFromServerAndCacheTask("$SERVER_URL/event",
DbHelpers.get(EventsCacheHelper::class),
Array<Event>::class.java).execute()
…
} else {
Toast.makeText(this@MainActivity,
"Cannot connect to server.",
Toast.LENGTH_SHORT)
}
}
…
}
Objekt DbHelpers použitý v hlavní aktivitě slouží jako cache pomocných tříd pro
práci s databází. Tento objekt obsahuje jeden atribut typu ConcurrentHashMap, ve
které budou uloženy registrované třídy. K tomuto atributu bude přistupováno
z různých vláken, takže pro zajištění správného chování je použita thread-safe
implementace mapy. Jako klíč je použit KClass objekt reprezentující vkládaný
Page 48
40
helper. V objektu DbHelpers jsou definovány funkce get a register. Funkce get má
jeden parametr typu KClass, pomocí kterého je vyhledán odpovídající helper
v mapě. Také je díky tomu možné provést přetypování na odpovídající typ helper
třídy, takže už není nutné používat explicitní přetypování při použití funkce get.
Funkce register přebírá dva parametry, objekt třídy helperu a samotný helper.
Tato funkce používá pro uložení helperu do mapy metodu putIfAbsent, která
zajišťuje atomickou operaci vložení do mapy, pokud už tam daný objekt není.
V mapě tedy bude vždy jen první zaregistrovaný helper pro daný KClass objekt.
object DbHelpers {
private val dbHelpers =
ConcurrentHashMap<KClass<SQLiteOpenHelper>,
SQLiteOpenHelper>()
fun <T: SQLiteOpenHelper> get(clazz: KClass<in T>): T {
val helper = dbHelpers[clazz]
?: throw IllegalStateException(
"DbHelper with class: '$clazz' was not registered yet")
return helper as T
}
fun <T: SQLiteOpenHelper> register(clazz: KClass<in T>,
helper: T) {
dbHelpers.putIfAbsent(
clazz as KClass<SQLiteOpenHelper>,
helper as SQLiteOpenHelper)
}
}
Další třídou používanou v hlavní aktivitě je GetFromServerAndCacheTask. Tato třída
je potomkem třídy AsyncTaskWithListeners, která umožňuje provést operaci
v novém vlákně, aby nebylo blokováno hlavní event vlákno reagující na události od
uživatele. Tato třída má za úkol pokusit se stáhnout informace ze serveru a uložit je
do databáze. Jako parametry v konstruktoru je přebírána url serveru, cacheHelper
a třída objektu, který se bude cachovat. Pro získání dat ze serveru je použita třída
RestTemplate ze Spring Frameworku, starající se o vytvoření requestu na server
a převedení response na požadovaný objekt.
Page 49
41
class GetFromServerAndCacheTask<T>(val url: String,
val cacheHelper: CacheHelper<T>,
val type: Class<T>)
: AsyncTaskWithListeners<String, Unit, Unit>() {
override fun doInBackground(vararg params: String) {
val itemName = if (params.size > 0) params[0] else null
getFromServer(itemName)?.let {
cacheHelper.updateCache(it)
}
}
private fun getFromServer(name: String?): T? {
try {
RestTemplate(true).run {
(requestFactory as SimpleClientHttpRequestFactory).run {
setConnectTimeout(10 * 1000)
setReadTimeout(10 * 1000)
}
return getForObject(url, type, name)
}
} catch (e: Exception) {
Log.w("DownloadFromServer",
"Unable to download from server.", e)
return null
}
}
}
CacheHelper je rozhraní definující dvě funkce - getFromCache a updateCache, které
implementují třídy DbHelperů pro konkrétní objekty.
Abstraktní třída AsyncTaskWithListeners je potomkem třídy AsyncTask z Android
SDK, která umožňuje přidávat callbacky před začátkem zpracování akce v jiném
vlákně a po skončení vlákna, včetně práce s výsledkem zpracování. Umožňuje tak
efektivně využívat podporu lambda výrazů v Kotlinu. Kolekce, které obsahují
registrované akce, jsou instance třídy CopyOnWriteArrayList kvůli zachování
správného chování při přístupu z různých vláken. V ukázce kódu nejsou funkce
přidávající a odebírající akce z listů.
Page 50
42
abstract class AsyncTaskWithListeners<Params, Progress, Result>
: AsyncTask<Params, Progress, Result>() {
private val preExecuteActions: MutableList<() -> Unit> =
CopyOnWriteArrayList<() -> Unit>()
private val postExecuteActions: MutableList<(Result?) -> Unit> =
CopyOnWriteArrayList<(Result?) -> Unit>()
override fun onPreExecute() {
super.onPreExecute()
for (preExecuteAction in preExecuteActions) {
preExecuteAction()
}
}
override fun onPostExecute(result: Result?) {
super.onPostExecute(result)
for (postExecuteAction in postExecuteActions) {
postExecuteAction(result)
}
}
}
EventsDbHelper je jednou z tříd zajišťující komunikaci s databází. Tato třída dědí od
třídy SQLiteOpenHelper z Android SDK, která obsahuje metody pro usnadnění
práce s databází v Androidu. Třída EventsDbHelper obsahuje funkce, které souvisejí
s ukládáním, vyhledáváním a mazáním přihlášených událostí. Pro komunikaci
s databází je použit objekt SQLiteDatabase, který lze získat pomocí volání metod
getReadableDatabase, respektive getWritableDatabase, podle toho, zda bude
z databáze čteno, respektive do databáze zapisováno. Objekt SQLiteDatabase
obsahuje metody, umožňující provádět dotazy a příkazy do databáze. V ukázce kódu
je část třídy, která vytváří a upgraduje databázi pomocí delegování na metody, které
vytvářejí a mažou tabulky pomocí standardních SQL příkazů.
class EventsDbHelper(val context: Context) :
SQLiteOpenHelper(context, "OpenDaysFim.db", null, 1) {
override fun onCreate(db: SQLiteDatabase) {
createTables(db)
}
override fun onUpgrade(db: SQLiteDatabase,
oldVersion: Int,
newVersion: Int) {
dropCreateTables(db)
}
…
Page 51
43
Funkce findAll vrací všechny uživatelem přihlášené události uložené v databázi,
využívá pro to funkci query, která je vytvořena jako extension funkce a volá metodu
query ze třídy SQLiteDatabase s nahrazením nepotřebných parametrů za null.
Tato funkce vrací objekt Cursor, který je obdobou ResultSetu z Javy.
fun findAll() = readableDatabase.use {
with(EventConstants) {
it.query(
TableConstants.EVENTS,
getColumns()
).use { cursor ->
CursorIterable(cursor) {
Event.fromCursor(cursor)
}.toList()
}
}
}
Pro lepší práci s kurzorem a možnost použití metod určených pro práci s kolekcemi
v Kotlinu, je použita třída CursorIterable, do které je kurzor předán. Druhým
parametrem konstruktoru CursorIterable je lambda výraz, který převádí jeden
záznam v kurzoru na objekt. V tomto případě je použita funkce fromCursor
companion objektu třídy Event, která vrací instanci této třídy.
internal class CursorIterable<T>(val cursor: Cursor,
val transformer: (Cursor) -> T)
: Iterable<T> {
override fun iterator(): Iterator<T> =
CursorIterator(cursor, transformer)
}
internal class CursorIterator<T>(val cursor: Cursor,
val transformer: (Cursor) -> T)
: Iterator<T> {
override fun hasNext(): Boolean = !cursor.isLast
&& !cursor.isAfterLast
override fun next(): T {
cursor.moveToNext()
return transformer(cursor)
}
}
Třída Event slouží pro uložení informací o události a je obdobou třídy Event ze
serverové části, ale má navíc vlastnosti selectedStart a selectedEnd, do kterých
se ukládá, v jaký čas se reálně chce uživatel události zúčastnit. A má navíc companion
objekt s funkcí fromCursor.
Page 52
44
companion object {
fun fromCursor(cursor: Cursor): Event {
val event = Event(cursor.getInt(0),
cursor.getString(1),
Date(cursor.getLong(2)),
Date(cursor.getLong(3)),
cursor.getString(4),
cursor.getString(5))
event.selectedStart = Date(cursor.getLong(6))
event.selectedEnd = Date(cursor.getLong(7))
return event
}
}
V třídě EventsDbHelper jsou ještě funkce save a delete, které ukládají, respektive
mažou předaný objekt Event z databáze. Ukládání používá metodu
insertWithConflict, která umožňuje přepisování existujících záznamů i vkládání
nových. Metoda with je v tomto případě použita pro zkrácení zápisu přístupu ke
konstantám obsahujícím názvy sloupců v tabulce událostí.
fun save(event: Event) {
with(EventConstants) {
writableDatabase.use {
it.insertWithOnConflict(TableConstants.EVENTS,
null,
ContentValues().apply {
put(COL_ID, event.id)
put(COL_NAME, "${event.name}")
…
}, SQLiteDatabase.CONFLICT_REPLACE)
}
}
}
fun delete(event: Event) {
delete(event.id)
}
fun delete(eventId: Int) {
writableDatabase.use {
it.delete(TableConstants.EVENTS,
"${EventConstants.COL_ID} == ?",
arrayOf("$eventId"))
}
}
Dále je v aplikaci několik dalších aktivit, ale zde bude popsána pouze jedna, protože
ostatní jsou podobné a mají podobné funkce. Prezentují uživateli data z databáze za
použití výše popsaných tříd a přebírají od uživatele data a ukládají je do databáze.
Aktivita EventDetailActivity slouží pro zobrazení informací o jedné události
a umožňuje uživateli si událost uložit, aby byl upozorněn v nastavený čas.
Page 53
45
Tato třída má několik vlastností, ve kterých jsou uloženy instance objektů GUI, jimiž
nastavuje data a listenery. Ve funkci onCreate je inicializováno view a získán
předaný objekt Event, který je obalen třídou EventParcelable, která umožňuje jeho
serializaci. Potom jsou do prvků GUI nastaveny některé informace a pomocí funkce
doInBackground získá z databáze aktuální údaje pro nastavení časů, které jsou
v postExecutionAction listeneru nastaveny do GUI. Funkce doInBackgroud přebírá
jako parametr funkci, která má být vykonána v jiném vlákně. Potom vytvoří, spustí
a vrátí instanci AsyncTaskWithListener.
fun <T> doInBackground(function: () -> T) =
object : AsyncTaskWithListeners<Unit, Unit, T>() {
override fun doInBackground(vararg p0: Unit?): T {
return function()
}
}.execute() as AsyncTaskWithListeners<Unit, Unit, T>
dále jsou v metodě onCreate registrovány listenery tlačítek zajišťujících přidání
a odebrání události z vybraných.
override fun onCreate(savedInstanceState: Bundle?) {
…
val event = intent
.getParcelableExtra<EventParcelable?>("selectedEvent")!!.event
txtEventName.text = event.name
…
val task = doInBackground {
DbHelpers.get(EventsDbHelper::class).findById(event.id)
}
task.addPostExecuteAction {
if (it != null) {
event.selectedStart = it.selectedStart
event.selectedEnd = it.selectedEnd
}
…
}
addButton.setOnClickListener {
doInBackground {
DbHelpers.get(EventsDbHelper::class).save(event.apply {
event.selectedStart.fillFromTimePicker(startPicker)
event.selectedEnd.fillFromTimePicker(endPicker)
})
}
startActivity(Intent(this@EventDetailActivity,
EventsActivity::class.java))
}
…
}
Page 54
46
Obr. 3 Hlavní aktivita a detail události.
Zdroj: vlastní zpracování
Page 55
47
4 Shrnutí výsledků
Programovací jazyk Kotlin představuje dobrou alternativu k jazyku Java. Protože
obsahuje funkce, které umožňují stručnější a přehlednější zápis, přesto je však
kompatibilní s knihovnami určenými pro jazyk Java. Přitom si zachovává podstatu
a výhody statického typování. Velkou předností je přitom jeho kompatibilita s Javou
verze 1.6, protože velké množství uživatelů, zejména velké firmy, stále ještě
nepodporují novější verze Javy. Další výhodou je, že se snaží vést k lepšímu stylu
programování, například používání neměnných objektů, vyhýbání se použití null
reference a podobně. Díky podpoře lambda výrazů, je možné snadněji tvořit
znovupoužitelný kód, a také se vyhnout různým duplicitám v kódu, které vznikají,
pokud se podobný kód liší například v jednom volání funkce.
Aplikace pro podporu dne otevřených dveří umožňuje uchazeči lépe si zorganizovat
konající se události, a snadno si vyhledat informace, i když zrovna není připojen
k internetu, pokud už si je alespoň jednou stáhnul. Všechny informace je možné
snadno upravovat pomocí serverové aplikace, a to buď přes jednoduché GUI, nebo
pomocí REST API.
Page 56
48
5 Závěry a doporučení
V práci byl představen programovací jazyk Kotlin spolu s ukázkou jeho praktického
využití na jednoduché aplikaci a porovnání s Javou, programovacím jazykem, nad
kterým je Kotlin postaven a je s ním plně kompatibilní. I když se jedná o jazyk
poměrně nový, je již dostatečně vyzrálý a použitelný na vývoj všech druhů aplikací.
Jeho hlavní výhodou je přehlednost, moderní syntaxe, následování nejnovějších
trendů v programování a v neposlední řadě zpětná kompatibilita se staršími
verzemi Javy. Další předností je snadnost učení a to obzvlášť pro programátory,
kteří už mají znalosti dalších populárních programovacích jazyků, protože Kotlin
přebírá vlastnosti a syntaxi z ostatních moderních jazyků. Mezi užitečné vlastnosti,
které Kotlin přebírá z jiných jazyků, než z Javy patří například používání vlastností
namísto getterů a setterů, unchecked výjimky nebo přetěžování operátorů.
Protože je Kotlin staticky typovaný jazyk, lze dříve odhalovat chyby, které se objeví
už při kompilaci a ne až za běhu. Další výhodou je rychlost, která je vyšší než
u dynamicky typovaných jazyků, jako je například Groovy. Statická typovost přináší
ještě jednu výhodu – lepší podporu ze strany integrovaných vývojových prostředí,
které nemusí provádět složitou analýzu průchodu kódem pro automatické
doplňování kódu a odhalování chyb.
Na Kotlinu je založena příkladová aplikace, která řeší požadavky uchazečů
o studium při první návštěvě fakulty na dni otevřených dveří. Mezi její hlavní
výhody patří možnost zorganizovat si den otevřených dveří podle požadavků
uchazeče. Může si předem naplánovat, jakých událostí se kdy zúčastní a kdykoliv se
dostat k informacím, které by mohl potřebovat. Při vývoji aplikace se naplno
projevila síla zvolených nástrojů, které napomohly k rapidnímu vývoji této aplikace
ve velmi krátkém čase. Zejména framework Spring, který umožňuje vytvořit
rozsáhlou funkcionalitu pomocí jen několika málo řádků kódu, kde jazyk Kotlin dále
zjednodušuje tuto implementaci, díky přímé podpoře pro často opakované vzory
v programování. Jádrem celého vývojového cyklu se stalo vývojové prostředí IntelliJ
IDEA od společnosti JetBrains s.r.o., které nativně podporuje jak Kotlin, tak i ostatní
technologie využité při tvorbě aplikace, jmenovitě Android, Gradle, Mercurial,
Spring, Hibernate a JavaScript.
Page 57
49
Další možný výzkum by se mohl týkat návrhů na vylepšení jazyka Kotlin, adresovat
jeho současné limitace a diskutovat možné budoucí směřování jazyka. Samotná
aplikace pro podporu dne otevřených dveří by mohla obsahovat prvky rozšířené
reality, obzvláště využitelné u interaktivních panoramatických fotek učeben.
Takováto virtuální zkušenost by mohla uchazeče ještě více motivovat k rozhodnutí
se pro Fakultu informatiky a managementu.
Page 58
50
6 Seznam použité literatury
[1] BLOCH, Joshua. Effective Java. (2nd Edition). Upper Saddle River, NJ, USA.
Vydavatelství: Prentice Hall PTR, 2008. Počet stran: 384. ISBN: 0321356683
9780321356680.
[2] Classes and Inheritance – Kotlin Programming Language[online]. Dostupné na
World Wide Web: <https://kotlinlang.org/docs/reference/classes.html>
[3] Functions – Kotlin Programming Language[online]. Dostupné na World Wide
Web: <https://kotlinlang.org/docs/reference/functions.html>
[4] Properties and Fields – Kotlin Programming Language[online]. Dostupné na
World Wide Web: <https://kotlinlang.org/docs/reference/properties.html>
[5] Visibility Modifiers – Kotlin Programming Language[online]. Dostupné na
World Wide Web: <https://kotlinlang.org/docs/reference/visibility-
modifiers.html>
[6] Null Safety – Kotlin Programming Language[online]. Dostupné na World Wide
Web: <https://kotlinlang.org/docs/reference/null-safety.html>
[7] Data Classes – Kotlin Programming Language[online]. Dostupné na World Wide
Web: <https://kotlinlang.org/docs/reference/data-classes.html>
[8] Generics – Kotlin Programming Language[online]. Dostupné na World Wide
Web: <https://kotlinlang.org/docs/reference/generics.html>
[9] PECINOVSKÝ, Rudolf. Návrhové vzory. Vydavatelství: Computer Press,
Albatros Media a. s., 2007. Počet stran: 528. ISBN: 8025145107 9788025145104.
[10] Processing Data with Java SE 8 Streams, Part 1[online]. Dostupné na
World Wide Web: <http://www.oracle.com/technetwork/articles/java/ma14-java-
se-8-streams-2177646.html>
[11] Collections – Kotlin Programming Language[online]. Dostupné na World
Wide Web: <https://kotlinlang.org/docs/reference/collections.html>
[12] JOHNSON, Rod, et al. Professional Java Development with the Spring
Framework. Vydavatelství: John Wiley & Sons, 2005. Počet stran: 676. ISBN:
0471748943 9780471748946.
[13] BAUER, Christian, KING, Gavin. Hibernate in Action. Vydavatelství:
Manning, 2005. Počet stran: 408. ISBN: 193239415X, 9781932394153.
[14] Bobril – I – Getting Started - CodeProject[online]. Dostupné na World
Wide Web: < http://www.codeproject.com/Articles/1044425/Bobril-I-Getting-
Started>
Page 59
51
[15] Spring Data JPA – Reference Documentation[online]. Dostupné na World
Wide Web: <http://docs.spring.io/spring-data/jpa/docs/current/reference/html>
[16] Activites – Android Developers[online]. Dostupné na World Wide Web:
<http://developer.android.com/guide/components/activities.html>
Page 60
52
7 Přílohy
1) Ukázky dalších aktivit
Page 62
Oskenované zadání práce