kotlin advanced - language reference for android developers
TRANSCRIPT
Kotlin lang - advanced(Android projects)
Bartosz Kosarzycki - StxNext Lightning Talks - Mar 11, 2016
talented developers | flexible teams | agile experts
Kotlin basics - view here
Stable 1.0 version is available from Feb 15, 2016
Kotlin lang - advanced1. Live templates2. Enum translation3. Calling extension functions from Kotlin/Java4. Constructors with backing fields5. Warnings6. F-bound polymorphism7. Variance (Covariance/Contravariance)8. Variance comparison in Kotlin/Java/Scala9. Annotation processing - KAPT
10. SAM conversions11. Type equality12. Lambda vs Closure13. Reified generics14. Fluent interfaces15. Infix notation16. Static extension methods in Kotlin17. Generic types18. Sealed classes19. Dokka - documentation in Kotlin20. J2K converter21. Real-world example22. Reflection
AGENDA
Live templates
exfunfun Any.f(): Unit { }
closure{ x -> x }
iterfor (i in iterable) { }
● let’s start with something simple - templates
Live templates
exvarvar Any.v: Any get() { } set(value) {
}
mainfun main(args: Array<String>) { }
innif (i != null) { }
Enum translation
Kotlinenum class SliderActivityType private constructor(val title: Int) { PORTFOLIO(R.string.portfolio), TEAM(R.string.team)}
Javapublic 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:
Calling extension
functions
Get app-version ext. function:@file:JvmName("ActivityUtil") //@file:JvmMultifileClasspackage 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));
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
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.
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 Carval 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
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 Animalclass 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:
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 Animalclass 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:
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 Animalclass 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:
CovariancePreserve 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 invariantList<String> is not a subtype of List<Object>
● wildcard types in Java allow method argument type to be covariantCollection<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() methodfrom 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
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 Ci.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> {}
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
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:
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 equalityreferential equality is checked by the === operation
Structural equalitystructural 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
Lambda vs
ClosureLAMBDA 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
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
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 }}
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
Infix notation
infix fun Int.minus(x: Int): Int { return this.minus(x)}
infix extension function:
val result = 1 minus 2println(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
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:
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
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 }}
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 convenientoverride 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}
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)}
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
Dokka
Standalone jar:● java -jar dokka-fatjar.jar ./src/
buildscript { dependencies { classpath "org.jetbrains.dokka:dokka-gradle-plugin:0.9.7" }}
sourceSets { main.java.srcDirs += 'src/main/kotlin'}
dokka { outputFormat = 'html' //'markdown', 'javadoc' outputDirectory = "$buildDir/kotlindocs"}
Dokka gradle plugin:
HTML output with css styles:
J2K converter
Simple Activity conversion:
Call Java 2 Kotlin converter:
● There is no Kotlin 2 Java as for now
public class SampleActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }}
class SampleActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) }}
Real-world
exampleMethod count:
Library Method count
cardview-v7/23.1.0 872
play-services-maps 1369
play-services-base 700
okhttp 1463
dagger-compiler 1582
kotlin-stdlib 2411
kotlin-runtime 705
support-vector-drawable 635
butterknife 635
okio 712
(617 after proguarding)
LoC: 1857 kotlin + 503 java linesCompilation time (debug): 45.584 sec (6 sec incremental)Compilation time (release): 44.142 sec(proguard + zipaligning)
Intel® Core™ i5-3470 CPU @ 3.20GHz × 4 8 GB RAM 1333 MHz, Ubuntu 15.10, Linux kernel 4.4.2-040402-generic
Real-world
exampleMethod 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 linesCompilation 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.02.6 GHz Intel Core i58 GB 1600 MHz DDR3, OsX El Capitan 10.11.2 (15C50)
Try it yourself - STXInsider example project:https://github.com/kosiara/stx-insider
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::classval 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)
Resources
RESOURCES:
● https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)● https://kotlinlang.org/docs/reference/generics.html● http://www.cs.cornell.edu/~ross/publications/mixedsite/mixedsite-tate-fool13.pdf● http://blog.jetbrains.com/kotlin/2015/06/better-annotation-processing-supporting-stubs-in-kapt/● https://yanniss.github.io/varj-ecoop12.pdf● https://schneide.wordpress.com/2015/05/11/declaration-site-and-use-site-variance-explained/● http://stackoverflow.com/questions/4231305/how-does-javas-use-site-variance-compare-to-cs-declaration-site-variance● http://www.cs.cornell.edu/~ross/publications/tamewild/● http://stackoverflow.com/questions/26507099/lambda-expressions-in-kotlin● http://gafter.blogspot.com/2006/11/reified-generics-for-java.html● http://stackoverflow.com/questions/1927789/why-should-i-care-that-java-doesnt-have-reified-generics● https://github.com/KeepSafe/dexcount-gradle-plugin● http://blog.jetbrains.com/kotlin/2015/09/kotlin-m13-is-out/● http://blog.jetbrains.com/kotlin/2011/10/dsls-in-kotlin-part-1-whats-in-the-toolbox-builders/● http://stackoverflow.com/questions/28210188/static-extension-methods-in-kotlin● http://antonioleiva.com/collection-operations-kotlin/
Thank you!
Bartosz [email protected]: @bkosarzycki
Online compiler:
http://try.kotlinlang.org/
STXInsider example project in Kotlin:https://github.com/kosiara/stx-insider