first few months with kotlin - introduction through android examples
TRANSCRIPT
Nebojša VukšićAndroid developer @ codecentric
Founder of Kotlin User Group Serbia
Nesh_Wolf
Kotlin User Group Serbia
https://www.meetup.com/Serbia-Kotlin-User-Grouphttps://www.facebook.com/kotlinserbia/
https://twitter.com/kotlin_serbia
What is Kotlin?
● statically-typed object oriented programming language● targeting JVM, Android and JavaScript● fully interoperable with Java● third party library● has excellent IDE support
Kotlin history
● developed by JetBrains● unveiled to public in 2011.(development started in 2012.)● 1.0 first stable version (February 2016.)● current version 1.1 RC
Problems that we have:
Why do we need Kotlin?
● Java○ is too verbose○ burden of previous versions○ Null Pointer Exception
issues○ util “hell”
● Android○ we need inheritance for
almost everything○ api ceremony○ nullability ○ lack of Java 8 features
(lambdas, stream api, method reference...)
Variablesval avenger: String = “Tony Stark” //constants
avenger = “Ultron” // compile error
var age : Int = 18 //variable
age = 20 // compiles since age is mutable
var number = 20 // Int type is inferred
text.length // compiler error
var text: String? = null // This can be null or not-null
Nullabilityvar name: String = null // compile error
text?.length // compiles ⇔ if ( text != null) {
text.length // smart casted to not-nullable type
}
name.length // this is ok since type is not nullable
Nullability
val s: String? = "This can be null or not-null"
val length = s!!.length
Making NPE explicit
Nullability
val s: String? = "This can be null or not-null"
val length = s!!.length
Making NPE explicit
Functionsfun add(a: Int, b: Int): Int {
return a + b
}
Calling functions:
add(1, 3)
log(1, “Num is”)
//“Num is 1”
fun log(num: Int, msg: String): Unit {
println(“$msg $num”)
}
Functionsfun add(a: Int, b: Int): Int = a + b
fun log(num: Int, msg: String): Unit
= println(“$msg $num”)
Calling functions:
add(1, 3)
log(1, “Num is”)
//“Num is 1”
Functionsfun add(a: Int, b: Int) = a + b
fun log(num: Int, msg: String)
= println(“$msg $num”)
Calling functions:
add(1, 3)
log(1, “Num is”)
//“Num is 1”
Functionsfun add(a: Int = 0, b: Int = 0) = a + b
fun log(num: Int, msg: String = “Num is”)
= println(“$msg $num”)
add() //returns 0
add(1) //returns 1
add(b = 1) //returns 1
log(1) // “Num is 1”
log(msg = “Its ”, num = 2)
// “Its 2”
Classes and data classes @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (age != user.age) return false; if (!name.equals(user.name)) return false; return email.equals(user.email); }
@Override public int hashCode() { int result = name.hashCode(); result = 31 * result + email.hashCode(); result = 31 * result + age; return result; }}
class User { private final String name; private final String email; private final int age;
User(String name, String email, int age) { this.name = name; this.email = email; this.age = age; } public String getName() { return name; } public String getEmail() { return email; } public int getAge() { return age; }
@Override public String toString() { return "User{ name='" + name + '\'' + ", email='" + email + '\'' + ", age=" + age + '}'; }
Classes and data classes @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (age != user.age) return false; if (!name.equals(user.name)) return false; return email.equals(user.email); }
@Override public int hashCode() { int result = name.hashCode(); result = 31 * result + email.hashCode(); result = 31 * result + age; return result; }}
class User { private final String name; private final String email; private final int age;
User(String name, String email, int age) { this.name = name; this.email = email; this.age = age; } public String getName() { return name; } public String getEmail() { return email; } public int getAge() { return age; }
@Override public String toString() { return "User{ name='" + name + '\'' + ", email='" + email + '\'' + ", age=" + age + '}'; }
Classes and data classes @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (age != user.age) return false; if (!name.equals(user.name)) return false; return email.equals(user.email); }
@Override public int hashCode() { int result = name.hashCode(); result = 31 * result + email.hashCode(); result = 31 * result + age; return result; }
class User ( val name: String, val email: String, val age: Int)
@Override public String toString() { return "User{ name='" + name + '\'' + ", email='" + email + '\'' + ", age=" + age + '}'; }
Classes and data classesdata class User ( val name: String, val email: String, val age: Int)
val user = User("John Smith", "[email protected]", 24)
val newUser = user.copy(name = "Sam")
//newUser == User("Sam", "[email protected]", 24)
val (name, email, age) = newUser
Data classesdata class User (
val name: String = "John Smith",
val email: String = "[email protected]",
val age: Int = 24
)
val user = User ()
// user == User("John Smith", "[email protected]", 24)
Classes and data classes
data class User( val name: String, val email: String, val age: Int)
● Kotlin classes are final by default● we need to annotate class as open, if we want to inherit them● data classes can’t be inherited
class limitations:
Classes and data classes
open class User( val name: String, val email: String, val age: Int)
● Kotlin classes are final by default● we need to annotate class as open, if we want to inherit them ● data classes can’t be inherited
class limitations:
Extension functionsfun String.swapSpacesForUnderscore(): String = this.replace(" ", "_")
"This is random message!".swapSpacesForUnderscore()
// returns "This_is_random_message!"
Compiles to:
public static final String swapSpacesForUnderscore(@NotNull String $receiver) { Intrinsics.checkParameterIsNotNull($receiver, "$receiver"); return $receiver.replace(," ", "_");}
Extension functionspackage com.app.android.util
fun String.swapSpacesForUnderscore(): String = this.replace(" ", "_")
package com.app.android.main
"Random text!".swapSpacesForUnderscore()
Extension functionspackage com.app.android.util
fun String.swapSpacesForUnderscore(): String = this.replace(" ", "_")
package com.app.android.main
import com.app.android.util.swapSpacesForUnderscore
"Random text!".swapSpacesForUnderscore()
Extension properties
val Array<String>.lastElement: String get() = this[size - 1]
val names: Array<String> = arrayOf("John", "Will", "Emma", "Peter")val name = names.lastElement //returns "Peter"
Compiles to:
public static final String getLastElement(@NotNull String[] $receiver) { Intrinsics.checkParameterIsNotNull($receiver, "$receiver"); return $receiver[$receiver.length - 1];}
Extension properties
val <T> Array<T>.lastElement: T get() = this[size - 1]
val names: Array<String> = arrayOf("John", "Will", "Emma", "Peter")val name = names.lastElement //returns "Peter"
Compiles to:
public static final Object getLastElement(@NotNull Object[] $receiver) { Intrinsics.checkParameterIsNotNull($receiver, "$receiver"); return $receiver[$receiver.length - 1];}
Extension properties
val <T> Array<T>.lastElement: T get() = this[size - 1]
val nums: Array<Int> = arrayOf(1, 2, 3, 4)val num = nums.lastElement //returns 4
Compiles to:
public static final Object getLastElement(@NotNull Object[] $receiver) { Intrinsics.checkParameterIsNotNull($receiver, "$receiver"); return $receiver[$receiver.length - 1];}
Android extensions pluginclass MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main)
val toolbar: Toolbar = findViewById(R.id.toolbar) as Toolbar setSupportActionBar(toolbar)
val fab: FloatingActionButton = findViewById(R.id.fab) as FloatingActionButton fab.setOnClickListener { Toast.makeText(this, "Hello!", Toast.LENGTH_SHORT).show() }}
}
Android extensions pluginclass MainActivity : AppCompatActivity() {
@BindView(R.id.toolbar) Toolbar toolbar;@BindView(R.id.fab) FloatingActionButton fab;
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) ButterKnife.bind(this)
setSupportActionBar(toolbar)
fab.setOnClickListener { Toast.makeText(this, "Hello!", Toast.LENGTH_SHORT).show() }}
}
Android extensions pluginimport kotlinx.android.synthetic.main.activity_main.toolbarimport kotlinx.android.synthetic.main.activity_main.fab
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
fab.setOnClickListener { Toast.makeText(this, "Hello!", Toast.LENGTH_SHORT).show() }}
}
Android extensions pluginimport kotlinx.android.synthetic.main.activity_main.toolbarimport kotlinx.android.synthetic.main.activity_main.fab
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
fab.setOnClickListener { Toast.makeText(this, "Hello!", Toast.LENGTH_SHORT).show() }}
}
Android extensions pluginimport kotlinx.android.synthetic.main.activity_main.toolbarimport kotlinx.android.synthetic.main.activity_main.loging_btn_registration_fragment
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
loging_btn_registration_fragment.setOnClickListener { Toast.makeText(this, "Hello!", Toast.LENGTH_SHORT).show() }}
}
Android extensions pluginimport kotlinx.android.synthetic.main.activity_main.toolbarimport kotlinx.android.synthetic.main.activity_main.loging_btn_registration_fragment as login
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main)
setSupportActionBar(toolbar) login.setOnClickListener { Toast.makeText(this, "Hello!", Toast.LENGTH_SHORT).show()}}
}
Function expressions
val add: (Int, Int) -> Int = { x,y -> x+y }add(1,2)
val validator: (String) -> Boolean ={ value -> value.contains("@") }validator("[email protected]")
● function expressions are blocks of code which we can instantiate(represent as type)
Function expressions
val add: (Int, Int) -> Int = { x,y -> x+y }add(1,2)
● function expressions are blocks of code which we can instantiate(represent as type)
val validator: (String) -> Boolean ={ it.contains("@") }validator("[email protected]")
Function expressions
val add: (Int, Int) -> Int = { x,y -> x+y }add(1,2)
val validator: (String) -> Boolean ={ it.contains("@") }validator("[email protected]") mailTextview.validateWith{ validator("[email protected]") }
● function expressions are blocks of code which we can instantiate(represent as type)
Higher order functionsfun <T> List<T>.filter(predicate: (T) -> Boolean): List<T>{ val items = ArrayList<T>()
return items}
Higher order functionsfun <T> List<T>.filter(predicate: (T) -> Boolean): List<T>{ val items = ArrayList<T>() for (item in this) {
} return items}
Higher order functionsfun <T> List<T>.filter(predicate: (T) -> Boolean): List<T>{ val items = ArrayList<T>() for (item in this) { if (predicate(item)) { items.add(item) } } return items}
val cars = listOf("BMW", "Fiat", "Mercedes", "KIA", "Ford")val filteredCars = cars.filter ({ it.startsWith("F") })
// filteredCars == listOf("Fiat", "Ford")
Higher order functionsfun <T> List<T>.filter(predicate: (T) -> Boolean): List<T>{ val items = ArrayList<T>() for (item in this) { if (predicate(item)) { items.add(item) } } return items}
val cars = listOf("BMW", "Fiat", "Mercedes", "KIA", "Ford")val filteredCars = cars.filter { it.startsWith("F") }
// filteredCars == listOf("Fiat", "Ford")
Quick overview
● Extension functions - adds functionality to types without overriding existing methods
● Function expressions - undeclared function body used as an expression
● Higher order function - function that accepts function or returns function
fun saveUser(user: User) { val editor = sharedPref.edit()
editor.putString(ACCESS_TOKEN, user.token) editor.putString(USER_EMAIL, user.email)
editor.commit()
}
Extension / Higher order function expression combo
fun SharedPreferences.edit(editor : SharedPreferences.Editor, func: () -> Unit) { func() editor.commit()}
Extension / Higher order function expression combofun saveUser(user: User) {
val editor = sharedPref.edit()sharedPref.edit(editor) {
editor.putString(ACCESS_TOKEN, user.token) editor.putString(USER_EMAIL, user.email) }
}
fun SharedPreferences.edit(editor : SharedPreferences.Editor, func: () -> Unit) { func() editor.commit()}
fun saveUser(user: User) { val editor = sharedPref.edit()sharedPref.edit(editor) {
editor.putString(ACCESS_TOKEN, user.token) editor.putString(USER_EMAIL, user.email)
}}
Extension / Higher order function expression combo
fun SharedPreferences.edit(editor : SharedPreferences.Editor, func: () -> Unit) { func() editor.commit()}
fun saveUser(user: User) { sharedPref.edit {
editor.putString(ACCESS_TOKEN, user.token) editor.putString(USER_EMAIL, user.email)
}}
Extension / Higher order function expression combo
fun SharedPreferences.edit(func: () -> Unit) {val editor = edit()
func() editor.commit()}
fun saveUser(user: User) { sharedPref.edit {
it.putString(ACCESS_TOKEN, user.token) it.putString(USER_EMAIL, user.email)
}}
Extension / Higher order function expression combo
fun SharedPreferences.edit(func: (SharedPreferences.Editor) -> Unit) {val editor = edit()
func(editor) editor.commit()}
fun saveUser(user: User) { sharedPref.edit {
it.putString(ACCESS_TOKEN, user.token) it.putString(USER_EMAIL, user.email)
}}
Extension / Higher order function expression combo
fun SharedPreferences.edit(func: (SharedPreferences.Editor) -> Unit) {val editor = edit()
func(editor) editor.commit()}
fun saveUser(user: User) { sharedPref.edit {
it.putString(ACCESS_TOKEN, user.token) it.putString(USER_EMAIL, user.email)
}}
Extension / Higher order function expression combo
fun SharedPreferences.edit(func: SharedPreferences.Editor.() -> Unit)) {val editor = edit()
func(editor) editor.commit()}
fun saveUser(user: User) { sharedPref.edit {
it.putString(ACCESS_TOKEN, user.token) it.putString(USER_EMAIL, user.email)
}}
Extension / Higher order function expression combo
fun SharedPreferences.edit(func: SharedPreferences.Editor.() -> Unit)) {val editor = edit()
editor.func() editor.commit()}
fun saveUser(user: User) { sharedPref.edit {
putString(ACCESS_TOKEN, user.token) putString(USER_EMAIL, user.email)
}}
Extension / Higher order function expression combo
fun SharedPreferences.edit(func: SharedPreferences.Editor.() -> Unit) {val editor = edit()
editor.func() editor.commit()}
Extension / Higher order function expression combo
fun SharedPreferences.Editor.put(pair: Pair<String, Any>) { val key = pair.first val value = pair.second when(value) {
is String -> putString(key, value) is Int -> putInt(key, value) is Boolean -> putBoolean(key, value) is Float -> putFloat(key, value) is Long -> putLong(key, value) else -> error(“Only primitive types are supported”)
}}
fun saveUser(user: User) { sharedPref.edit {
putString(ACCESS_TOKEN, user.token) putString(USER_EMAIL, user.email)
}}
Extension / Higher order function expression combo
fun SharedPreferences.edit(func: SharedPreferences.Editor.() -> Unit)) {val editor = edit()
editor.func() editor.commit()}
Extension / Higher order function expression combofun saveUser(user: User) {
sharedPref.edit {
put(Pair(ACCESS_TOKEN, user.token)) put(Pair(USER_EMAIL, user.email))
}
}
fun SharedPreferences.edit(func: SharedPreferences.Editor.() -> Unit) {val editor = edit()
editor.func() editor.commit()}
fun saveUser(user: User) {
sharedPref.edit {
put(ACCESS_TOKEN to user.token) put(USER_EMAIL to user.email)
}
}
Extension / Higher order function expression combo
fun SharedPreferences.edit(func: SharedPreferences.Editor.() -> Unit) {val editor = edit()
editor.func() editor.commit()}
fun saveUser(user: User) {
sharedPref.edit {
put(ACCESS_TOKEN to user.token) put(USER_EMAIL to user.email)
}
}
Extension / Higher order function expression combo
fun SharedPreferences.edit(func: SharedPreferences.Editor.() -> Unit) {val editor = edit()
editor.func() editor.commit()}
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
fun saveUser(user: User) {
sharedPref.edit {
put(ACCESS_TOKEN to user.token) put(USER_EMAIL to user.email)
}
}
Extension / Higher order function expression combo
inline fun SharedPreferences.edit(func: SharedPreferences.Editor.() -> Unit) {val editor = edit()
editor.func() editor.commit()}
Extension / Higher order function expression comboFrom this:
fun saveUser(user: User) {val editor = sharedPref.edit()
editor.putString(ACCESS_TOKEN, user.token) editor.putString(USER_EMAIL, user.email)
editor.commit()
}
To this:
inline fun saveUser(user: User) { sharedPref.edit {
put(ACCESS_TOKEN to user.token) put(USER_EMAIL to user.email)
}
}
Summary● immutable and mutable variables● nullability● functions(default values and named arguments)● classes and data classes● extension functions and properties● function expression● higher order functions● ultra mega giga combo of three above concepts● use inline modifier
Resources● Official Kotlin documentation● Official Kotlin Github● Anko ● Kotlin koans● Awesome Kotlin – collection of materials● Slack kanal● Design patterns in Kotlin● Keddit - demo app● Kotlin For Android (at DevFest İzmir 2016)● Kotlin – Ready for Production – Hadi Hariri● Android development with Kotlin – Jake Wharton
Nebojša VukšićAndroid developer @ codecentric
Founder of Kotlin User Group Serbia
Nesh_Wolf
Kotlin User Group Serbia
https://www.meetup.com/Serbia-Kotlin-User-Group
https://www.facebook.com/kotlinserbia/https://twitter.com/kotlin_serbia