KTX: Making Android and Kotlin APIs more delicious.

KTX: Making Android and Kotlin APIs more delicious.

Community is loving Kotlin, because of its Language features example
  • No more builders needed with Kotlin’s default values
  • Easy creation of singletons
  • equals() and hashCode() out of the box
  • Final classes by default
  • Forced Null-Checks
  • No primitive and reference type confusion

If you loving Kotlin ~ Your Loving Effective Java


Effective Java
  • Item 2: Consider a builder when faced with many constructor parameters
    • Kotlin: default values → builder pattern
  • Item 3: Enforce the singleton property with a private constructor or an enum type
    • Kotlin: Singleton Enums → Object class
  • Item 9: Always override hashCode when you override equals
    • Kotlin: hashcode & override → Data class
  • item 12: Final should be Default for Classes in Java
    • Kotlin: Classes are final by default
  • item 49: Check you parameter validity
    • Kotlin: ? , !! , requireNotNull() etc

Effective Java: Item 18: favour composition over inheritance

class Cat {
  fun meow()
  fun poop()
}

class Dog {
  fun bark()
  fun poop()
}

class Animal {
  fun poop()
}

class Cat : Animal {
  fun meow()
}

class Dog : Animal {
  fun bark()
}

class CleaningRobot {
  fun clean()
  fun drive()
}

class KillerRobot {
  fun kill()
  fun drive()
}

class Robot {
  fun drive()
}

class CleaningRobot : Robot {
  fun clean()
}

class KillerRobot : Robot {
  fun kill()
}

But I want a killer-Robo-Dog Which don’t poop!

class KillerRoboDog : Animal , Robot {
  fun kill()
  fun bark()
  fun drive()
  fun poop() // NO NO 
}

Solution

dog = baker + pooper 
cat = mower + pooper
cleanerRobot = driver + cleaner
killerRobot = driver + killer 
killerRoboDog = baker + killer + driver

i.e 

class KillerRoboDog(baker:Barker,killer:Killer,driver:Driver) {
...
}

this is nothing but a wrapper/ decorator patter

Inheritance is appropriate only where the subclass really is a
subtype of the superclass i.e. “is-a” relationship exists 
between the two classes.

First-class support for composition using Kotlin extension aka KTX,

  • Kotlin provides extending a class with new functionality without
    • having to inherit from the class
    • or use design patterns such as Decorator.
class Hero {
  fun useSuperpowers() {
    println("Applied super powers")
  }
}

class SuperHero(val hero:Hero) {
  fun savePlanet() {
    hero.useSuperpowers()
  }
}

val superman = SuperHero(Hero())
superman.savePlanet()

OR

fun Hero.savePlanet() = this.useSuperpowers()

val superman = Hero()
superman.savePlanet()

Cost of abstraction? No inheritance? No Wrapper?

  • Extensions do not actually modify classes they extend.
  • You do not insert new members into a class,
  • You make new functions on Type of variable/receiver
  • Which is resolved statically.
// app.kt
package org.example
fun Date.getTime() { /*...*/ }

// Test.java
static void org.example.AppKt.getTime(Date $self){...}

Reducing the cost: Inline

Basically inline tells the compiler to copy these functions and parameters to call site, hence no more static function.

BUT…

  • inline reduce memory overhead. But inline also increases the resulting bytecode.
  • Which is why inline usage should be avoided with large functions or accessors with large code logic
A few more things to know when working with inline functions:
  • We can have some lambdas inlined when passed in an inline function by using noinline modifier.
  • A normal return statement is not allowed inside a lambda. If we want to explicitly return from a lambda, we need to use a label
  • To access type passed as parameter we use reified type parameter.

Extension Design Principle

  • Adapt existing functionality not create new ones
  • Don’t fix APIs using extensions, try enhancing the APIs
  • Not make code concise but meaning full
  • Leverage Kotlin features: inline, reified, scoping
  • Use inline unless code size or allocation is a constrain
  • Optimizing for the single use case, not for the specific use case

Good Examples of KTX

//Adapt existing functionality , also Optimising for single use case not for specific use case

val notif = getSystemService(NotficationManager::class.java) // API 23+
val notif2 = ContextCompat.getSystemService(NotficationManager::class.java) // Below 23+

inline fun <reified T> context.systemService() {
  //if API 23+ getSystemService(NotficationManager::class.java)
  // else ContextCompat.getSystemService(NotficationManager::class.java)
}

//Enchancing the API
val view = Inflater.from(parent.context).inflate(R.id.sampleView,parent,false)

inline fun ViewGroup.inflate(@LayoutRes resourceId:Int):View{
  return Inflater.from(context).inflate(resourceId:Int,this,false)
}

val view = ViewGroup.inflate(R.id.sampleView)


//concise but meaning full
view.setPadding(0,0,12,0)

inline fun view.updatePadding(left:Int=0,right:Int=0,up:Int=0,down:Int=0){
  view.setPadding(left,right,up,down)
}

view.updatePadding(up=12)

// Leverage Kotlin features : inline , reified , scoping
val listOfEvens = listOf(1,2,3,3).map{it*2}.also{println(it)}.filter{i%2==0}

fun inline <refied T> fun Activity.launchActivity(bundle:Bundle = Bundle()){
  startActivity(Intent(this,T::class.java).also{extra = bundle})
}

//in activity
launchActivity<SplashActivity>()
launchActivity<SettingActivity>(BundleOf("test" to 123))

Comments