Skip to main content

Notes - Android Jetpack: Sweetening Kotlin Development with Android KTX (Google I/O '18)

note

This article has been translated by Gemini.

In this Notes article, I roughly jotted down notes in English from the content of the English session video/podcast. This article is intended to be read as a reference while watching the video. I hope this article helps you even a little when you watch the actual video. (Please feel free to contact me if there are any errors or typos!)

This article is based on Android Jetpack: Sweetening Kotlin Development with Android KTX (Google I/O ‘18).

f🆔shaunkawano:20180514094438p:plain

Last Year at Google I/O
#

  • We are allowed to extend Android APIs to have new ways of writing Android types.
  • Create more concise versions of what we intended to do.
  • Intent is to feel semantically equivalent.

Why can’t we directly offer APIs for it? => Lambda matters.

When we pass a lambda in Java 8 or in Kotlin, by default it has to create an anonymous class, which consumes a method and causes class loading.

inline
#

  • Language functionality that allows us to eliminate lambda allocation.
  • By marking a function with inline, the body of the function gets copied inside the caller side, resulting in zero overhead.
inline fun ViewGroup.forEachIndexed(action: (Int, View) -> Unit) {
  for (index in 0 until childCount) {
    action(index, getChildAt(index))
  }
}

val userLayout: ViewGroup = findViewById(R.id.users)
userLayout.forEachIndexed { index, view -> 
  // Do something
}

reified
#

  • Compiler trick.
  • Forces the type information of the generic to be known at compile time so that it can be made available at runtime.
inline fun <reified T> Context.systemService(): T = 
  ContextCompat.getSystemService(this, T::class.java)!!

val notifications = systemService<NotificationManager>()
var onlyDigits = true
for (c in phoneNumber) {
  if (!c.isDigit()) {
    onlyDigits = false
    break
  }
}

Kotlin has an extension function isDigit(), so we can use it with all {}:

val onlyDigits = phoneNumber.all { it.isDigit() }

But Android already has a built-in function:

val onlyDigits = TextUtils.isDigitsOnly(phoneNumber)

We can create an extension function for it:

inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)

But is it worth it to extract it as an extension? => YES.

  • More natural.
  • With an extension, the IDE will show this with auto-complete, so you can easily find this API and simply select it to use it.

Android KTX
#

core-ktx -> support-compat -> Android framework
core-ktx -> core -> Android framework
fragment-ktx -> fragment
palette-ktx -> palette
collection-ktx -> collection
lifecycle-reactivestreams-ktx -> lifecycle-reactivestreams
sqlite-ktx -> sqlite
navigation-*-ktx -> navigation-*
work-runtime-ktx -> work-runtime

KTX Principles
#

#

  • Adapt existing functionality and redirect features upstream.
    • The body of KTX can be just an alias of current Android APIs.
    • The implementation of functions should be trivial.
  • Default to inline unless code size or allocation is prohibitive.
    • If you write Kotlin yourself, making inline the default may not be best as it can lead to more overhead.
  • Leverage features unique to Kotlin.

#

  • Code golf APIs to be as short as possible.
    • KTX is not there to make the code shorter.
  • Optimize for a single and/or specific use case.

Reference
#

API reference | Android Developers

Android Developers Reference now contains some of the KTX extension functions.

  • Extension Function Summary

Building Kotlin-friendly Libraries
#

Port public API or entire library to Kotlin
#

  • May not be viable for Android Framework or androidx.* libraries.
    • Maybe in the future, strongly possible.

Ship sibling artifact with Kotlin extensions
#

KEEP-110
#

Kotlin Evolution Enhancement Process

@ExtensionFunction / @ExtensionProperty
#

  • Turn a static method with at least one argument into an extension function or an extension property.
class TextUtils {
  @ExtensionFunction // Tell compiler to have an extension function for this method
  static boolean isDigitsOnly(CharSequence str) {
    int len = str.length();
    // …
  }
}

In Kotlin, you can use the extension function made by the Kotlin compiler:

val onlyDigits = phoneNumber.isDigitsOnly()

// We no longer need this manual extension function:
// inline fun CharSequence.isDigitsOnly() = TextUtils.isDigitsOnly(this)

// In bytecode, we get:
// val onlyDigits = TextUtils.isDigitsOnly(phoneNumber)

@KtName
#

  • An alternative name for methods, fields, and parameters for use by Kotlin code.
  • Advantages:
    • You can retain the single source of truth in Java APIs.
      • You don’t have to add Kotlin code.
    • Even in pure Java libraries, they can enhance their APIs for Kotlin users.

@DefaultValue
#

  • Default parameter value.
class View {
  void setPadding(
    @KtName("left") @DefaultValue("paddingLeft") int left,
    @KtName("top") @DefaultValue("paddingTop") int top,
    @KtName("right") @DefaultValue("paddingRight") int right,
    @KtName("bottom") @DefaultValue("paddingBottom") int bottom
  ) {
    
  }
}
avatarView.setPadding(left = 10, right = 10)

// We no longer need this extension function:
// inline fun View.updatePadding { … }

// In bytecode, we get:
// avatarView.setPadding(left = 10, top = avatarView.paddingTop, right = 10, bottom = avatarView.paddingBottom)

Note

  • Semantics may change.
  • Naming may change.
  • This proposal may not be accepted and added into the Kotlin compiler.
    • But some prototypes are already in the Kotlin compiler.

In Bug Tracker we now have a new component: Android Public Tracker > App Development > Support Libraries > Android KTX.

  • Still accepting PRs, but issues inside GitHub are no longer the single source of truth; Bug Tracker will be.

Impressions / Summary / Key Takeaways
#

  • Carefully weighing which extension functions to add based on specific principles is something only people like Jake Wharton can do.
  • The section on dead code elimination (extension functions for Fragment Transaction) shows how smart the Kotlin Compiler is.
  • If KEEP-110 is adopted, KTX might become unnecessary or extremely lightweight. However, as someone mentioned on Twitter, it might result in a massive amount of annotations on Java code, which sounds painful. I’m excited to see how it turns out either way.
    • Nevertheless, it seems to have huge benefits for the Android platform, so I personally hope it becomes a reality!

That’s all!