Kotlin – Úvod do knihovny Anko pro Android  (1/4)

Tento článek je prvním ze série o knihovně Anko (ANdroidKOtlin) pro vývoj Android aplikací. Další tři části budou na našem blogu vycházet v následujících týdnech. Naučíte se například, jak můžete definovat uživatelské rozhraní přímo v kódu pomocí Anko DSL namísto jeho zápisu do XML. Knihovna Anko se netýká pouze definice uživatelského rozhraní, ale obsahuje mnoho rozšíření, které vám během vývoje Androidu usnadní život.

Anko je knihovna napsaná v jazyce Kotlin obsahující řadu extension funkcí pro zrychlení a ulehčení vývoje Android aplikací. Je pořád ve fázi intenzivního vývoje a definice uživatelského rozhraní v kódu ještě podle mě není úplně připravena do produkce. Proto my v eManu používáme Anko především díky bohaté knihovně extension funkcí, o kterých si povíme v dalších článcích této série. Myslím si ale, že je velmi zajímavé se naučit, jak můžeme definovat UI přímo v kódu. Tomu se budeme v této sérii článků také věnovat.

verticalLayout {
      padding = dip(30)
      val usernameEditText = editText {
        id = R.id.userNameEditText
        hintResource = R.string.create_user_hint_username
        textSize = 24f
      }
}

Přehled dílů série:

1. Vytvoření projektu v Android Studio 3 a první UI
2. Tlačítka, toasty, dialogy, intenty, uživatelská rozhraní a práce na pozadí (background threads)
3. Vylepšujeme naše UI
4. Fragmenty & RecyclerView

 

1. Vytvoření projektu v Android Studio 3 a první UI

V tomto prvním díle se naučíte, jak vytvořit nový Kotlin Android projekt s konfigurací Gradle souborů krok za krokem. Na závěr si vyzkoušíte, jak napsat UI ve formě přihlašovací obrazovky do aplikace v knihovně Anko DSL.
Poznámka: V druhém díle této série pak do vašeho UI přidáte více logiky.

Import projektu z GitHubu

Android aplikaci, kterou si touto sérií článků vytvoříte, si můžete stáhnout přímo tady z repozitáře na eMan GitHubu, nebo si ji pojďte vytvořit společně se mnou krok za krokem. Ničeho se nebojte, poskytnu vám k tomu kompletní tutoriál.

Popis repozitáře:

  • master -> obsahuje finální aplikaci
  • part 1 až 4 -> jednotlivý kód aplikace k danému článku

 

 Vytvoření Kotlin Android projektu s použitím Anko

Pro celou sérii článků používám Android Studio 3.0 (verze beta2 v době psaní), Kotlin plugin je tady už jeho součástí, takže není potřeba nic instalovat. Pokud používáte starší verzi AS, instalaci Kotlin pluginu se nevyhnete: Preferences -> Plugins -> Browse repositories.

00_kotlin_AS_plugin

Nyní je naše AS připraveno pro Kotlin. Jak bychom poznamenali anglicky: Let’s have some fun! 🙂 Při čtení této série o Anko doporučuji používat Android Studio 3, bude to jednodušší.

Začněte vytvořením nového projektu. Vytvořte nový Android projekt z dialogového okna nebo z nabídky: File -> New -> New Project.

Nyní zadejte název aplikace, package, umístění projektu a vaší doménu. Dejte si pozor, abyste nechali zaškrtnutou volbu Include Kotlin support (kterou máme k dispozici právě od AS 3).

01_create_an_project

Pokud chcete upravit package name, můžete kliknout na tlačítko Edit na pravé straně a změnit si ho ručně, jako jsem to udělal já.

Teď budete vyzváni, abyste zvolili minimální podporovanou verzi Androidu. Zvolíme API 15 pro telefony a tablety.

02_as_min_version

Nyní zvolte prázdnou aktivitu, kterou si nějak pojmenujte. Já ponechám název MainActivity.

Po dokončení těchto kroků byste měli mít vytvořený nový Android Kotlin projekt obsahující vaši MainActivity.kt v Kotlinu.

03_main_activity

Konfigurace Gradle

V této části si ukážeme integraci knihovny Anko do projektu. Taky prozradíme, jak si můžete lépe nadefinovat závislosti v Gradle souborech.

Nejprve začněte s hlavním build.gradle souborem:

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    ext.kotlin_version = '1.1.4'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.0-beta2'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

Vidíte, že Kotlin plugin přidal definici verze Kotlinu 1.1.4 do ext. bloku a na classpath byl přidán kotlin-gradle-plugin.

Nyní se podívejte do souboru build.gradle v app modulu:

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 26
    buildToolsVersion "26.0.1"
    defaultConfig {
        applicationId "cz.eman.android.sample.anko"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"

    implementation 'com.android.support:appcompat-v7:26.0.1'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'

    testImplementation 'junit:junit:4.12'

    androidTestImplementation 'com.android.support.test:runner:1.0.0'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.0'
}

Všimněte si řádku č. 3 a č. 5. Zde jsou aplikovány pluginy kotlin-android a kotlin-android-extension. Na řádku č. 29 je přidána závislost na standardní Kotlin knihovně, která obsahuje i definici Java SDK a to buď jre7, nebo jre8. Definice JDK byla v Kotlinu zavedena od verze 1.1.

V následujících krocích se naučíte, jak nadefinovat závislosti v Gradle takovým způsobem, abyste měli jejich definici na jednom místě pro celý projekt.

Jak jsem už zmiňoval, bylo by hezké mít všechny definice závislostí v extension DSL bloku v Gradle, např. přesunout definici verzí do ext. bloku v kořenovém build.gradle souboru tak, jako to udělal Kotlin plugin pro definici verze Kotlinu. Já osobně dávám přednost definici závislostí v samostatném souboru. Je tedy na vás, co si vyberete. Pokud zvolíte stejnou cestu jako já, pak budete definovat všechny závislosti a konstanty v souboru dependencies.gradle:

  • Přidejte verze závislostí do samostatného pole (versions) a samotnou definici artefaktů pak rozdělte do dalších jednotlivých polí. Verze závislostí můžete přidat na oddělená pole například takto:
    • gradlePlugins – gradle pluginy, které v kořenovém build.gradle souboru přidáte na classpath
    • supportDependencies – support prvky knihovny, které budete v projektu používat
    • kotlinDependencies – všechny Kotlin knihovny budou tady
    • testDependencies – knihovny potřebné pro testovací část
  • Dále přidejte Anko 0.9.1 (v době psaní tohoto článku již Anko 10.1, ale my v produkci používáme verzi 0.9.1, proto jsem se rozhodl ji ještě použít i pro tento článek)
  • Java 8 – od AS 3.0 používáme Java 8:
    • sourceCompatibility: JavaVersion.VERSION_1_8
    • targetCompatibility: JavaVersion.VERSION_1_8
  • Kotlin Standardní knihovna s jre8: org.jetbrains.kotlin: kotlin-stdlib-jre8

Váš finální soubor dependencies.gradle by měl tedy vypadat takto:

 

ext {

    //Versions
    versions = [
            appVersion         : '1.0.0',
            code               : 1,

            buildTools         : "27.0.2",
            compileSdk         : 26,
            minSdk             : 15,
            targetSdkVersion   : 26,
            sourceCompatibility: JavaVersion.VERSION_1_8,
            targetCompatibility: JavaVersion.VERSION_1_8,
            encoding           : "UTF-8",

            gradleBuildTools   : '3.0.1',
            gradle             : '4.1',

            kotlin             : '1.2.30',
            supportLib         : '27.0.2',
            constraintLayout   : '1.0.2',
            anko               : '0.9.1',

            espresso           : '3.0.0',
            junit              : '4.12',
            testRunner         : '1.0.0'
    ]

    // constants
    testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner"

    gradlePlugins = [
            android: "com.android.tools.build:gradle:${versions.gradleBuildTools}",
            kotlin : "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
    ]

    //Support Libraries dependencies
    supportDependencies = [
            appCompat      : "com.android.support:appcompat-v7:${versions.supportLib}",
            constrainLayout: "com.android.support.constraint:constraint-layout:${versions.constraintLayout}"
    ]

    kotlinDependencies = [
            kotlinStbLib: "org.jetbrains.kotlin:kotlin-stdlib-jre8:${versions.kotlin}",
            ankoSdk15   : "org.jetbrains.anko:anko-sdk15:${versions.anko}"
    ]

    testDependencies = [
            junit       : "junit:junit:${versions.junit}",
            espressoCore: "com.android.support.test.espresso:espresso-core:${versions.espresso}",
            testRunner  : "com.android.support.test:runner:${versions.testRunner}"
    ]

}

 

Nyní se zaměřte na build.gradle soubory. Konkrétně na kořenový:

// Top-level build file where you can add configuration options common to all sub-projects/modules.

apply from: 'dependencies.gradle'

buildscript {
    // Load dependencies - Gradle cannot handle with external properties inside of buildscript
    // So we need to apply external file here again
    apply from: 'dependencies.gradle'

    repositories {
        jcenter()
        maven {
            url 'https://maven.google.com'
        }
    }
    dependencies {
        classpath gradlePlugins.android

        // Kotlin Grade plugin
        classpath gradlePlugins.kotlin

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}


allprojects {
    repositories {
        jcenter()
        maven {
            url 'https://maven.google.com'
        }
    }
}

task wrapper(type: Wrapper) {
    gradleVersion = versions.gradle
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

 

A na ten, co máte v app modulu:

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion versions.compileSdk
    buildToolsVersion versions.buildTools

    defaultConfig {
        applicationId "cz.eman.android.sample.anko"
        minSdkVersion versions.minSdk
        targetSdkVersion versions.targetSdk
        versionCode versions.code
        versionName versions.appVersion
        testInstrumentationRunner rootProject.ext.testInstrumentationRunner
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    compileOptions {
        sourceCompatibility versions.sourceCompatibility
        targetCompatibility versions.targetCompatibility
    }

    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
        test.java.srcDirs += 'src/test/kotlin'
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    // Kotlin
    implementation kotlinDependencies.kotlinStbLib
    implementation kotlinDependencies.ankoSdk15

    // Support Libraries
    implementation supportDependencies.appCompat
    implementation supportDependencies.constrainLayout

    // Tests
    testCompile testDependencies.junit

    androidTestImplementation testDependencies.testRunner
    androidTestImplementation(testDependencies.espressoCore) {
        exclude module: 'support-annotations'
    }
}

 

V kořenovém Gradle souboru je na řádku č. 3 vložená reference na soubor dependencies.gradle, který musíte znovu vložit do buildscript{} bloku. Gradle totiž neumí pracovat s externíma propertama uvnitř tohoto bloku. Dále jste přidali referenci na Google Maven repozitář a v dependencies{} bloku jste přidali Gradle pluginy na classpath.

V app/build.gradle si můžete všimnout, že přibylo několik závislostí na Anko knihovnu implementation kotlinDependencies.ankoSdk15. SDK 15 vám říká, že chcete používat Anko s podporou minimální verze Androidu API 15, což odpovídá vaší definici minSdkVersion = 15.

Kromě Anko knihovny s SDK můžete použít i další malé Anko knihovny. Tato knihovna je totiž rozdělena na takové malé knihovničky. To je skvělá zpráva, protože nemusíte mít celou knihovnu, ale stačí vám vložit jen tu část, kterou budete potřebovat. Například pokud chcete používat knihovnu Anko pouze pro práci s RecyclerView, můžete zahrnout pouze: org.jetbrains.anko: anko-recyclerview-v7.

Informace o dostupných knihovnách, které můžete používat, najdete na oficiálních stránkách této knihovny.

Nyní máte připravené Gradle soubory a můžete se pomalu pustit do práce s Anko knihovnou. Než začnete, čeká vás ale ještě jeden nepovinný krok. Ti z vás, kteří nechtějí definovat kotlin složku jako zdrojovou v projektu, ho mohou přeskočit.

Kotlin jako zdrojová složka

Jak jsem řekl, tento krok můžete a nemusíte udělat. Pokud chcete zachovat java složku jako zdrojovou, tento krok vynechte.

Poznámka: Pokud budete postupovat podle níže uvedených kroků, uvidíte stále složku java jako zdrojovou v Android Project View, neboť Android Studio v tomto view vloží všechny java/kotlin soubory do složky java.

Přejmenujte složku java (src/main/java) na kotlin (src/main/kotlin).

04_kotlin_sourcefolder

Nyní musíte do app/build.gradle přidat blok sourceSets dsl:

Android {
  ...
 
   SourceSets {
    	Main.java.srcDirs + = 'src / main / kotlin'
    	Test.java.srcDirs + = 'src / test / kotlin'
	}}
}}

Podívejte se na strukturu projektu.

V zobrazení projektu Android máte jako zdrojovou složku stále java:

05_project_structure_android

Přepněte AS projekt do zobrazení projektu (Project) a uvidíte, že zdrojová složka je teď kotlin:

06_project_view

Problém je popsán zde.

Pokuste se spustit aplikaci, abyste se ujistili, že vše funguje tak, jak má:

07_first_app_run

Vytvoření prvního view v Anko knihovně

Podívejte se na activity_main.xml. Vidíte, že vygenerovaný layout má jako kořenový layout android.support.constraint.ConstraintLayout.

Anko je velmi užitečná a obsahově silná knihovna, ale je ve stádiu vývoje, takže potřebuje ještě trošku času na přidání všech funkcí a podpory pro vytváření UI. Proto samozřejmě i zde narazíte na různé chyby nebo vám budou chybět vámi požadované části.

Jednou z chybějících funkcí je podpora právě zmíněné ConstraintLayout.

Tento problém můžete vyřešit kombinací vytváření layoutu jak v XML, tak i přímo v kódu, čímž docílíte použití ConstraintLayout. To nicméně není žádoucí, cílem těchto článků je ukázat, jak vytvořit layouty přímo v kódu. Řešení tohoto tématu se tak dozvíte na konci série. Z důvodu tohoto omezení tedy použijte LinearLayout. Odeberte i závislost na ConstraintLayout ze samotného projektu (dependencies.gradle, app/build.gradle).

Co vás čeká dál? Vytvoříte aplikaci, která bude mít dva UI layouty:

  • Sign In – přihlašovací obrazovka
  • Say Hello

 

Funkce Sign In

V tomto procesu vytvoříte přihlašovací obrazovku obsahující:

  • Uživatelské jméno (frosty)
  • Heslo (snowman)
  • Tlačítko pro přihlášení

Uživatelské jméno a heslo budou předdefinovány (nebudete tady implementovat žádný registrační proces).

Sing In Aktivita

Nyní vytvořte v projektu nový package pod názvem sign_in, ve kterém vytvořte novou aktivitu (nezapomeňte, že ji vytváříte v Kotlinu 🙂 ).

08_signin_activity_1

Váš projekt by měl tedy mít nyní tuto strukturu:

09_project_structure

Anko komponenta

Jak už víte, můžete použít Anko DSL přímo v kódu vaší aktivity či fragmentu pro vytvoření layoutu. Teď si ukážeme, jak můžete DSL layout přesunout do samostatné třídy (komponenty) a docílit tak znovupoužitelnosti uživatelského rozhraní v jiné aktivitě či fragmentu.

Vytvořte tak vaši první Anko kompentu AnkoComponent s definicí UI layoutu, kterou nazvete SignInView. Poté nastavte SignInActivity, aby ji použila jako content view.

class SingInView : AnkoComponent<SignInActivity> {

  override fun createView(ui: AnkoContext<SignInActivity>): View {
    TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
  }

}

Přidejte nový blok editText{}. Uvnitř tohoto bloku máte samozřejmě přístup ke všem atributům, které jste zvyklí používat v XML, a také ke všem metodám, které můžete volat z kódu. Nejdříve ale musíte definovat unikátní id vašeho editText widgetu.

Vytvořte soubor ids.xml, kde budete postupně definovat identifikátory pro vaše widgety:

Screenshot_11

Teď konečně můžete nastavit id = R.id.username v editText bloku.

Screenshot_2

Nyní přidáte nové atributy hintResource a textSize = 24f. Nejdříve ale přidejte nové klíče do strings.xml.

Screenshot_4

Teď můžete přidat zmíněné atributy:

Screenshot_5

Poznámka: Pokud z nějakého důvodu potřebujete použít přímo text namísto reference do strings.xml, můžete použít atribut hint.

Kód, který jste napsali, vypadá přehledně a čistě, nemyslíte? Ale ještě něco chybí… Jak jsem řekl, použijete LinearLayout, takže dalším cílem je obalit do ní váš editText{}.

LinearLayout může mít vertikální a horizontální polohu. Tu můžete nastavit pomocí atributu orientace. V Anko máme přímo specifický blok DSL pro vertikální zobrazení – verticalLayout{}.

Screenshot_6

Pokud chcete použít LinearLayout s horizontální orientací, můžete to zapsat takto:

Screenshot_7

V dalším kroku definujte parametry pro vaše verticalLayout a usernameEditText. V Anko se to provede pomocí funkce lparams:

class SingInView : AnkoComponent<SignInActivity> {

  override fun createView(ui: AnkoContext<SignInActivity>) = with(ui) {
    verticalLayout {
      lparams(width  = matchParent, height = matchParent)

      editText {
        id = R.id.usernameEditText
        hintResource = R.string.sign_in_username
        textSize = 24f
      }.lparams(width = matchParent, height = wrapContent)
    }
  }

}

Poznámka: LayoutParams v Anko definujte hned za ukončením DSL bloku daného view. Toto je doporučení z oficiální dokumentace. Je možné layout parametry definovat uvnitř bloku daného view, ale s určitým omezením. Navíc to ne vždy zafunguje tak, jak očekáváte. Vývojáři Anko knihovny v diskuzích upozorňují, že layout parametry se mají opravdu raději umisťovat až na konec bloku. Více např. v tomto issue na GitHubu.


Nyní je pomalu ten správný čas vyzkoušet vaši aplikaci a zjistit, zda všechno funguje, jak má. Než budete moct aplikaci spustit, musíte dokončit ještě poslední dva kroky:

  • přiřadit aktivitě vytvořenou Anko komponentu
  • přesměrovat flow aplikace z MainActivity do SignInActivity

Otevřete si v AS třídu SignInActivity. Chcete-li definovat, že content view má být vytvořeno z naší komponenty SignInView (AnkoComponent), tak stačí jen přidat do metody onCreate následující řádek kódu (poté, co se zavolá super.onCreate (...)):

class SignInActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    SingInView().setContentView(this)
  }
}

Přidejte přechod z MainActivity do SingInActivity. Nehledejte tady žádnou logiku. Ve 3. díle této série článků o knihovně Anko se naučíte pracovat s extension funkcemi pro zjednodušení práce s intenty, tedy jak můžete spustit novou aktivitu s jejich využitím.

Screenshot_8

Konečně můžete spustit aplikaci:

10_anko_app_first_run

Poznámka: Nelekejte se, jak vypadá design vaší aplikace! Úkolem 3. článku bude vaše UI trochu zkrášlit a vylepšit.

Pokud vidíte to, co já na předchozím obrázku, pak vám aplikace naběhla. Také se vám tím pádem povedlo zobrazit UI definované v komponentě (SingInView). Nyní přidejte další editText blok pro zadávání hesla.

Screenshot_9

 

11_anko_app_passwd

Nyní už umíte pracovat s editText blokem, ale jak se definuje tlačítko? Přidání tlačítka je velmi podobné definici editText bloku. Nejjednodušší způsob, jak přidat tlačítko, je prostě jen napsat:

button("Sign In")

Docela lehké, co říkáte?

Spusťte znovu aplikaci:

12_anko_app_sign

Tamtadadá! Vaše tlačítko Sign In je tam! 🙂

Samozřejmě, že toto byla jen ukázka, takže tlačítko nadefinujte tak, jak byste to udělali v XML. To znamená, že mu nastavíte jeho id atribut a velikost. V dalším článku pak zaregistrujete akci, tedy co se má stát po jeho stisknutí.

Finální kód SignInView.kt:

class SingInView : AnkoComponent<SignInActivity> {

  override fun createView(ui: AnkoContext<SignInActivity>) = with(ui) {
    verticalLayout {
      lparams(width  = matchParent, height = matchParent)

      editText {
        id = R.id.usernameEditText
        hintResource = R.string.sign_in_username
        textSize = 24f
      }.lparams(width = matchParent, height = wrapContent)

      editText {
        id = R.id.passwordEditText
        hintResource = R.string.signIn_password
        textSize = 24f
      }.lparams(width = matchParent, height = wrapContent)

      button {
        id = R.id.signIn_button
        textResource = R.string.signIn_button
      }.lparams(width = matchParent, height = wrapContent)
    }
  }

}

 

Shrnutí 1. části

V tomto prvním článku jste se naučili, jak vytvořit krok za krokem nový Kotlin Android projekt. Provedli jste konfiguraci Gradle souborů a nakonec jste vytvořili svůj první layout za použití Anko knihovny.

V dalším článku (část 2) přidáte logiku do vaší funkce Sign In. Naučíte se pracovat s vlákny, intenty, toasty a dialogy pomocí knihovny Anko.

Díky za přečtení a snad se potkáme i u dalšího dílu série!

Václav Souhrada
Kotlin & Android Developer at eMan, Czech Kotlin User Group Founder and Organizer

RSS