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

Kotlin gotchas and random rants

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

Kotlin gotchas and random rants

Talked about some Kotlin gotchas and random rants based on what I observed while working on a Kotlin project for first time in last one year

En-lightning talks @ GDG Toronto Android meetup event
https://www.meetup.com/ToAndroidDev/events/263372664/

Youtube link: https://www.youtube.com/watch?v=mNviUg0ocsk

Avatar for Jigar Brahmbhatt

Jigar Brahmbhatt

August 20, 2019
Tweet

More Decks by Jigar Brahmbhatt

Other Decks in Programming

Transcript

  1. SOME CONTEXT ▸ Feb 2016, Kotlin v1.0 stable released ▸

    May 2017, Google announced first-class support for Kotlin on Android ▸ May 2019, Google announced Kotlin as preferred language for Android ▸ Intersect moved to Kotlin little more than a year ago ▸ Delivered 2 largely Kotlin based apps in production ▸ Today, I’ll talk about gotchas we observed here and there
  2. USING vs LEARNING private val iAmNullable: String? = null private

    lateinit var iAmLateInit: String private val iAmLazy by lazy { “lazy” }
  3. USING vs LEARNING private val iAmNullable: String? = null private

    lateinit var iAmLateInit: String private val iAmLazy by lazy { “lazy” }
  4. USING vs LEARNING private val iAmNullable: String? = null private

    lateinit var iAmLateInit: String private val iAmLazy by lazy { “lazy” } lateinit var currentAccountData: AccountData if (this::currentAccountData.isInitialized) { fetchData(currentAccountData.id) }
  5. USING vs LEARNING private val iAmNullable: String? = null private

    lateinit var iAmLateInit: String private val iAmLazy by lazy { “lazy” } // full screen view to show when // we get API error lateinit var tryAgainView: View fun onApiError() { tryAgainView.show() }
  6. USING vs LEARNING private val iAmNullable: String? = null private

    lateinit var iAmLateInit: String private val iAmLazy by lazy { “lazy” } // full screen view to show when // we get API error lateinit var tryAgainView: View tryAgainView = findViewById(R.id.try_again_view) fun onApiError() { tryAgainView.show() }
  7. USING vs LEARNING private val iAmNullable: String? = null private

    lateinit var iAmLateInit: String private val iAmLazy by lazy { “lazy” } lateinit var incomingData: String fun getIntent(context: Context, data: String? = null): Intent { return Intent(context, MainActivity::class.java).apply { putExtra(DATA, data) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) incomingData = intent.getStringExtra(DATA) }
  8. USING vs LEARNING private val iAmNullable: String? = null private

    lateinit var iAmLateInit: String private val iAmLazy by lazy { “lazy” } lateinit var incomingData: String fun getIntent(context: Context, data: String? = null): Intent { return Intent(context, MainActivity::class.java).apply { putExtra(DATA, data) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) incomingData = intent.getStringExtra(DATA) } // Intent.java public String getStringExtra(String name) { return mExtras == null ? null : mExtras.getString(name); } ✗
  9. USING vs LEARNING private val iAmNullable: String? = null private

    lateinit var iAmLateInit: String private val iAmLazy by lazy { “lazy” } lateinit var incomingData: String fun getIntent(context: Context, data: String? = null): Intent { return Intent(context, MainActivity::class.java).apply { putExtra(DATA, data) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) incomingData = intent.getStringExtra(DATA) } // Intent.java @Nullable public String getStringExtra(String name) { return mExtras == null ? null : mExtras.getString(name); } ✓
  10. USING vs LEARNING private val iAmNullable: String? = null private

    lateinit var iAmLateInit: String private val iAmLazy by lazy { "lazy" }
  11. USING vs LEARNING private val iAmNullable: String? = null private

    lateinit var iAmLateInit: String private val iAmLazy by lazy { "lazy" } private val userListAdapter by lazy { UserListAdapter(this) } private val bulletPath: Path by lazy { Path() } ✓ ✗
  12. USING vs LEARNING private val iAmNullable: String? = null private

    lateinit var iAmLateInit: String private val iAmLazy by lazy { "lazy" } private val userListAdapter by lazy { UserListAdapter(this) } private val bulletPath: Path by lazy { Path() }
  13. USING vs LEARNING private val iAmNullable: String? = null private

    lateinit var iAmLateInit: String private val iAmLazy by lazy { "lazy" } private val userListAdapter by lazy { UserListAdapter(this) } private val bulletPath: Path by lazy { Path() } Airbnb/MvRx --> lifecycleAwareLazy
  14. USING vs LEARNING private val iAmNullable: String? = null private

    lateinit var iAmLateInit: String private val iAmLazy by lazy { "lazy" } public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer) public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> = when (mode) { LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer) LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer) LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer) }
  15. USING vs LEARNING private val iAmNullable: String? = null private

    lateinit var iAmLateInit: String private val iAmLazy by lazy { "lazy" } public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer) public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> = when (mode) { LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer) LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer) LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer) } val bulletPath: Path by lazy(LazyThreadSafetyMode.NONE) { Path() }
  16. USING vs LEARNING private val iAmNullable: String? = null private

    lateinit var iAmLateInit: String private val iAmLazy by lazy { "lazy" } public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer) public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> = when (mode) { LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer) LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer) LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer) } val bulletPath: Path by lazy(LazyThreadSafetyMode.NONE) { Path() }
  17. KOTLIN <> JAVA <> TOOLING override fun writeToParcel(parcel: Parcel, flags:

    Int) { parcel.writeString(id) parcel.writeString(name) parcel.writeParcelable(customObj, flags) }
  18. KOTLIN <> JAVA <> TOOLING override fun writeToParcel(parcel: Parcel, flags:

    Int) { parcel.apply { writeString(id) writeString(name) writeParcelable(customObj, flags) } }
  19. Android Studio default code style still does wildcard imports (Kotlin

    & Java guidelines do not allowed it) In fact, Ktlint throws warnings about it! KOTLIN <> JAVA <> TOOLING
  20. KOTLIN <> JAVA <> TOOLING public class Transaction implements Serializable

    { private static final long serialVersionUID = 1L; @SerializedName("postedTimestamp") private OffsetDateTime postedTimestamp = null; public Transaction() { } @ApiModelProperty( example = "2017-10-27T03:17:06.837-05:00", value = "Timestamp of posted transaction" ) public OffsetDateTime getPostedTimestamp() { return this.postedTimestamp; } public void setPostedTimestamp(OffsetDateTime postedTimestamp) { this.postedTimestamp = postedTimestamp; } }
  21. KOTLIN <> JAVA <> TOOLING fun getTimeLabel(): String { return

    postedTimestamp.formatTimeAsLabel() } public class Transaction implements Serializable { private static final long serialVersionUID = 1L; @SerializedName("postedTimestamp") private OffsetDateTime postedTimestamp = null; public Transaction() { } @ApiModelProperty( example = "2017-10-27T03:17:06.837-05:00", value = "Timestamp of posted transaction" ) public OffsetDateTime getPostedTimestamp() { return this.postedTimestamp; } public void setPostedTimestamp(OffsetDateTime postedTimestamp) { this.postedTimestamp = postedTimestamp; } }
  22. KOTLIN <> JAVA <> TOOLING fun getTimeLabel(): String { return

    postedTimestamp.formatTimeAsLabel() } public class Transaction implements Serializable { private static final long serialVersionUID = 1L; @SerializedName("postedTimestamp") private OffsetDateTime postedTimestamp = null; public Transaction() { } @ApiModelProperty( example = "2017-10-27T03:17:06.837-05:00", value = "Timestamp of posted transaction" ) public OffsetDateTime getPostedTimestamp() { return this.postedTimestamp; } public void setPostedTimestamp(OffsetDateTime postedTimestamp) { this.postedTimestamp = postedTimestamp; } } Swagger Codegen or openapi-generator for kotlin doesn't have retrofit & other required dependencies yet! So we’re kinda screwed ¯\_(ツ)_/¯
  23. KOTLIN <> JAVA <> TOOLING We couldn’t run security analysis

    and pen testing tools on our codebase because it wasn’t yet supporting Kotlin ¯\_(ツ)_/¯ Third party enterprise tools not Kotlin friendly yet
  24. NO checked exceptions on Kotlin! KOTLIN <> JAVA <> TOOLING

    Understood! But it’s difficult to get used to it after years of Java
  25. KOTLIN <> ANDROID Ugly data classes with Parcelable data class

    MyParcelableData(val id: String, val name: String, val customObj: MyCustomObj)
  26. KOTLIN <> ANDROID Ugly data classes with Parcelable data class

    MyParcelableData(val id: String, val name: String, val customObj: MyCustomObj) : Parcelable { constructor(parcel: Parcel) : this( parcel.readString(), parcel.readString(), parcel.readParcelable(MyCustomObj::class.java.classLoader)) override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeString(id) parcel.writeString(name) parcel.writeParcelable(customObj, flags) } override fun describeContents(): Int { return 0 } companion object CREATOR : Parcelable.Creator<MyParcelableData> { // …… } }
  27. KOTLIN <> ANDROID Ugly data classes with Parcelable data class

    MyParcelableData(val id: String?, val name: String?, val customObj: MyCustomObj?) : Parcelable { constructor(parcel: Parcel) : this( parcel.readString(), // error -compileSdk 29 parcel.readString(), // error parcel.readParcelable(MyCustomObj::class.java.classLoader)) // error override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeString(id) parcel.writeString(name) parcel.writeParcelable(customObj, flags) } override fun describeContents(): Int { return 0 } companion object CREATOR : Parcelable.Creator<MyParcelableData> { // …… } }
  28. RESOURCES ‣ https://www.youtube.com/watch?v=Ta5wBJsC39s (Kotlin Under the Hood: Understand the Internals)

    ‣ https://www.bignerdranch.com/blog/kotlin-when-to-use-lazy-or-lateinit/ ‣ https://github.com/airbnb/MvRx/blob/master/mvrx/src/main/kotlin/com/airbnb/mvrx/ lifecycleAwareLazy.kt ‣ https://www.reddit.com/r/androiddev/comments/ala9p2/ why_kotlinx_synthetic_is_no_longer_a_recommended/ ‣ https://old.reddit.com/r/androiddev/comments/ala9p2/ why_kotlinx_synthetic_is_no_longer_a_recommended/efdvpkg/ ‣ https://android-review.googlesource.com/c/platform/frameworks/support/+/882241 ‣ https://twitter.com/chrisbanes/status/897075635142754305 ‣ https://github.com/pinterest/ktlint ‣ https://github.com/pinterest/ktlint/issues/48 (wildcard discussion) ‣ https://kotlinlang.org/docs/tutorials/android-plugin.html