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

Mastering API Visibility in Kotlin (Rheinwerk Konferenz für Kotlin 2021)

Marton Braun
September 14, 2021

Mastering API Visibility in Kotlin (Rheinwerk Konferenz für Kotlin 2021)

When designing a library, minimizing your API surface - the types, methods, properties, and functions you expose to the outside world - is a great idea. This doesn't apply to just libraries: it's a consideration you should make for every module in a multi-module project. In this talk, we'll look at all the ways that Kotlin lets you get your visibility just right.

Resources and more: https://zsmb.co/talks/mastering-api-visibility/

Marton Braun

September 14, 2021
Tweet

More Decks by Marton Braun

Other Decks in Technology

Transcript

  1. Márton Braun
    zsmb.co
    zsmb13
    Mastering API
    Visibility in Kotlin

    View full-size slide

  2. Minimal APIs

    View full-size slide

  3. Minimal APIs
    Item 15: Minimize the accessibility of classes and members

    View full-size slide

  4. Minimal APIs
    Item 15: Minimize the accessibility of classes and members
    Chapter 4: Classes and Interfaces

    View full-size slide

  5. Minimal APIs
    › Easier to maintain and change

    View full-size slide

  6. Minimal APIs
    › Easier to maintain and change

    View full-size slide

  7. Minimal APIs
    › Easier to maintain and change

    View full-size slide

  8. Minimal APIs
    › Easier to maintain and change

    View full-size slide

  9. Minimal APIs
    › Easier to maintain and change

    View full-size slide

  10. Minimal APIs
    › Easier to maintain and change

    View full-size slide

  11. Minimal APIs
    › Easier to maintain and change

    View full-size slide

  12. Minimal APIs
    › Easier to maintain and change
    public fun Krate.stringPref(
    key: String,
    isValid: (newValue: String) -> Boolean,
    )

    View full-size slide

  13. replaceWith = ReplaceWith(
    "this.stringPref(key).validate(isValid)",
    ),
    Minimal APIs
    › Easier to maintain and change
    @Deprecated(
    message = "Use a chained .validate {} call instead",
    level = DeprecationLevel.WARNING,
    )
    public fun Krate.stringPref(
    key: String,
    isValid: (newValue: String) -> Boolean,
    )

    View full-size slide

  14. replaceWith = ReplaceWith(
    "this.stringPref(key).validate(isValid)",
    ),
    Minimal APIs
    › Easier to maintain and change
    @Deprecated(
    message = "Use a chained .validate {} call instead",
    level = DeprecationLevel.WARNING,
    )
    public fun Krate.stringPref(
    key: String,
    isValid: (newValue: String) -> Boolean,
    )

    View full-size slide

  15. replaceWith = ReplaceWith(
    "this.stringPref(key).validate(isValid)",
    ),
    Minimal APIs
    › Easier to maintain and change
    @Deprecated(
    message = "Use a chained .validate {} call instead",
    level = DeprecationLevel.WARNING,
    )
    public fun Krate.stringPref(
    key: String,
    isValid: (newValue: String) -> Boolean,
    )

    View full-size slide

  16. Minimal APIs
    › Easier to maintain and change @Deprecated(
    message = "Use a chained .validate {} call instead",
    level = DeprecationLevel.WARNING,
    replaceWith = ReplaceWith(
    "this.stringPref(key).validate(isValid)",
    imports = arrayOf("com.example.validate"),
    ),
    )
    public fun Krate.stringPref(
    key: String,
    isValid: (newValue: String) -> Boolean,
    )

    View full-size slide

  17. Minimal APIs
    › Easier to maintain and change @Deprecated(
    message = "Use a chained .validate {} call instead",
    level = DeprecationLevel.ERROR,
    replaceWith = ReplaceWith(
    "this.stringPref(key).validate(isValid)",
    imports = arrayOf("com.example.validate"),
    ),
    )
    public fun Krate.stringPref(
    key: String,
    isValid: (newValue: String) -> Boolean,
    )

    View full-size slide

  18. Minimal APIs
    › Easier to maintain and change @Deprecated(
    message = "Use a chained .validate {} call instead",
    level = DeprecationLevel.ERROR,
    replaceWith = ReplaceWith(
    "this.stringPref(key).validate(isValid)",
    imports = arrayOf("com.example.validate"),
    ),
    )
    public fun Krate.stringPref(
    key: String,
    isValid: (newValue: String) -> Boolean,
    )

    View full-size slide

  19. Minimal APIs
    › Easier to maintain and change @Deprecated(
    message = "Use a chained .validate {} call instead",
    level = DeprecationLevel.HIDDEN,
    replaceWith = ReplaceWith(
    "this.stringPref(key).validate(isValid)",
    imports = arrayOf("com.example.validate"),
    ),
    )
    public fun Krate.stringPref(
    key: String,
    isValid: (newValue: String) -> Boolean,
    )

    View full-size slide

  20. Minimal APIs
    › Easier to maintain and change @Deprecated(
    message = "Use a chained .validate {} call instead",
    level = DeprecationLevel.HIDDEN,
    replaceWith = ReplaceWith(
    "this.stringPref(key).validate(isValid)",
    imports = arrayOf("com.example.validate"),
    ),
    )
    public fun Krate.stringPref(
    key: String,
    isValid: (newValue: String) -> Boolean,
    )

    View full-size slide

  21. Minimal APIs
    › Easier to maintain and change

    View full-size slide

  22. Minimal APIs
    › Easier to maintain and change
    › Easier to learn

    View full-size slide

  23. Minimal APIs
    › Easier to maintain and change
    › Easier to learn

    View full-size slide

  24. Minimal APIs
    › Easier to maintain and change
    › Easier to learn

    View full-size slide

  25. Minimal APIs
    › Easier to maintain and change
    › Easier to learn
    › Harder to misuse

    View full-size slide

  26. Visibility in Kotlin
    public

    View full-size slide

  27. Visibility in Kotlin
    public
    private

    View full-size slide

  28. Visibility in Kotlin
    public
    private internal

    View full-size slide

  29. Visibility in Kotlin
    public
    private internal

    View full-size slide

  30. Internal visibility
    public interface Service {
    fun createUser(): User
    }
    internal class NetworkService : Service {
    override fun createUser(): User {
    ...
    }
    }

    View full-size slide

  31. Internal visibility
    public class NetworkClient {
    public var state: State = Disconnected
    internal set
    public fun connect() {
    state = Connected
    }
    }

    View full-size slide

  32. Internal visibility
    internal fun String.asMention(): String = "@$this"

    View full-size slide

  33. Internal visibility

    View full-size slide

  34. Testing
    private var state: State

    View full-size slide

  35. @Test
    fun verifyState() {
    assertEquals(expectedState, state)
    }
    Testing
    private var state: State

    View full-size slide

  36. @Test
    fun verifyState() {
    assertEquals(expectedState, state)
    }
    Testing
    private var state: State

    View full-size slide

  37. @Test
    fun verifyState() {
    assertEquals(expectedState, state)
    }
    Testing
    internal var state: State

    View full-size slide

  38. @Test
    fun verifyState() {
    assertEquals(expectedState, state)
    }
    Testing
    @VisibleForTesting(otherwise = PRIVATE)
    internal var state: State

    View full-size slide

  39. Java interop

    View full-size slide

  40. Java interop
    class Repository {
    internal fun createEntity() { ... }
    }

    View full-size slide

  41. Java interop
    void repositoryExample() {
    }
    class Repository {
    internal fun createEntity() { ... }
    }

    View full-size slide

  42. Java interop
    void repositoryExample() {
    new Repository().createEntity$examplelibrary();
    }
    class Repository {
    internal fun createEntity() { ... }
    }

    View full-size slide

  43. Java interop
    void repositoryExample() {
    new Repository().createEntity$examplelibrary();
    }
    class Repository {
    internal fun createEntity() { ... }
    }

    View full-size slide

  44. class Repository {
    internal fun createEntity() { ... }
    }
    Java interop
    @JvmName("pleaseDoNotCallThisMethod")
    ();
    void repositoryExample() {
    new Repository().
    }
    pleaseDoNotCallThisMethod

    View full-size slide

  45. Java interop
    void repositoryExample() {
    new Repository().
    }
    class Repository {
    internal fun createEntity() { ... }
    }
    Cannot resolve method 'createEntity' in 'Repository'
    @JvmSynthetic
    ();
    createEntity

    View full-size slide

  46. Explicit API mode

    View full-size slide

  47. Explicit API mode
    › Explicit visibility modifiers for all declarations

    View full-size slide

  48. Explicit API mode
    › Explicit visibility modifiers for all declarations
    › Explicit types for public declarations

    View full-size slide

  49. Explicit API mode
    kotlin {
    explicitApi()
    }

    View full-size slide

  50. Explicit API mode
    kotlin {
    explicitApi()
    }
    tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
    freeCompilerArgs += [
    '-Xexplicit-api=strict',
    ]
    }
    }

    View full-size slide

  51. Explicit API mode
    kotlin {
    explicitApi()
    }
    tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
    freeCompilerArgs += [
    '-Xexplicit-api=strict',
    '-progressive',
    ]
    }
    }

    View full-size slide

  52. Explicit API mode
    kotlin {
    explicitApiWarning()
    }
    tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
    freeCompilerArgs += [
    '-Xexplicit-api=warning',
    '-progressive',
    ]
    }
    }

    View full-size slide

  53. Explicit API mode
    class User(var name: String, var age: Int) {
    fun sayHi() {
    println("Hi, I'm $name")
    }
    fun reset() {
    name = ""
    age = 0
    }
    }

    View full-size slide

  54. Explicit API mode
    class User(var name: String, var age: Int) {
    fun sayHi() {
    println("Hi, I'm $name")
    }
    fun reset() {
    name = ""
    age = 0
    }
    }

    View full-size slide

  55. public
    Explicit API mode
    public class User(var name: String, var age: Int) {
    fun sayHi() {
    println("Hi, I'm $name")
    }
    fun reset() {
    name = ""
    age = 0
    }
    }

    View full-size slide

  56. Explicit API mode
    public class User(public var name: String, var age: Int) {
    fun sayHi() {
    println("Hi, I'm $name")
    }
    fun reset() {
    name = ""
    age = 0
    }
    }
    public

    View full-size slide

  57. Explicit API mode
    public class User(public var name: String, public var age: Int) {
    public fun sayHi() {
    println("Hi, I'm $name")
    }
    fun reset() {
    name = ""
    age = 0
    }
    }

    View full-size slide

  58. Explicit API mode
    public class User(public var name: String, public var age: Int) {
    public fun sayHi() {
    println("Hi, I'm $name")
    }
    internal fun reset() {
    name = ""
    age = 0
    }
    }

    View full-size slide

  59. Explicit API mode
    private val DEFAULT_CLIENT = Client()
    public interface ClientFactory {
    public fun client() = DEFAULT_CLIENT
    }

    View full-size slide

  60. Explicit API mode
    private val DEFAULT_CLIENT = Client()
    public interface ClientFactory {
    public fun client() = DEFAULT_CLIENT
    }

    View full-size slide

  61. Explicit API mode
    private val DEFAULT_CLIENT = Client()
    public interface ClientFactory {
    public fun client() = DEFAULT_CLIENT
    }

    View full-size slide

  62. Explicit API mode
    private val DEFAULT_CLIENT = Client()
    public interface ClientFactory {
    public fun client() = DEFAULT_CLIENT
    }

    View full-size slide

  63. Explicit API mode
    private val DEFAULT_CLIENT = OfflineClient()
    public interface ClientFactory {
    public fun client() = DEFAULT_CLIENT
    }

    View full-size slide

  64. Explicit API mode
    private val DEFAULT_CLIENT = OfflineClient()
    public interface ClientFactory {
    public fun client(): Client = DEFAULT_CLIENT
    }

    View full-size slide

  65. Explicit API mode
    › Explicit visibility modifiers for all declarations
    › Explicit types for public declarations

    View full-size slide

  66. Explicit API mode
    › Explicit visibility modifiers for all declarations
    › Explicit types for public declarations
    › Configured per-module

    View full-size slide

  67. Explicit API mode
    › Explicit visibility modifiers for all declarations
    › Explicit types for public declarations
    › Configured per-module
    › Strict or warning level

    View full-size slide

  68. Published API

    View full-size slide

  69. Published API
    public inline fun song() {
    secretFunction()
    }
    internal fun secretFunction() {
    println("through the mountains")
    }

    View full-size slide

  70. Published API
    public inline fun song() {
    secretFunction()
    }
    internal fun secretFunction() {
    println("through the mountains")
    }
    fun clientCode() {
    song()
    }

    View full-size slide

  71. fun clientCode() {
    }
    secretFunction()
    Published API
    public inline fun song() {
    secretFunction()
    }
    internal fun secretFunction() {
    println("through the mountains")
    }
    e: Public-API inline function cannot access non-public-API
    song()

    View full-size slide

  72. Published API
    public inline fun song() {
    secretFunction()
    }
    internal fun secretFunction() {
    println("through the mountains")
    }
    fun clientCode() {
    }
    secretFunction()

    View full-size slide

  73. Published API
    fun clientCode() {
    }
    secretFunction()
    public inline fun song() {
    secretFunction()
    }
    internal fun secretFunction() {
    println("through the mountains")
    }

    View full-size slide

  74. Published API
    fun clientCode() {
    }
    song()
    @PublishedApi
    public inline fun song() {
    secretFunction()
    }
    internal fun secretFunction() {
    println("through the mountains")
    }

    View full-size slide

  75. Published API
    fun clientCode() {
    song()
    secretFunction()
    }
    public inline fun song() {
    secretFunction()
    }
    @PublishedApi
    internal fun secretFunction() {
    println("through the mountains")
    }
    e: Cannot access 'secretFunction': it is internal

    View full-size slide

  76. Published API
    fun clientCode() {
    }
    song()
    @PublishedApi
    public inline fun song() {
    secretFunction()
    }
    internal fun secretFunction() {
    println("through the mountains")
    }

    View full-size slide

  77. Opt-in APIs
    core

    View full-size slide

  78. Opt-in APIs
    core
    addon

    View full-size slide

  79. Opt-in APIs
    core
    addon

    View full-size slide

  80. core
    addon
    Opt-in APIs

    View full-size slide

  81. core
    addon
    Opt-in APIs

    View full-size slide

  82. package com.example.lib.core
    public annotation class InternalMyLibraryApi
    core
    addon
    Opt-in APIs

    View full-size slide

  83. Opt-in APIs
    package com.example.lib.core
    public annotation class InternalMyLibraryApi

    View full-size slide

  84. Opt-in APIs
    package com.example.lib.core
    @RequiresOptIn(
    level = RequiresOptIn.Level.ERROR,
    message = "This is internal API for my library, " +
    "please don't rely on it."
    )
    public annotation class InternalMyLibraryApi

    View full-size slide

  85. Opt-in APIs
    package com.example.lib.core
    @RequiresOptIn(
    level = RequiresOptIn.Level.WARNING,
    message = "This is internal API for my library, " +
    "please don't rely on it."
    )
    public annotation class InternalMyLibraryApi

    View full-size slide

  86. Opt-in APIs
    package com.example.lib.core
    @RequiresOptIn(
    level = RequiresOptIn.Level.ERROR,
    message = "This is internal API for my library, " +
    "please don't rely on it."
    )
    public annotation class InternalMyLibraryApi

    View full-size slide

  87. Opt-in APIs
    package com.example.lib.core
    @RequiresOptIn(
    level = RequiresOptIn.Level.ERROR,
    message = "This is internal API for my library, " +
    "please don't rely on it."
    )
    public annotation class InternalMyLibraryApi
    tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
    freeCompilerArgs += [
    '-Xopt-in=kotlin.RequiresOptIn',
    ]
    }
    }

    View full-size slide

  88. Opt-in APIs
    package com.example.lib.core
    @RequiresOptIn(
    level = RequiresOptIn.Level.ERROR,
    message = "This is internal API for my library, " +
    "please don't rely on it."
    )
    public annotation class InternalMyLibraryApi
    tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
    freeCompilerArgs += [
    '-Xopt-in=kotlin.RequiresOptIn',
    ]
    }
    }

    View full-size slide

  89. Opt-in APIs
    package com.example.lib.core
    @RequiresOptIn(
    level = RequiresOptIn.Level.ERROR,
    message = "This is internal API for my library, " +
    "please don't rely on it."
    )
    public annotation class InternalMyLibraryApi
    tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
    freeCompilerArgs += [
    '-Xopt-in=kotlin.RequiresOptIn',
    ]
    }
    }

    View full-size slide

  90. Opt-in APIs
    package com.example.lib.core
    @RequiresOptIn(
    level = RequiresOptIn.Level.ERROR,
    message = "This is internal API for my library, " +
    "please don't rely on it."
    )
    public annotation class InternalMyLibraryApi
    @InternalMyLibraryApi
    public fun coreApi()

    View full-size slide

  91. package com.example.lib.core
    @RequiresOptIn(
    level = RequiresOptIn.Level.ERROR,
    message = "This is internal API for my library, " +
    "please don't rely on it."
    )
    public annotation class InternalMyLibraryApi
    @InternalMyLibraryApi
    public fun coreApi()
    core
    addon
    Opt-in APIs

    View full-size slide

  92. public fun addonFunction() {
    coreApi()
    }
    package com.example.lib.core
    @RequiresOptIn(
    level = RequiresOptIn.Level.ERROR,
    message = "This is internal API for my library, " +
    "please don't rely on it."
    )
    public annotation class InternalMyLibraryApi
    @InternalMyLibraryApi
    public fun coreApi()
    core
    addon
    Opt-in APIs

    View full-size slide

  93. core
    addon
    @InternalMyLibraryApi
    public fun coreApi()
    Opting in
    public fun addonFunction() {
    coreApi()
    }

    View full-size slide

  94. Opting in
    @InternalMyLibraryApi
    public fun coreApi()
    @InternalMyLibraryApi
    public fun addonFunction() {
    coreApi()
    }

    View full-size slide

  95. Opting in
    tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
    freeCompilerArgs += [
    '-Xopt-in=com.example.lib.core.InternalMyLibraryApi',
    ]
    }
    }
    @InternalMyLibraryApi
    public fun coreApi()
    public fun addonFunction() {
    coreApi()
    }
    @InternalMyLibraryApi

    View full-size slide

  96. Opting in
    @InternalMyLibraryApi
    public fun coreApi()
    public fun addonFunction() {
    coreApi()
    }

    View full-size slide

  97. Opting in
    @InternalMyLibraryApi
    public fun coreApi()
    public fun addonFunction() {
    @OptIn(InternalMyLibraryApi::class)
    coreApi()
    }

    View full-size slide

  98. Opting in
    @InternalMyLibraryApi
    public fun coreApi()
    public fun addonFunction() {
    @OptIn(InternalMyLibraryApi::class)
    coreApi()
    }
    tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
    freeCompilerArgs += [
    '-Xopt-in=kotlin.RequiresOptIn',
    ]
    }
    }

    View full-size slide

  99. Opting in
    @InternalMyLibraryApi
    public fun coreApi()
    public fun addonFunction() {
    @OptIn(InternalMyLibraryApi::class)
    coreApi()
    }
    tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
    freeCompilerArgs += [
    '-Xopt-in=kotlin.RequiresOptIn',
    ]
    }
    }

    View full-size slide

  100. Opting in
    @InternalMyLibraryApi
    public fun coreApi()
    public fun addonFunction() {
    @OptIn(InternalMyLibraryApi::class)
    coreApi()
    }
    tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
    freeCompilerArgs += [
    '-Xopt-in=kotlin.RequiresOptIn',
    ]
    }
    }

    View full-size slide

  101. Opting in
    @InternalMyLibraryApi
    public fun coreApi()
    @OptIn(InternalMyLibraryApi::class)
    public fun addonFunction() {
    coreApi()
    }
    tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
    freeCompilerArgs += [
    '-Xopt-in=kotlin.RequiresOptIn',
    ]
    }
    }

    View full-size slide

  102. Opting in
    @InternalMyLibraryApi
    @JvmSynthetic
    public fun coreApi()
    @OptIn(InternalMyLibraryApi::class)
    public fun addonFunction() {
    coreApi()
    }
    tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
    freeCompilerArgs += [
    '-Xopt-in=kotlin.RequiresOptIn',
    ]
    }
    }

    View full-size slide

  103. Opt-in conventions
    InternalCoroutinesApi
    DelicateCoroutinesApi
    ExperimentalCoroutinesApi
    ObsoleteCoroutinesApi

    View full-size slide

  104. Android time

    View full-size slide

  105. Android time

    View full-size slide

  106. Android time
    implementation 'androidx.annotation:annotation:1.1.0'

    View full-size slide

  107. Android time
    implementation 'androidx.annotation:annotation-experimental:1.1.0'

    View full-size slide

  108. core
    Validating API

    View full-size slide

  109. core
    Validating API
    buildscript {
    dependencies {
    classpath 'org.jetbrains.kotlinx:binary-compatibility-validator:0.5.0'
    }
    }
    apply plugin: 'binary-compatibility-validator'

    View full-size slide

  110. core
    apiValidation {
    ignoredPackages += [
    'com/getstream/sdk/chat/databinding',
    'io/getstream/chat/android/ui/databinding',
    ]
    ignoredProjects += [
    'stream-chat-android-docs',
    'stream-chat-android-sample',
    'stream-chat-android-ui-components-sample',
    'stream-chat-android-test',
    ]
    nonPublicMarkers += [
    'io.getstream.chat.android.core.internal.InternalStreamChatApi',
    ]
    }
    Validating API

    View full-size slide

  111. core
    apiValidation {
    ignoredPackages += [
    'com/getstream/sdk/chat/databinding',
    'io/getstream/chat/android/ui/databinding',
    ]
    ignoredProjects += [
    'stream-chat-android-docs',
    'stream-chat-android-sample',
    'stream-chat-android-ui-components-sample',
    'stream-chat-android-test',
    ]
    nonPublicMarkers += [
    'io.getstream.chat.android.core.internal.InternalStreamChatApi',
    ]
    }
    Validating API

    View full-size slide

  112. core
    apiValidation {
    ignoredPackages += [
    'com/getstream/sdk/chat/databinding',
    'io/getstream/chat/android/ui/databinding',
    ]
    ignoredProjects += [
    'stream-chat-android-docs',
    'stream-chat-android-sample',
    'stream-chat-android-ui-components-sample',
    'stream-chat-android-test',
    ]
    nonPublicMarkers += [
    'io.getstream.chat.android.core.internal.InternalStreamChatApi',
    ]
    }
    Validating API

    View full-size slide

  113. core
    apiValidation {
    ignoredPackages += [
    'com/getstream/sdk/chat/databinding',
    'io/getstream/chat/android/ui/databinding',
    ]
    ignoredProjects += [
    'stream-chat-android-docs',
    'stream-chat-android-sample',
    'stream-chat-android-ui-components-sample',
    'stream-chat-android-test',
    ]
    nonPublicMarkers += [
    'io.getstream.chat.android.core.internal.InternalStreamChatApi',
    ]
    }
    Validating API

    View full-size slide

  114. core
    Validating API gradlew apiDump

    View full-size slide

  115. core
    Validating API
    public abstract interface class io/getstream/chat/android/client/ChatEventListener {
    public abstract fun onEvent (Lio/getstream/chat/android/client/events/ChatEvent;)V
    }
    public final class io/getstream/chat/android/client/api/models/AutocompleteFilterObject :
    io/getstream/chat/android/client/api/models/FilterObject {
    public final fun component1 ()Ljava/lang/String;
    public final fun component2 ()Ljava/lang/String;
    public final fun copy
    (Ljava/lang/String;Ljava/lang/String;)Lio/getstream/chat/android/client/api/models/Autocompl
    eteFilterObject;
    public fun equals (Ljava/lang/Object;)Z
    public final fun getFieldName ()Ljava/lang/String;
    public final fun getValue ()Ljava/lang/String;
    public fun hashCode ()I
    public fun toString ()Ljava/lang/String;
    }
    gradlew apiDump

    View full-size slide

  116. core
    Validating API gradlew apiCheck

    View full-size slide

  117. core
    Validating API
    Execution failed for task ':stream-chat-android-client:apiCheck'.
    > API check failed for project stream-chat-android-client.
    @@ -218,9 +218,8 @@
    public final class io/getstream/chat/android/client/api/models/AutocompleteFilterObject :
    io/getstream/chat/android/client/api/models/FilterObject {
    public final fun component1 ()Ljava/lang/String;
    - public final fun component2 ()Ljava/lang/String;
    - public final fun copy
    (Ljava/lang/String;Ljava/lang/String;)Lio/getstream/chat/android/client/api/models/Autocompl
    eteFilterObject;
    + public final fun copy
    (Ljava/lang/String;)Lio/getstream/chat/android/client/api/models/AutocompleteFilterObject;
    public fun equals (Ljava/lang/Object;)Z
    public final fun getFieldName ()Ljava/lang/String;
    public final fun getValue ()Ljava/lang/String;
    You can run :stream-chat-android-client:apiDump task to overwrite API declarations
    gradlew apiCheck

    View full-size slide

  118. core
    Validating API
    Execution failed for task ':stream-chat-android-client:apiCheck'.
    > API check failed for project stream-chat-android-client.
    @@ -218,9 +218,8 @@
    public final class io/getstream/chat/android/client/api/models/AutocompleteFilterObject :
    io/getstream/chat/android/client/api/models/FilterObject {
    public final fun component1 ()Ljava/lang/String;
    - public final fun component2 ()Ljava/lang/String;
    - public final fun copy
    (Ljava/lang/String;Ljava/lang/String;)Lio/getstream/chat/android/client/api/models/Autocompl
    eteFilterObject;
    + public final fun copy
    (Ljava/lang/String;)Lio/getstream/chat/android/client/api/models/AutocompleteFilterObject;
    public fun equals (Ljava/lang/Object;)Z
    public final fun getFieldName ()Ljava/lang/String;
    public final fun getValue ()Ljava/lang/String;
    You can run :stream-chat-android-client:apiDump task to overwrite API declarations
    gradlew apiCheck

    View full-size slide

  119. core
    Validating API
    Execution failed for task ':stream-chat-android-client:apiCheck'.
    > API check failed for project stream-chat-android-client.
    @@ -218,9 +218,8 @@
    public final class io/getstream/chat/android/client/api/models/AutocompleteFilterObject :
    io/getstream/chat/android/client/api/models/FilterObject {
    public final fun component1 ()Ljava/lang/String;
    - public final fun component2 ()Ljava/lang/String;
    - public final fun copy
    (Ljava/lang/String;Ljava/lang/String;)Lio/getstream/chat/android/client/api/models/Autocompl
    eteFilterObject;
    + public final fun copy
    (Ljava/lang/String;)Lio/getstream/chat/android/client/api/models/AutocompleteFilterObject;
    public fun equals (Ljava/lang/Object;)Z
    public final fun getFieldName ()Ljava/lang/String;
    public final fun getValue ()Ljava/lang/String;
    You can run :stream-chat-android-client:apiDump task to overwrite API declarations
    gradlew apiCheck

    View full-size slide

  120. core
    Validating API

    View full-size slide

  121. • Mastering API Visibility in Kotlin
     https://zsmb.co/mastering-api-visibility-in-kotlin/
    • Maintaining Compatibility in Kotlin Libraries
     https://zsmb.co/maintaining-compatibility-in-kotlin-libraries/
    • How to Build Awesome Android Libraries
     https://zsmb.co/talks/how-to-build-awesome-android-libraries/
    • Effective Java, 3rd Edition
     https://www.amazon.com/Effective-Java-Joshua-Bloch/dp/0134685997
    • Effective Kotlin
     https://leanpub.com/effectivekotlin/
    Resources

    View full-size slide

  122. zsmb13
    zsmb.co/talks

    View full-size slide

  123. Mastering API
    Visibility in Kotlin
    zsmb.co/talks
    zsmb13
    Márton Braun
    › Minimal API is nice
    › Internal visibility
    › Explicit API mode
    › Published API
    › Opt-in APIs

    View full-size slide