SPS - Kotlin
Seminár z programovania v sieťach

Kotlin

Multi-platformový jazyk vyvíjaný JetBrains a Google.

Beží na JVM, plne spolupracuje s Javou, ale je schopný kompilovať aj do JavaScript-u a natívneho kódu.

Kotlin JVM, Kotlin Native, Kotlin JS

Oficiálny jazyk na vývoj mobilných aplikácií - Android.

Zadanie záverečného projektu z PAZ1a

Riešenie na GitLabe

Pokemons

Vytvorenie projektu... Nazveme ho Pokemons

Stiahnite si pripravené súbory: html, css

Vytvoríme si nový Main.kt súbor:

fun main() {
    println("Hello world!")
}

Po "zbuildovaní" projektu sa vytvoria súbory:

Tie sú referencované v našom index.html súbore

Do Main.kt vložíme:

val API_URL = js("getApiUrl()") as String

Vytvoríme si entitu Pokemon (data class):

data class Pokemon(
    val name: String,
    val dex: String,
    val types: Array<String>,
    val url: String,
    val imageUrl: String)            

Architektúra aplikácie:

Vytvoríme si kontrakt medzi nimi:

interface PokemonContract {

    interface View {
        fun showPokemons(pokemons: List<Pokemon>)
        fun showLoader()
        fun hideLoader()
    }

    interface Presenter {
        fun attach(view: View)
        fun loadPokemons()
    }
}

Implementujeme jednotlivé interfacy:

class PokemonPage(private val presenter: PokemonContract.Presenter) : PokemonContract.View {

    override fun showPokemons(pokemons: List<Pokemon>) {
        // TODO
    }

    override fun showLoader() {
        // TODO
    }

    override fun hideLoader() {
        // TODO
    }
}

class PokemonPresenter : PokemonContract.Presenter {

    private lateinit var view: PokemonContract.View

    override fun attach(view: PokemonContract.View) {
        this.view = view
    }

    override fun loadPokemons() {
        // TODO
    }
}       

Získame dáta zo vzdialeného servera... Do triedy PokemonPresenter pridáme metódu:

private fun getAsync(url: String, callback: (String) -> Unit) {
    val xmlHttp = XMLHttpRequest()
    xmlHttp.open("GET", url)
    xmlHttp.onload = {
        if (xmlHttp.readyState == 4.toShort() && xmlHttp.status == 200.toShort()) {
            callback.invoke(xmlHttp.responseText)
        }
    }
    xmlHttp.send()
}   

V triede PokemonPresenter implementujeme metódu loadPokemons():

override fun loadPokemons() {
    view.showLoader()
    getAsync(API_URL) { response ->
        val pokemons = JSON.parse<Array<Pokemon>>(response)
        view.hideLoader()
        view.showPokemons(pokemons.toList())
        pokemons.forEach { pokemon -> println(pokemon.types) }
    }
}

Otestujeme, či sme dostali dáta zo servera... Opravíme Main.kt:

fun main() {
    val pokemonPresenter = PokemonPresenter()
    val pokemonPage = PokemonPage(pokemonPresenter)
    pokemonPresenter.attach(pokemonPage)
    pokemonPresenter.loadPokemons()
}

Zobrazovanie dát

V triede PokemonPage získame referencie na div elementy v index.html súbore:

private val loader = document.getElementById("loader") as HTMLDivElement
private val content = document.getElementById("content") as HTMLDivElement

Zmeníme viditeľnosť loadera:

override fun showLoader() {
    loader.style.visibility = "visible"
}

override fun hideLoader() {
    loader.style.visibility = "hidden"
}

Vytvárame pokémonové kartičky pomocou novej triedy CardBuilder:

class CardBuilder {

    fun build(pokemon: Pokemon): HTMLElement {
        val containerElement = document.createElement("div") as HTMLDivElement
        val imageElement = document.createElement("img") as HTMLImageElement
        val nameElement = document.createElement("div") as HTMLDivElement
        val dexElement = document.createElement("div") as HTMLDivElement
        val typesElement = document.createElement("ul") as HTMLUListElement
        val viewDetailsButtonElement = document.createElement("button") as HTMLButtonElement

        bind(pokemon = pokemon,
                imageElement = imageElement,
                nameElement = nameElement,
                dexElement = dexElement,
                typesElement = typesElement,
                viewDetailsButtonElement = viewDetailsButtonElement)

        applyStyle(containerElement,
                imageElement = imageElement,
                nameElement = nameElement,
                dexElement = dexElement,
                typesElement = typesElement,
                viewDetailsButtonElement = viewDetailsButtonElement)

        containerElement
                .appendChild(
                        imageElement,
                        nameElement,
                        dexElement,
                        typesElement,
                        viewDetailsButtonElement
                )
        return containerElement
    }

    private fun Element.appendChild(vararg elements: Element) {
        elements.forEach {
            this.appendChild(it)
        }
    }
}

Implementujeme bindovanie HTML elementov k dátam pokemóna:

private fun bind(pokemon: Pokemon,
                    imageElement: HTMLImageElement,
                    nameElement: HTMLDivElement,
                    dexElement: HTMLDivElement,
                    viewDetailsButtonElement: HTMLButtonElement,
                    typesElement: HTMLUListElement) {

    imageElement.src = pokemon.imageUrl
    nameElement.innerHTML = pokemon.name
    dexElement.innerHTML = pokemon.dex
    bindPokemonTypes(typesElement, pokemon.types)
    viewDetailsButtonElement.innerHTML = "view details"
    viewDetailsButtonElement.addEventListener("click", {
        window.open(pokemon.url)
    })
}

private fun bindPokemonTypes(typesElement: HTMLUListElement,
                             types: Array<String>) {
    types.forEach {type ->
        val typeElement = document.createElement("li") as HTMLLIElement
        typeElement.innerHTML = type
        typesElement.appendChild(typeElement)
    }
}

Implementujeme metódu na nastavenie css štýlu jednotlivým HTML elementom:

private fun applyStyle(containerElement: HTMLDivElement,
                        imageElement: HTMLImageElement,
                        nameElement: HTMLDivElement,
                        dexElement: HTMLDivElement,
                        typesElement: HTMLUListElement,
                        viewDetailsButtonElement: HTMLButtonElement) {
    containerElement.addClass("card", "card-shadow")
    imageElement.addClass("cover-image")
    nameElement.addClass("text-name", "float-left")
    dexElement.addClass("text-dex", "float-left")
    typesElement.addClass("float-left")
    typesElement.children.asList().forEach { element ->
        element.addClass("text-type", "float-left")
    }
    viewDetailsButtonElement.addClass("view-details", "ripple", "float-right")
}

V triede PokemonPage pridáme field CardBuilder

class PokemonPage(private val presenter: PokemonContract.Presenter) : PokemonContract.View {
    private val cardBuilder: CardBuilder = CardBuilder()
    ...
}

Následne implementujeme metódu showPokemons() + pridáme ešte jednu metódu:

override fun showPokemons(pokemons: List) {
    pokemons.forEach { pokemon ->
        val card = cardBuilder.build(pokemon)
        content.appendChild(card)
    }
}

fun show() {
    presenter.attach(this)
    presenter.loadPokemons()
}

Upravíme Main.kt, zbuildujeme aplikáciu a obdivujeme náš výtvor :)

fun main() {
    val pokemonPresenter = PokemonPresenter()
    val pokemonPage = PokemonPage(pokemonPresenter)
    pokemonPage.show()
}

Úlohy:

Riešenie na GitLabe