$30 off During Our Annual Pro Sale. View Details »

Kotlin gotchas and random rants

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

Jigar Brahmbhatt

August 20, 2019
Tweet

More Decks by Jigar Brahmbhatt

Other Decks in Programming

Transcript

  1. KOTLIN GOTCHAS AND
    RANDOM RANTS
    GDG TORONTO ANDROID
    Jigar Brahmbhatt (@jabbar_jigariyo)

    View Slide

  2. 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

    View Slide

  3. USING vs LEARNING

    View Slide

  4. Nullable : lateinit : lazy
    USING vs LEARNING

    View Slide

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

    View Slide

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

    View Slide

  7. 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)
    }

    View Slide

  8. 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()
    }

    View Slide

  9. 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()
    }

    View Slide

  10. 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)
    }

    View Slide

  11. 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);
    }

    View Slide

  12. 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);
    }

    View Slide

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

    View Slide

  14. 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() } ✓

    View Slide

  15. 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() }

    View Slide

  16. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  20. KOTLIN <> JAVA <> TOOLING

    View Slide

  21. Android Studio yet not
    Kotlin first!
    KOTLIN <> JAVA <> TOOLING

    View Slide

  22. KOTLIN <> JAVA <> TOOLING

    View Slide

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

    View Slide

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

    View Slide

  25. 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

    View Slide

  26. Swagger / open-api
    codegen support issue
    KOTLIN <> JAVA <> TOOLING

    View Slide

  27. 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;
    }
    }

    View Slide

  28. 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;
    }
    }

    View Slide

  29. 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
    ¯\_(ツ)_/¯

    View Slide

  30. Third party enterprise tools
    not Kotlin friendly yet
    KOTLIN <> JAVA <> TOOLING

    View Slide

  31. 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

    View Slide

  32. NO checked exceptions on Kotlin!
    KOTLIN <> JAVA <> TOOLING
    Understood! But it’s difficult to get used to it after years of Java

    View Slide

  33. KOTLIN <> ANDROID

    View Slide

  34. KOTLIN <> ANDROID
    Ugly data classes with Parcelable

    View Slide

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

    View Slide

  36. 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 {
    // ……
    }
    }

    View Slide

  37. 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 {
    // ……
    }
    }

    View Slide

  38. KOTLIN <> ANDROID
    kotlinx synthetic imports

    View Slide

  39. KOTLIN <> ANDROID
    kotlinx synthetic imports

    View Slide

  40. KOTLIN <> ANDROID

    View Slide

  41. KOTLIN <> ANDROID

    View Slide

  42. THAT ENDS MY BLAH BLAH!

    View Slide

  43. 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

    View Slide