StxNext Lightning Talks - Mar 11, 2016
Kotlin Advanced - language reference for Android developers
This presentation contains the second talk on Kotlin language we had at STXNext. We try go deeper into language specifics and look at the positive impact new syntax can have on boilerplate removal and readability improvement.
Kotlin really shines in Android development when one looks at “Enum translation”, “Extension functions”, “SAM conversions”, “Infix notation”, “Closures” and “Fluent interfaces” applied to lists. The talk, however, compares language-specifics of Java & Kotlin in terms of “Type Variance”, “Generics” and “IDE tools” as well.
We present real-world example based on Stx-Insider project written in Kotlin which incorporates Dagger 2, Kotterknife, Retrofit2 and is composed of 5+ Activities.
Full agenda
Live templates
Enum translation
Calling extension functions from Kotlin/Java
Constructors with backing fields
Warnings
F-bound polymorphism
Variance (Covariance/Contravariance)
Variance comparison in Kotlin/Java/Scala
Annotation processing - KAPT
SAM conversions
Type equality
Lambda vs Closure
Reified generics
Fluent interfaces
Infix notation
Static extension methods in Kotlin
Generic types
Sealed classes
Dokka - documentation in Kotlin
J2K converter
Real-world example
Reflection
Presentation is accompanied with an example project (StxInsider):
https://github.com/kosiara/stx-insider
6. Enum translation
Kotlin
enum class SliderActivityType
private constructor(val title: Int) {
PORTFOLIO(R.string.portfolio),
TEAM(R.string.team)
}
Java
public enum SliderActivityType {
PORTFOLIO(R.string.portfolio),
TEAM(R.string.team);
private final int title;
SliderActivityType(int title) {
this.title = title;
}
public int getTitle() {
return title;
}
}
● easier to read
● more concise
● help to avoid typos and boilerplate code
In Kotlin translated enums are:
7. Calling extension
functions
Get app-version ext. function:
@file:JvmName("ActivityUtil") //@file:JvmMultifileClass
package com.stxnext.stxinsider.util
fun Activity.getAppVersion(activity: Activity): String {
try {
val manager = activity.packageManager
val info = manager.getPackageInfo(activity.packageName, 0).versionName
} catch (e: PackageManager.NameNotFoundException) { /* ignore */ }
return "0.0.0"
}
Kotlin call:
versionTextView.setText(getAppVersion(this))
Java call:
versionTextView.setText(ActivityUtil.getAppVersion(MainActivity.this,
MainActivity.this));
8. Constructors with
backing fields
● using constructors with backing fields in
a proper way saves a lot of boiler-plate
code
“Java-style”
kotlin code:
class TeamCategoryFragment : Fragment() {
internal val TAG = TeamCategoryFragment::class.simpleName
lateinit var teamCategoryHeader: TeamCategoryHeader
lateinit var teamListRecyclerView: RecyclerView
fun teamCategoryHeader (teamCategoryHeader: TeamCategoryHeader): TeamCategoryFragment {
this.teamCategoryHeader = teamCategoryHeader
return this
}
override fun onCreateView(): View? {
val view = inflater!!.inflate(R.layout.fragment_layout, container, false)
teamListRecyclerView = view.findViewById(R.id.fragment_list) as RecyclerView
return view;
}
}
class TeamCategoryFragment (var teamCategoryHeader: TeamCategoryHeader) : Fragment() {
internal val TAG = TeamCategoryFragment::class.simpleName
lateinit var teamListRecyclerView: RecyclerView
override fun onCreateView(): View? {
val view = inflater!!.inflate(R.layout.fragment_layout, container, false)
teamListRecyclerView = view.findViewById(R.id.fragment_list) as RecyclerView
return view;
}
}
Contructor
with backing
field
9. Warnings
Checks:
Full list of supression contants:
https://github.com/JetBrains/kotlin/blob/master/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java
@Suppress("UNCHECKED_CAST")
@Suppress("CANNOT_CHECK_FOR_ERASED")
@Suppress("SENSELESS_COMPARISON")
@Suppress("GENERIC_THROWABLE_SUBCLASS")
etc.
10. F-bound
polymorphism
Kotlin:
interface Movable {
Movable move(int x, int y);
}
class Car implements Movable {
@Override
public Movable move(int x, int y) {
return this;
}
}
public class FBoundedExample {
Movable moveMeOneInch (Movable m)
{ return m.move(1,1); }
<T extends Movable > T moveMeOneInchFBound (T m)
{ m.move( 1,1); return m; }
Car car1 = (Car) moveMeOneInch( new Car());
Car car2 = moveMeOneInchFBound( new Car());
}
Java equivalent:
interface Movable {
fun move(x: Int, y: Int): Movable { return this }
}
class Car : Movable
fun moveMeOneInch(m: Movable): Movable {
return m.move(1, 1)
}
fun <T : Movable> moveMeOneInchFBound(m: T): T {
m.move(1, 1)
return m
}
val car1:Car = moveMeOneInch(Car()) as Car
val car2:Car = moveMeOneInchFBound(Car())
● in moveMeOneInch() we have to do
unsafe casting to (Car)
● in moveMeOneInchFBound() no
casting in necessary
● Kotlin&Java work the same but the
syntax is differrent
(<T : Movable> vs <T extends Movable>
● upper-bound generics
no casting necessary
11. Covariance
Notes:
● Cat is a subclass of Animal
● Animal shelter puts animals and
gets them for adoption
● We want to implement
CatShelter and get&put Cats
open class Animal
class Cat : Animal()
open class AnimalShelter {
open fun getAnimalForAdoption() : Animal {
return Animal()
}
open fun putAnimal(animal : Animal) {
}
}
Base class:
class CatShelter : AnimalShelter() {
//covariant method return type
override fun getAnimalForAdoption(): Cat {
return Cat()
}
//covariant method argument type - NOT POSSIBLE
override fun putAnimal(animal: Cat) { //overrides nothing
super.putAnimal(animal)
}
}
Derived class:
12. Covariance
Notes:
● Cat is a subclass of Animal
● Animal shelter puts animals and
gets them for adoption
● We want to implement
CatShelter and get&put Cats
open class Animal
class Cat : Animal()
open class AnimalShelter2<in T> {
open fun getAnimalForAdoption() : Animal {
return Animal()
}
open fun putAnimal(animal : T) {
}
}
Base class:
class CatShelter2 : AnimalShelter2<Cat>() {
//covariant method return type
override fun getAnimalForAdoption(): Cat {
return Cat()
}
//covariant method argument type
override fun putAnimal(animal: Cat) {
super.putAnimal(animal)
}
}
Derived class:
13. Contravariance
Notes:
● Cat is a subclass of Animal
● Animal shelter puts animals and
gets them for adoption
● We want to implement
CatShelter and get&put Cats
open class Animal
class Cat : Animal()
abstract class AnimalShelter3<out T> {
abstract fun getAnimalForAdoption() : T
open fun putAnimal(animal : Animal) { }
}
Base class:
class CatShelter3 : AnimalShelter3<Any>() {
//contravariant method return type
override fun getAnimalForAdoption(): Any {
return Cat()
}
}
Derived class:
14. Covariance
Preserve List<T> type
safety in Java:
List<String> strs = new ArrayList<String>();
List<Object> objs = strs; // Java prohibits this! (Incompatible types)
objs.add(1); //Cannot cast exception if compilation allowed the above line
● generic types in Java are invariant
List<String> is not a subtype of List<Object>
● wildcard types in Java allow method
argument type to be covariant
Collection<String> is a subtype of Collection<? extends Object>
(extends bound/upper-bound)
interface Collection<E> ... {
void addAll(Collection<? extends E> items);
}
which is why addAll() method
from Collection<E> is:
● doesn’t have wildcard
types
● uses declaration-site
variance and type
projections instead
● star-projections are
present
● generic constraints
(upper-bounds) are
possible
KOTLIN:
use-site variance
15. Variance
comparison
JAVA
● uses wildcards to express variance
● wildcards (use-site variance) are very
expressive but also hard to understand
and inconvienient for programmers
KOTLIN
● developed mixed-site variance system in
collaboration with Ross Tate
● combination of definition-site and use-site
variance that avoids the failings of wildcards
C<? extends T>
covariant instantiation of C
i.e.
“C-of-some-subtype-of-T”.
Example:
fun <T : Comparable<T>> sort(list: List<T>) {}
open class AnimalShelter2< in T> {}
open class AnimalShelter2< out T> {}
16. Annotation
processing
Implementation state:
● old implementation: kapt worked by intercepting
communication between annotation processors and javac,
and added already-compiled Kotlin classes on top of the
Java classes
● new implementation of KAPT: generates stubs of Kotlin classes
before running javac and llows the usage of APT based
libraries we already have at our current java stack
KAPT
kapt {
generateStubs = true
}
dependencies {
kapt 'com.google.dagger:dagger-compiler:2.0.2'
}
Example config:
● JSR 269 Annotation
Processing available in Kotlin
since M12
● Dagger 2 works :)
● DBFlow works
17. SAM
conversions
SAM call:
● function literals can be converted into implementations
of Java interfaces with a single non-default method
● the parameter types of the interface method must match
the parameter types of the Kotlin function
Lambda call:
mInsiderApiService.getTeamsAsync({ list ->
list.forEach { item -> print(item.description) } },
{ /* do nothing on error */ } )
versionTextView.setOnClickListener(
View.OnClickListener { print("Message content") } )
executor.execute(Runnable {
println("This runs in a thread pool") })
mInsiderApiService.getTeamsAsync(
object : Callback<List<SliderItem>> {
override fun onResponse(
p0: Call<List<SliderItem>>?,
response: Response<List<SliderItem>>?) {
/* something */ }
override fun onFailure(
p0: Call<List<SliderItem>>?,
p1: Throwable?) { /* something */ }
})
Java interface call:
18. Type equality
Checking type equality
BaseClass
TallItemView
if (this instanceof TallItemView) { .... }
// instanceof makes it very easy to be asymmetric
if (this.getClass()
.equals(TallItemView.class) ) { .... }
Java
if (this.javaClass
.isAssignableFrom(TallItemView::class.java) )
Kotlin
Referential equality
referential equality is checked by the ===
operation
Structural equality
structural equality is checked by the ==
operation
== is translated to: a?.equals(b) ?: (b === null)
a == null is translated to: a === null
if a is not null, it calls the equals(Any?)
function, otherwise b === null
19. Lambda vs
Closure
LAMBDA CLOSURE
● language construct
● a syntax for
anonymous function
● can be assigned to a
variable
● “closes over” the
environment in which
it was defined
● lambda which
references fields
external to its body
● function which is
evaluated in its own
environment
20. Reified generics
● in Java generic type parameters are not
reified: they are not available at runtime
● for inline functions
(inserting the function code at the address of each function call)
● safe casting of generic types
● problem comes from type-erasure
class MyClass<T> {
private final T o;
public MyClass() {
this.o = new T(); //type parameter ‘T’ cannot be instantiated directly
}
}
Example (Java)
public class MyClass2<T> {
@SuppressWarnings("unchecked")
public T doSomething() {
return (T) new MyClass(); //unchecked cast
}
}
class MyClass2 {
inline fun <reified T> doSomething() : T {
return MyClass() as T;
}
}
class MyClass
Kotlin
21. Fluent interfaces
Kotlin:
https://plugins.jetbrains.com/plugin/7903
Fluent setter generator plugin for Android Studio:
(JAVA)
public SampleActivity firstVariable(int firstVariable) {
this.firstVariable = firstVariable;
return this;
}
public SampleActivity secondVariable(int secondVariable) {
this.secondVariable = secondVariable;
return this;
}
● almost like an internal DSL
● ideal for filtering, creating,
customizing etc.
● used for model classes
Java:
Snakbar snack = Snackbar
.make(mainView , "Sample snackbar" , Snackbar.LENGTH_LONG)
.setAction( "Undo", undoClickListener) ;
Fluent interface example:
class Person(val name: String) {
val parents : List<String> = arrayListOf()
constructor(name: String, parent: String)
: this(name) {
parents.plus(parent)
}
fun parent(parent: String): Person {
parents.plus(parent); return this
}
}
22. Fluent interfaces /**
* Fluent sort
*/
fun <T : kotlin.Comparable<T>> kotlin.collections.MutableList<T>.
sortList(): MutableList<T> {
sort()
return this
}
/**
* For-each fluent interface
*/
fun <T : kotlin.Comparable<T>> kotlin.collections.MutableList<T>.
forEachList(action: (T) -> kotlin.Unit): MutableList<T> {
for (elem in this)
action.invoke(elem)
return this
}
Additional functions:
Fluent lists example:
val list = listOf(1, 2, 3, 4, 5, 6, 7, 8)
list .forEach { println(it) }
list forEachLoop { println(it) }
/**
* In-place forEach loop (discouraged in 1.0 release)
*/
infix fun <T> kotlin.collections.Iterable<T>
.forEachLoop(action: (T) -> kotlin.Unit): kotlin.Unit {
this.forEach { action }
}
val outList = list
.filter { it < 100 }
.filterNot { it == 1 }
.toMutableList()
.sortList()
.forEachList { it + 1 }
.filter { it % 2 == 0 }
.first()
//result: 2
23. Infix notation
infix fun Int.minus(x: Int): Int {
return this.minus(x)
}
infix extension function:
val result = 1 minus 2
println(3 minus 4)
type isPortfolio { println("Do something") }
type isTeam { println("Do something else") }
infix fun SliderActivityType.isPortfolio( execClosure : () -> Unit ) {
if (this.equals(SliderActivityType.PORTFOLIO))
execClosure.invoke()
}
infix fun SliderActivityType.isTeam( execClosure : () -> Unit ) {
if (this.equals(SliderActivityType.TEAM))
execClosure.invoke()
}
this displayToast "This is a message"
infix fun Activity.displayToast(txt : String) {
Toast.makeText(this, txt, Toast.LENGTH_SHORT).show()
}
● only one method
argument
● function literal can be
passed outside the
parentheses
24. Infix notation
if (this isGranted Manifest.permission.READ_CONTACTS) {
//do something here
}
infix fun Activity.isGranted(permissionStr : String) :
Boolean {
if (ContextCompat.checkSelfPermission(this,
permissionStr) !=
PackageManager.PERMISSION_GRANTED)
return false
return true
}
Handle Android
permissions check:
this loge "I really don't like errors"
infix fun Activity.loge(txt : String) {
Log.e(this.javaClass.simpleName, txt)
}
Log errors:
25. Static extension
methods
fun Util.isDeviceOnline(context: Context): Boolean {
val connMgr = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo = connMgr.activeNetworkInfo
return networkInfo != null && networkInfo.isConnected
}
fun Activity.isDeviceOnline(context: Context) = { Util().isDeviceOnline(context) }
fun OkHttpClient.isDeviceOnline(context: Context) = { Util().isDeviceOnline(context) }
Workaround (extension method for multiple classes):
http://stackoverflow.com/questions/28210188/static-extension-methods-in-kotlin
● In Kotlin 1.0 there is no possibility to add a static extension method
26. Generic types
Create generic object instance:
● in JVM generic types are lost due to type erasure
class MyClass<T> {
private final T o;
public MyClass() {
this.o = new T(); //type parameter ‘T’ cannot be instantiated directly
}
}
27. Generic types
val bindFunc = { baseView: FrameLayout , item: ListItem , position: Int , clickListener: View.OnClickListener ->
val nameTextView = baseView.findViewById(R.id. item_simple_list_main_header) as TextView
nameTextView. text = item.title
baseView.setOnClickListener(clickListener)
}
val adapter = SimpleItemListAdapter<ListItem , ListItemView<ListItem>>(onClickFunc ,
{ ListItemView <ListItem> (R.layout. item_simple_list, bindFunc , baseContext , null /* attrs */ ) } );
Create generic object instance:
● in JVM generic types are lost due to type erasure
override fun onCreateItemView(parent: ViewGroup, viewType: Int): TView {
val view = factory()
return view as TView
}
//not really convenient
override fun onCreateItemView(parent: ViewGroup, viewType: Int, classParam : Class<T>): T {
val v: T = classParam.constructors[0].newInstance(mContext, null) as T
return v as T
}
28. Sealed classes
sealed class Pet(val name: String) {
class Dog(name: String): Pet(name)
class Cat(name: String): Pet(name)
}
Sealed class:
fun Pet.saySomething(): String {
return when (this) {
is Dog -> "woof"
is Cat -> "meow"
}
}
● algebraic data type - i.e. composite data
type which is formed by combining other
types
● sealed classes - instead of open classes
with private constructors
● “when” statement without “else” clause
● “Pet” cannot have other subclasses than
“Dog” and “Cat”
● since M13
// private constructor to prevent creating more subclasses outside
open class Pet private(val name: String) {
class Dog(name: String): Pet(name)
class Cat(name: String): Pet(name)
}
29. Dokka
What is dokka:
KDoc documentation:
● similar do Javadoc
● supports stale Javadoc out of the box
● markdown support included
/**
* # Beacon SDK initialization
*
* This method registers 3 beacons with IDs taken from Estimote Cloud.
* Invoke this method in [onCreate].
*
* ## Showcase demo
*
* Steps:
* * Grant application bluetooth, GPS and WiFi permissions
* * Wait for about 1 minute (beacons broadcast in 0.1 ~ 2.0 sec intervals)
* * Snackbar should appear on the app's main activity
*
*/
private fun initializeNearables() { ….. }
● generates documentation in
html/markdown/javadoc
● maintained by Jetbrains
● generated from
gradle/maven/ant
● standalone executable jar
available
● [ref] instead of @see ref
● https://github.com/Kotlin/dokka
33. Real-world
example
Method count:
Library Method count
joda-time 1707
converter-gson 236
com.google.android.gms 1912
kotterknife 456
com.google.dagger 290
retrofit-2.0.0-beta2 593
com.android.support/design 1017
com.android.support/recyclerview-v7 1579
LoC: 1857 kotlin + 503 java lines
Compilation time (debug): 47.135 sec (6 sec incremental)
Compilation time (release): 53.173 sec
(proguard + zipaligning)
Mac Mini late 2014, SSD 256 GB drive USB-3.0
2.6 GHz Intel Core i5
8 GB 1600 MHz DDR3, OsX El Capitan 10.11.2 (15C50)
Try it yourself - STXInsider example project:
https://github.com/kosiara/stx-insider
34. Reflection
Calling reflection:
Text text text
dependencies {
compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.0'
}
● Reflection is moved into separate *.jar which reduces the
size of runtime library
val javaMethod = util.javaClass.methods[0]
val javaConstructor = util.javaClass.constructors[0]
val utilInstance = javaConstructor.newInstance()
javaMethod.invoke(utilInstance)
Java reflection in Kotlin:
val classRef: KClass<Util> = Util::class
val constructor = classRef.constructors.first()
val method = classRef.functions.first()
val util = constructor.call()
method.call(util)
val aFun = classRef.functions
.filter { it.name.contains("aaa") }.first()
val bFun = classRef.functions.filter {
it.parameters.size == 1
}.first()
aFun.call(util)
bFun.call(util)