Android
Kotlin
Dev

About me

Alejandro Vidal Rodriguez (alex)
Agile Engineer @TribalScale

www.tribalscale.ae

we are hiring!

Agenda

  • Set up our first Kotlin project

  • Our first activity

  • Data classes

  • MVP

  • High order functions & lambdas

  • Connecting data

  • Extension functions & companion objects

  • Null safety

  • Default values

  • Anko & coroutines

  • Delegated methods and variables

  • Under the hood

  • Where to go from here

What are we building?

A pokedex!

Repository


    git clone git@github.com:goofyahead/KotlinWorkshop.git

    git checkout step-1

Hello kotlin!

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
...
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

step-1

Well done!

Our first activity

class PokemonListActivity : AppCompatActivity(), PokemonListContract.View {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_pokemon_list)
    }

    override fun onPokemonsReady(pokemons: List<Pokemon>) {
        TODO("not implemented")
    }
}
interface PokemonListContract {

    interface Presenter {
        fun loadPokemons(...)
    }
}

step-2

step-2

Data classes

data class Pokemon(
        val name: String,
        val types: List<String>,
        val weight: Int,
        val height: Int)

vs

public class PokemonJava {
    
    private String name;
    private List<String> types; 
    private int weight;
    private int height;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getTypes() {
        return types;
    ...

step-2

val, var & lateinit

private val tag : String = ""

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_pokemon_list)
    tag = "other" // cant be changed
}

private lateinit var tag : String

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_pokemon_list)
    tag = "other"
}

MVP

step-3


class PokemonListPresenter(
        private val pokemonRepository: PokemonRepository) 
: PokemonListContract.Presenter {

    override fun loadPokemons(callback: (List<Pokemon>) -> Unit) {
        callback(pokemonRepository.getAllPokemons())
    }

}

the standard way

step-3

interface PokemonListContract {
    
    interface View {
       fun onPokemonsReady(pokemons : List<Pokemon>)
    }
    
    interface Presenter {
        fun register (view : View)
        fun unregister(view : View)
        fun loadPokemons()
    }
}
class PokemonListPresenter(
        private val pokemonRepository: PokemonRepository,
        private val pokemonListView: PokemonListContract.View) 
: PokemonListContract.Presenter {

    override fun loadPokemons() {
        val pokemons = pokemonRepository.getAllPokemons()
        pokemonListView.onPokemonsReady(pokemons)
    }
}

High order functions

step-3

fun <T> lock(lock: Lock, body: () -> T): T {
    lock.lock()
    try {
        return body()
    }
    finally {
        lock.unlock()
    }
}

val result = lock(lock, ::methodToBeSynchronized) 
// {callToMethodToBeSynchronized()} < Kotlin 1.2

Or lambdas

step-3

fun <T> lock(lock: Lock, body: () -> T): T {
    lock.lock()
    try {
        return body()
    }
    finally {
        lock.unlock()
    }
}

val result = lock(lock, { 
    val first = it.getFirst()
    doSomethingWith(first) 
} 

Not that simple, I know

step-3

Connecting stuff

step-4

class PokemonAdapter(private val pokemonList: List<Pokemon>,
                     private val itemClick: (Pokemon) -> Unit)
    : RecyclerView.Adapter<PokemonAdapter.ViewHolder>() {

    override fun getItemCount(): Int {
        return pokemonList.size
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context)
                  .inflate(R.layout.pokemon_item, parent, false)
        return ViewHolder(view, itemClick)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bindPokemon(pokemonList[position])
    }

    class ViewHolder(view: View,
                     private val itemClick: (Pokemon) -> Unit)
        : RecyclerView.ViewHolder(view) {

        fun bindPokemon(pokemon: Pokemon) {
            with(pokemon) {
                itemView.pokemonName.text = name
                itemView.pokemonWeight.text = weight.toString()
                itemView.pokemonHeight.text = height.toString()
                itemView.setOnClickListener { itemClick(this) }
            }
        }
    }
}

Using the adapter

step-4

class PokemonListActivity : AppCompatActivity() {

    private val presenter = PokemonListPresenter(NetworkPokemonRepository())

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_pokemon_list)
        presenter.loadPokemons({onPokemonsReady(it)})
    }

    fun onPokemonsReady(pokemons: List<Pokemon>) {
        pokedexList.layoutManager = LinearLayoutManager(this)
        pokedexList.adapter = PokemonAdapter(pokemons,{onPokemonClicked(it)})
    }

    fun onPokemonClicked(pokemon: Pokemon){
        TODO("not implemented yet")
    }
}
import kotlinx.android.synthetic.main.activity_pokemon_list.*

Where are the findViewById

Our sample repository

step-4

class NetworkPokemonRepository : PokemonRepository {

    override fun getAllPokemons(): List<Pokemon> {
        val bulbasur = Pokemon(
                "bulbasur",
                "https://cdn.bulbagarden.net/upload/thumb/2/21/001Bulbasaur.png/250px-001Bulbasaur.png",
                listOf("grass", "poison") ,
                12,
                12)
        val pikachu = Pokemon(
                "pikachu",
                "https://cdn.bulbagarden.net/upload/thumb/0/0d/025Pikachu.png/250px-025Pikachu.png",
                listOf("electric") ,
                4,
                20)
        val charmander = Pokemon(
                "charmander",
                "https://cdn.bulbagarden.net/upload/thumb/7/73/004Charmander.png/250px-004Charmander.png",
                listOf("fire", "flying") ,
                2,
                32)
        return listOf(bulbasur, pikachu, charmander)
    }
}

The result

step-4

Profit

step-4

Extensions vs static methods

public class StringUtil {

    public static String getStationGenre(Station station) {
        String genre;

        if (station.genres() != null
                && !station.genres().nodes().isEmpty()
                && station.genres().nodes().get(0).genre() != null {
            genre = station.genres().nodes().get(0).genre().name();
        } else {
            genre = station.category().name();
        }

        return genre;
    }

    public static String join(CharSequence delimeter, String... items) {
        return TextUtils.join(delimeter, items);
    }
    
    ...
StringUtils.getGenre(mySong);

step-5

Another PR with stringUtils

step-5

Extension functions

step-5

package com.example.tsl057.kotlinworkshop.extensions

import android.app.Activity
import android.widget.Toast

fun Activity.showMessage(message : String) {
    Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
import com.example.tsl057.kotlinworkshop.extensions.md5
import com.example.tsl057.kotlinworkshop.extensions.showMessage
...

class PokemonListActivity : AppCompatActivity() {

    ...

    fun onPokemonClicked(pokemon: Pokemon){
        this.showMessage("Clicked on ${pokemon.name}")
    }
}

Samples

step-5

import java.nio.charset.Charset
import java.security.MessageDigest

fun String.md5(): String {
    val digester = MessageDigest.getInstance("MD5")
    digester.update(this.toByteArray(Charset.defaultCharset()))
    return digester.digest().toHex()
}
private val HEX_CHARS = "0123456789ABCDEF".toCharArray()

fun ByteArray.toHex() : String{
    val result = StringBuffer()

    forEach {
        val octet = it.toInt()
        val firstIndex = (octet and 0xF0).ushr(4)
        val secondIndex = octet and 0x0F
        result.append(HEX_CHARS[firstIndex])
        result.append(HEX_CHARS[secondIndex])
    }

    return result.toString()
}

Motivation

step-5

// Java
Collections.swap(list, list.get(3), list.get(4));

// Java
list.swap(list.get(3), list.get(4));

In Java, we are used to classes named "*Utils": FileUtils, StringUtils and so on.

So we implement swap in ALL the lists?

// Kotlin
fun Collections.swap(<T> a, <T> b) : Collection<T> {...}

the famous null safety


data class Pokemon(
        val name: String?,
        val picture: String?,
        val types: List<String>?,
        val weight: Int,
        val height: Int)

step-6

val unknownPokemon = Pokemon(
                null,
                null,
                null ,
                10,
                22)

return listOf(bulbasur, pikachu, charmander, unknownPokemon, null)

Elvis operator

step-6

class ViewHolder(view: View,
                 private val itemClick: (Pokemon) -> Unit)
    : RecyclerView.ViewHolder(view) {

    fun bindPokemon(pokemon: Pokemon) {
        with(pokemon) {
            Picasso.with(itemView.context)
                    .load(picture ?: "https://defaulturl.png" )
                    .into(itemView.pokemonImage)
            itemView.pokemonName.text = name ?: "unknown"
            itemView.pokemonWeight.text = weight.toString()
            itemView.pokemonHeight.text = height.toString()
            itemView.setOnClickListener { itemClick(this) }
        }
    }
}

?.let

step-6

class PokemonAdapter(private val pokemonList: List<Pokemon?>,
                     private val itemClick: (Pokemon) -> Unit)
    : RecyclerView.Adapter<PokemonAdapter.ViewHolder>() {

    ...

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        pokemonList[position]?.let { holder.bindPokemon(it) }
    }

    ...
}
class PokemonListPresenter(
        private val pokemonRepository: PokemonRepository)
    : PokemonListContract.Presenter {

    override fun loadPokemons(callback: (List<Pokemon?>) -> Unit) {
        callback(pokemonRepository.getAllPokemons().filterNotNull())
    }
}

or just filter them

we control null

step-6

default values

data class Pokemon(
        val name: String?,
        val picture: String = "https://images/31QUl4LeuNL.jpg",
        val types: List<String>?,
        val weight: Int,
        val height: Int)

step-7

val unknownPokemon = Pokemon(
                name = null,
                types = null,
                weight = 10,
                height = 22)

return listOf(bulbasur, pikachu, charmander, unknownPokemon, null)

We need filter

step-7

when + var lambdas

 override fun onOptionsItemSelected(item: MenuItem): Boolean {
    filterFunction = when (item.itemId) {
        R.id.electric -> { pokemon: Pokemon -> pokemon.types?.contains("electric") ?: false }

        R.id.fire -> { pokemon: Pokemon -> pokemon.types?.contains("fire") ?: false }

        R.id.flying -> { pokemon: Pokemon -> pokemon.types?.contains("flying") ?: false }

        else -> { pokemon: Pokemon -> true }
    }
    presenter.loadPokemons({ onPokemonsReady(it) })
    return true
}

step-7

private var filterFunction: (Pokemon) -> Boolean = { true }

...

fun onPokemonsReady(pokemons: List<Pokemon>) {
    pokedexList.layoutManager = LinearLayoutManager(this)
    pokedexList.adapter = PokemonAdapter(pokemons.filter(filterFunction), 
                                        { onPokemonClicked(it) })
}

anko

dependencies {
    compile "org.jetbrains.anko:anko:$anko_version"
}

step-8

Anko Commons: a lightweight library full of helpers for intents, dialogs
Anko Layouts: a fast and type-safe way to write dynamic Android layouts
Anko SQLite: a query DSL and parser collection for Android SQLite
Anko Coroutines: utilities based on the kotlinx.coroutines library.

logging

class PokemonListActivity : AppCompatActivity(), AnkoLogger {

...

    fun onPokemonClicked(pokemon: Pokemon) {
        this.showMessage("Clicked on ${pokemon.name}")
        info("md5 of this pokemon is ${pokemon.toString().md5()}")
        debug(pokemon)
    }

}

step-8


I/PokemonListActivity: md5 of this pokemon is 4DAD79C592A...

I/PokemonListActivity: Pokemon(name=pikachu, picture=https...

Helpers

startActivity<SomeOtherActivity>("id" to 5)

step-8

applyRecursively()

//apply a lambda to view and all its children
snackbar(view, "Hi there!")
snackbar(view, R.string.message)
longSnackbar(view, "Wow, such duration")
snackbar(view, "Action, reaction", "Click me!") { doStuff() }

co-routines

dependencies {
    compile "org.jetbrains.anko:anko-coroutines:$anko_version"
}

step-8

bg {
    // Runs in background
    info("so tired....")
    Thread.sleep(2000)
    info("waking up!")
}

Delegates

step-9

interface Nameable {
    var name: String
}

class JackName : Nameable {
    override var name: String = "Jack"
}

class LongDistanceRunner: Runnable {
    override fun run() {
        println("run a lot")
    }
}

class Person(name: Nameable, runner: Runnable): Nameable by name, Runnable by runner

fun main(args: Array<String>) {
    val person = Person(JackName(), LongDistanceRunner())
    println(person.name) //Jack
    person.run() //long
}

Java way

step-9

class View {
    
  void show() {
    print("View.show()");  
  }
    
}

class Screen {

  private View view; // delegation link
  
  public Screen(View view) {
    this.view = view;
  }

  void show() {
    view.show();
  }
  
}

What?

step-10

Duck test in a nice way

step-9


interface Attacker {
    fun attack(pokemon: Pokemon)
}
class BasicAttack : Attacker, AnkoLogger {
    override fun attack(pokemon: Pokemon) {
        pokemon.lifePoints -= 10
        info("well kinda basic attack")
    }
}
class ElectricAttack : Attacker, AnkoLogger {
    override fun attack(pokemon: Pokemon) {
        pokemon.types?.map {
            damage = when (it) {
                FIRE -> {
                    ...
                }
                POISON -> {
                    ...
                }
            }
        }
}

delegated properties

step-9

class LifeDelegate {

    operator fun getValue(thisRef: Any?, property: KProperty<*>, value: Int): Int {
        return value
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
        println("$value has been assigned to '${property.name} in $thisRef.'")
    }
}
class DatabaseDelegate<in R, T>(readQuery: String, writeQuery: String, id: Any) {
    
    fun getValue(thisRef: R, property: KProperty<*>): T {
        return queryForValue(readQuery, mapOf("id" to id))
    }
 
    fun setValue(thisRef: R, property: KProperty<*>, value: T) {
        update(writeQuery, mapOf("id" to id, "value" to value))
    }
}

Pikachu GO!

step-9

Kotlin is only a language

is our responsability

How to migrate

  • Start small (with your model classes)
  • Train your coworkers
  • Become an expert to answer all the questions that will raise
  • Explore similar concepts like RxJava, annotated classes, Java8, Lombok...
  • Some concepts will change the way you think about a problem, so give it a few days to settle
  • Not everyone will be willing to change, help them, show them why is great
  • Have a side project to test all your crazy ideas, challenge yourself!

Under the hood

What more?

 

  • Integration with Java libs
    https://github.com/segunfamisa/retrofit-kotlin-sample
  • Kotlin Koans https://kotlinlang.org/docs/tutorials/koans.html
  • annotations @jvmstatic
    https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/
  • Kotlin 1.2
    https://blog.jetbrains.com/kotlin/2017/09/kotlin-1-2-beta-is-out/

Statements on the web

Kotlin costs nothing to adopt 

It enforces no particular philosophy of programming

Adopting Kotlin is low risk

 It can be learned in a few hours

Questions?

contact

goofyahead@gmail.com

https://medium.com/@goofyahead

Kotlin development

By Alejandro Vidal Rodriguez

Kotlin development

  • 25