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.2Or 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 childrensnackbar(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