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
- 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
Post a Comment