Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin tips and tricks for Android

Kotlin tips and tricks for Android

If you’re somewhat familiar with Kotlin, you know, that the language provides a bunch of simple and beautifully logical solutions to complex programming problems. The first couple of discoveries are exciting but the deeper you get the more interesting these “Aha!” moments become.

This talk will focus on actionable tips and tricks specific to Android development. We will look into how some language features such as extension functions, sealed classes and delegation can help with age-old problems and make developing apps more fun than ever before.

Pandula Péter

October 02, 2018
Tweet

Other Decks in Programming

Transcript

  1. Top-level functions inline fun consume(action: () -> Unit): Boolean {


    action()
 return true
 } override fun onOptionsItemSelected(item: MenuItem?) = when(item?.itemId) {
 R.id.settings -> consume { openSettingsPage() }
 R.id.about -> consume { openAboutPage() }
 else -> super.onOptionsItemSelected(item)
 } Helpers.kt MainActivity.kt
  2. Extension functions public final class ExtensionsKt {
 public static final

    void toggle(@NotNull ObservableBoolean $receiver) {
 Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
 $receiver.set(!$receiver.get());
 }
 } @NotNull
 private final ObservableBoolean isSomethingEnabled = new ObservableBoolean();
 ...
 ExtensionsKt.toggle(this.isSomethingEnabled); Extensions.decompiled.java MainViewModel.decompiled.java
  3. Extension functions inline fun EditText.onTextChanged(crossinline action: (CharSequence?) -> Unit) =


    addTextChangedListener(object : TextWatcher { 
 override fun afterTextChanged(s: Editable?) = Unit
 
 override fun beforeTextChanged(
 s: CharSequence?, start: Int, count: Int, after: Int
 ) = Unit
 
 override fun onTextChanged(
 s: CharSequence?, start: Int, before: Int, count: Int
 ) = action(s)
 }) binding.editText.onTextChanged { println("Text changed: $it") } Extensions.kt MainActivity.kt
  4. fun Context.color(@ColorRes resourceId: Int) =
 ContextCompat.getColor(this, resourceId) Extension functions fun

    Context.dimension(@DimenRes resourceId: Int) =
 resources.getDimensionPixelSize(resourceId) fun Context.drawable(@DrawableRes resourceId: Int) =
 AppCompatResources.getDrawable(this, resourceId)
  5. Extension properties companion object {
 private const val VIDEO_ID =

    "video_id"
 
 private var Intent.videoId
 get() = getStringExtra(VIDEO_ID)
 set(value) {
 putExtra(VIDEO_ID, value)
 }
 ...
 } VideoPlayerActivity.kt
  6. Extension properties companion object {
 ...
 fun getStartIntent(context: Context, videoId:

    String) =
 Intent(context, VideoPlayerActivity::class.java).apply {
 this.videoId = videoId
 }
 } override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 playVideo(intent.videoId)
 } VideoPlayerActivity.kt
  7. Sealed classes Menu Leader- board Game Level 1 Level 2

    Level 3 sealed class Screen {
 object Menu : Screen()
 object Leaderboard : Screen()
 class Game(val level: Int) : Screen()
 } fun getScreenName(screen: Screen) = when (screen) {
 Screen.Menu -> "Menu screen"
 Screen.Leaderboard -> "Leaderboard screen"
 is Screen.Game -> "Level ${screen.level}"
 }
  8. Sealed classes sealed class Screen {
 abstract val name: String


    
 object Menu : Screen() { override val name = "Menu screen" }
 object Leaderboard : Screen() { override val name = "Leaderboard screen" }
 sealed class Game : Screen() {
 object Level1 : Game() { override val name = "Level 1" }
 object Level2 : Game() { override val name = "Level 2" }
 object Level3 : Game() { override val name = "Level 3" }
 }
 }
  9. Property delegation - Built-in delegates val settingsMenuItem by lazy {

    binding.navigationView.menu.findItem(R.id.settings) } var isCheckboxEnabled by Delegates.observable(false) { _: KProperty<*>,
 old: Boolean,
 new: Boolean ->
 if (old != new) {
 // Persist the new value in SharedPreferences, notify subscribers, etc.
 }
 } Lazy Observable private val userRepository by inject<UserRepository>() Inject (Koin)
  10. Property delegation - Custom delegates operator fun getValue(thisRef: R, property:

    KProperty<*>): T
 operator fun setValue(thisRef: R, property: KProperty<*>, value: T) For val properties: get() interface ReadWriteProperty<R, T> interface ReadOnlyProperty<R, T> For var properties: get() and set()
  11. Property delegation - Custom delegates class InvalidateDelegate<T>(private var value: T)

    : ReadWriteProperty<View, T> {
 override fun getValue(thisRef: View, property: KProperty<*>) = value
 override fun setValue(thisRef: View, property: KProperty<*>, value: T) {
 this.value = value
 thisRef.invalidate()
 }
 } Delegates.kt operator fun getValue(thisRef: R, property: KProperty<*>): T
 operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
  12. Property delegation - Custom delegates class InvalidateDelegate<T>(private var value: T)

    {
 operator fun getValue(thisRef: View, property: KProperty<*>) = value
 operator fun setValue(thisRef: View, property: KProperty<*>, value: T) {
 this.value = value
 thisRef.invalidate()
 }
 } var borderColor by InvalidateDelegate(Color.BLACK) Delegates.kt SomeCustomView.kt operator fun getValue(thisRef: R, property: KProperty<*>): T
 operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
  13. Property delegation - Custom delegates class IntervalDelegate(var value: Int, val

    minValue: Int, val maxValue: Int) {
 operator fun getValue(thisRef: Any?, property: KProperty<*>) = value
 operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
 this.value = value.coerceIn(minValue, maxValue)
 }
 } var hour by IntervalDelegate(16, 0, 23)
 var minute by IntervalDelegate(20, 0, 59)
 var second by IntervalDelegate(0, 0, 59)
 var millisecond by IntervalDelegate(500, 0, 999)
  14. Putting it all together - Bundle Arguments sealed class BundleDelegate<T>(protected

    val key: kotlin.String) : ReadWriteProperty<Bundle, T> {
 class Int(key: kotlin.String) : BundleDelegate<kotlin.Int>(key) {
 override fun getValue(thisRef: Bundle, property: KProperty<*>) =
 thisRef.getInt(key)
 override fun setValue(thisRef: Bundle, property: KProperty<*>, value: kotlin.Int) =
 thisRef.putInt(key, value)
 } 
 class String(key: kotlin.String) : BundleDelegate<kotlin.String>(key) {
 override fun getValue(thisRef: Bundle, property: KProperty<*>) =
 thisRef.getString(key)
 override fun setValue(thisRef: Bundle, property: KProperty<*>, value: kotlin.String) =
 thisRef.putString(key, value)
 }
 }
  15. Putting it all together - Bundle Arguments class GameActivity :

    AppCompatActivity() {
 private var Bundle.score by BundleDelegate.Int("score")
 private var score = 0
 
 override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 savedInstanceState?.let { score = it.score }
 }
 
 override fun onSaveInstanceState(outState: Bundle) {
 super.onSaveInstanceState(outState)
 outState.score = score
 }
 }
  16. Putting it all together - Bundle Arguments class MusicPlayerFragment :

    Fragment() { companion object {
 private var Bundle.playlistId by BundleDelegate.String("playlist_id")
 private var Bundle.songIndex by BundleDelegate.Int("song_index")
 
 fun newInstance(playlistId: String, songIndex: Int) =
 MusicPlayerFragment().apply {
 arguments = Bundle().apply {
 this.playlistId = playlistId
 this.songIndex = songIndex
 }
 }
 } 
 private val playlistId by lazy { arguments.playlistId }
 private val songIndex by lazy { arguments.songIndex }
 }
  17. Putting it all together - Intent Extras companion object {

    private var Intent.videoId by IntentExtraDelegate.String("video_id") 
 fun getStartIntent(context: Context, videoId: String) =
 Intent(context, VideoPlayerActivity::class.java).apply {
 this.videoId = videoId
 }
 } override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 playVideo(intent.videoId)
 } VideoPlayerActivity.kt
  18. Putting it all together - SharedPreferences fun onSomeCheckboxChecked(isChecked: Boolean) {


    sharedPrefManager.someBooleanField = isChecked
 }
 
 fun logSomeCheckboxState() {
 println(
 "SomeCheckbox is ${
 if (sharedPrefManager.someBooleanField) "on" else "off"
 }."
 )
 }