Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin no Android: Desbravando as oportunidades de ponta a ponta!

Kotlin no Android: Desbravando as oportunidades de ponta a ponta!

This talk was first held during Kotlin Community Summit '18, the first Kotlin conference in São Paulo, that happened on September 18, 2018, in São Paulo, Brazil.

In this talk I talked a little about some possibilities and opportunities that we can have in using the Kotlin language in an Android project.
From the construction of interfaces to asynchronous processes and going through the tests, I showed how we can extract the most from this language in a modern project.

More info: https://eventos.imasters.com.br/kotlinsummit/

Walmyr Carvalho

September 15, 2018
Tweet

More Decks by Walmyr Carvalho

Other Decks in Technology

Transcript

  1. Kotlin - Criada pela JetBrains e anunciada publicamente em 2012

    - Open Source (Apache 2.0) - Suporta compilação pra JVM, JS e nativo - Null-safety por design
  2. Kotlin + Android KTX Anko + Kotlin Android Extensions (kotlinx)

    Coroutines Spek + MockK Geral UI Async Testes Build Gradle Kotlin DSL + Detekt
  3. O Android KTX é um conjunto de extension functions criadas

    pelo Google para otimizar o desenvolvimento de tarefas comuns do Android.
  4. Módulo (artefato) Versão Pacotes androidx.core:core-ktx 1.0.0-alpha1 Todos os pacotes core

    abaixo androidx.fragment:fragment-ktx 1.0.0-alpha1 androidx.fragment.app androidx.palette:palette-ktx 1.0.0-alpha1 androidx.palette.graphics androidx.sqlite:sqlite-ktx 1.0.0-alpha1 androidx.sqlite.db androidx.collection:collection-ktx 1.0.0-alpha1 androidx.collection androidx.lifecycle:lifecycle-viewmodel-ktx 2.0.0-alpha1 androidx.lifecycle androidx.lifecycle:lifecycle-reactivestreams-ktx 2.0.0-alpha1 androidx.lifecycle android.arch.navigation:navigation-common-ktx 1.0.0-alpha01 androidx.navigation android.arch.navigation:navigation-fragment-ktx 1.0.0-alpha01 androidx.navigation.fragment android.arch.navigation:navigation-runtime-ktx 1.0.0-alpha01 androidx.navigation android.arch.navigation:navigation-testing-ktx 1.0.0-alpha01 androidx.navigation.testing android.arch.navigation:navigation-ui-ktx 1.0.0-alpha01 androidx.navigation.ui android.arch.work:work-runtime-ktx 1.0.0-alpha01 androidx.work.ktx Fonte: developer.android.com/kotlin/ktx
  5. // Kotlin db.beginTransaction() try { // insira os dados db.setTransactionSuccessful()

    } finally { db.endTransaction() } // Kotlin + Android KTX db.transaction { // insira os dados }
  6. inline fun <T> SQLiteDatabase.transaction( exclusive: Boolean = true, body: SQLiteDatabase.()

    -> T ): T { if (exclusive) { beginTransaction() } else { beginTransactionNonExclusive() } try { val result = body() setTransactionSuccessful() return result } finally { endTransaction() } } SQLiteDatabase.kt
  7. // Kotlin view.viewTreeObserver.addOnPreDrawListener( object : ViewTreeObserver.OnPreDrawListener { override fun onPreDraw():

    Boolean { viewTreeObserver.removeOnPreDrawListener(this) actionToBeTriggered() return true } } ) // Kotlin + Android KTX view.doOnPreDraw { actionToBeTriggered() }
  8. context.withStyledAttributes(set = someAttributeSet, attrs = attributes, defStyleAttr = ..., defStyleRes

    = ...) { // do your rolê } context.withStyledAttributes(set = someAttributeSet, attrs = attributes) { // do your rolê }
  9. buildscript { repositories { google() jcenter() } dependencies { classpath("com.android.tools.build:gradle:3.1.4")

    classpath(kotlin("gradle-plugin", version = "1.2.61")) } } allprojects { repositories { google() jcenter() } } tasks.register("clean", Delete::class) { delete(rootProject.buildDir) } build.gradle.kts
  10. android { compileSdkVersion(27) defaultConfig { applicationId = "me.walmyrcarvalho.hellosummit" minSdkVersion(15) targetSdkVersion(27)

    versionCode = 1 versionName = "1.0" testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" } buildTypes { getByName("release") { isMinifyEnabled = false proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") } } } app/build.gradle.kts
  11. dependencies { implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) implementation(kotlin("stdlib-jdk7", KotlinCompilerVersion.VERSION))

    implementation("com.android.support:appcompat-v7:27.1.1") implementation(“com.android.support.constraint:constraint-layout:1.1.3") testImplementation("junit:junit:4.12") androidTestImplementation("com.android.support.test:runner:1.0.2") androidTestImplementation("com.android.support.test.espresso:espresso-core:3.0.2") } app/build.gradle.kts
  12. O Detekt é uma ferramenta de análise estática de código

    para Kotlin, bem completa e customizável, ideal para pipelines de CI/CD e PRs em geral.
  13. buildscript { repositories { jcenter() } } plugins { id("io.gitlab.arturbosch.detekt").version("[version]")

    } detekt { toolVersion = "[version]" input = files("src/main/kotlin") filters = ".*/resources/.*,.*/build/.*" } build.gradle
  14. autoCorrect: true failFast: false test-pattern: # Configure exclusions for test

    sources active: true patterns: # Test file regexes - '.*/test/.*' - '.*Test.kt' - '.*Spec.kt' exclude-rule-sets: - 'comments' exclude-rules: - 'NamingRules' - 'WildcardImport' - 'MagicNumber' - 'MaxLineLength' - 'LateinitUsage' - 'StringLiteralDuplication' - 'SpreadOperator' - 'TooManyFunctions' - 'ForEachOnRange' default-detekt-config.yml
  15. build: maxIssues: 10 weights: # complexity: 2 # LongParameterList: 1

    # style: 1 # comments: 1 processors: active: true exclude: # - 'FunctionCountProcessor' # - 'PropertyCountProcessor' # - 'ClassCountProcessor' # - 'PackageCountProcessor' # - 'KtFileCountProcessor' default-detekt-config.yml
  16. console-reports: active: true exclude: # - 'ProjectStatisticsReport' # - 'ComplexityReport'

    # - 'NotificationReport' # - 'FindingsReport' # - 'BuildFailureReport' comments: active: true CommentOverPrivateFunction: active: false CommentOverPrivateProperty: active: false EndOfSentenceFormat: active: false endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!]$) UndocumentedPublicClass: active: false searchInNestedClass: true searchInInnerClass: true searchInInnerObject: true searchInInnerInterface: true UndocumentedPublicFunction: active: false default-detekt-config.yml
  17. Alguns conjuntos de regras incluem análises como: - Code smell

    - Complexidade/legibilidade - Disparo de exceções - Formatação - Nomenclaturas - Code style - Linhas/blocos vazios de código - Padrões de testes
  18. O Anko é uma biblioteca que facilita o desenvolvimento de

    interfaces no Android utilizando Kotlin, sem a necessidade de se escrever XMLs
 (além de outras coisinhas a mais).
  19. O Anko é divido em quatro macro-repositórios: - anko-commons: Métodos

    comuns a todos ao Anko em geral - anko-layouts: Bindings gerais de layouts gerais do Anko - anko-coroutines: Callbacks para Anko layouts com Coroutines - anko-sqlite: Helpers do Anko para SQLite Além desses, existem repositórios para Views, ViewGroups e Layouts variados, como CardView, RecyclerView e mais.
  20. dependencies { // Anko Commons implementation "org.jetbrains.anko:anko-commons:$anko_version" // Anko Layouts

    // sdk15, sdk19, sdk21, sdk23 are also available implementation "org.jetbrains.anko:anko-sdk25:$anko_version" implementation "org.jetbrains.anko:anko-appcompat-v7:$anko_version" // Coroutine listeners for Anko Layouts implementation "org.jetbrains.anko:anko-sdk25-coroutines:$anko_version" implementation "org.jetbrains.anko:anko-appcompat-v7-coroutines:$anko_version" // Anko SQLite implementation "org.jetbrains.anko:anko-sqlite:$anko_version" } build.gradle
  21. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) verticalLayout { padding =

    dip(30) editText { hint = "Name" textSize = 24f } editText { hint = "Password" textSize = 24f } button("Login") { textSize = 26f } } } MainActivity.kt
  22. linearLayout { button("Login") { textSize = 26f }.lparams(width = wrapContent)

    { horizontalMargin = dip(5) topMargin = dip(10) } } MainActivity.kt
  23. verticalLayout { editText { hint = "Name" } editText {

    hint = "Password" } }.applyRecursively { view -> when(view) { is EditText -> view.textSize = 20f }} MainActivity.kt
  24. Com o Anko também temos uma série de funções para

    a exibição de componentes (como Dialogs, ProgressBars e Toasts), uso de logging, uso de resources/dimensions e outros.
  25. // Toasts toast("Hey, I'm a toast!") toast(R.string.message) longToast("Wow, this is

    a nice summit!") // Snackbars snackbar(view, "Hey, I'm a snackbar!") snackbar(view, R.string.message) snackbar(view, "Action!", "Click me!") { doYourStuff() } longSnackbar(view, "Wow, this is a nice summit!") MainActivity.kt
  26. O Kotlin Android Extensions (ou kotlinx) ajuda a evitar a

    necessidade de se utilizar o findViewById(…) na hora de se instanciar Views no Android (além de outras coisinhas a mais):
  27. // Using R.layout.activity_main from the 'main' source set import kotlinx.android.synthetic.main.activity_main.*

    class MainActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Instead of findViewById<TextView>(R.id.textView) textView.setText("Hello, Kotlin Community Summit!”) } } MainActivity.kt
  28. O kotlinx tem mais opções como Parcelers custom, cache de

    view (com políticas de cache) e outras coisas legais!
  29. Coroutines no Kotlin são threads "mais leves”, com uma sintaxe

    de código assíncrono tão direta quanto a de um código síncrono, com o objetivo de prover APIs simples para descomplicar operações assíncronas em geral.
  30. Elas são tipos de funções chamadas suspending functions, e tem

    seus métodos marcados com a palavra chave suspend:
  31. // run the code on a background thread pool fun

    asyncOverlay() = async(CommonPool) { // start two async operations val original = asyncLoadImage("original") val overlay = asyncLoadImage(“overlay") // apply the overlay with both callbacks applyOverlay(original.await(), overlay.await()) } // execute the coroutine on UI context launch(UI) { val image = asyncOverlay().await() showImage(image) }
  32. O launch é a maneira mais simples de se criar

    uma coroutine numa thread em background, tendo um Job (uma task, basicamente) como sua referência para si.
  33. public actual fun launch( context: CoroutineContext = DefaultDispatcher, start: CoroutineStart

    = CoroutineStart.DEFAULT, parent: Job? = null, block: suspend CoroutineScope.() -> Unit ): Job
  34. Conceitualmente, o async é similar ao launch, mas a diferença

    deles é que o async retorna um Deferred, que é basicamente uma future (ou promise).
  35. public actual fun <T> async( context: CoroutineContext = DefaultDispatcher, start:

    CoroutineStart = CoroutineStart.DEFAULT, parent: Job? = null, block: suspend CoroutineScope.() -> T ): Deferred<T>
  36. Tecnicamente, um Deferred é um Job, só que com uma

    future com uma promessa de entregar um valor em breve.
  37. Ou seja: No async você tem a mesma funcionalidade do

    launch, mas com a diferença que ele retorna um valor no .await():
  38. fun getUser(userId: String): User = async { // return User

    } launch(UI) { val user = getUser(id).await() navigateToUserDetail(user) }
  39. val job = launch { // do your stuff }

    job.cancel() val job = launch { while (isActive){ //do your stuff } }
  40. Resumindo: Se eu não preciso de um callback explícito ->

    launch Se eu preciso esperar uma future (Deferred) -> async Se eu preciso bloquear minha thread -> runBlocking
  41. val retrofit = Retrofit.Builder() .baseUrl("https://example.com/") .addCallAdapterFactory(CoroutineCallAdapterFactory()) .build() ... interface MyService

    { @GET("/user") fun getUser(): Deferred<User> // or @GET("/user") fun getUser(): Deferred<Response<User>> }
  42. O Spek é um framework para especificação de testes para

    TDD (Test Driven Development) ou BDD (Behavior Driven Development).
  43. object MyTest: Spek({ group("a group") { test("a test") { //

    your assertions here } group("a nested group") { test("another test") { // your assertions here } } } }) MyTest.kt
  44. object MyTest: Spek({ print("this is the root”) group("some group") {

    print("some group”) it("some test") { print("some test") } } group("another group") { print("another group”) it("another test") { print("another test") } } }) MyTest.kt
  45. // this the root // - some group // -

    - another group // - some test // - - another test
  46. object MyTest: Spek({ beforeGroup { print("before root") } group("some group")

    { beforeEachTest { print("before each test") } it("some test") { print("some test") } afterEachTest { print("before each test") } } afterGroup { print("after root") } }) MyTest.kt
  47. // Instead of this: lateinit var calculator: Calculator beforeEachTest {

    calculator = Calculator() } // You can do: val calculator by memoized { Calculator() }
  48. object CalculatorSpec: Spek({ describe("A calculator") { val calculator by memoized

    { Calculator() } describe("addition") { it("returns the sum of its arguments") { assertThat(3, calculator.add(1, 2)) } } } }) CalculatorSpec.kt
  49. O MockK é um biblioteca de mock otimizada para Kotlin

    e com diversas features interessantes como DSLs, suporte a annotations, argument capturing e outras.
  50. val car = mockk<Car>() every { car.drive(Direction.NORTH) } returns Outcome.OK

    car.drive(Direction.NORTH) // returns OK verify { car.drive(Direction.NORTH) }
  51. @ExtendWith(MockKExtension::class) class CarTest { @MockK lateinit var car1: Car @RelaxedMockK

    lateinit var car2: Car @MockK(relaxUnitFun = true) lateinit var car3: Car @SpyK var car4 = Car() @Test fun calculateAddsValues1() { // ... use car1, car2, car3 and car4 } }
  52. Mais importante do que sair utilizando todas essas bibliotecas é

    realmente entender como elas funcionam, como são construídas e tentar levar as boas ideias para sua rotina. "