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 Slide

  2. API surface

    View Slide

  3. API surface

    View Slide

  4. API surface

    View Slide

  5. API surface

    View Slide

  6. API surface

    View Slide

  7. API surface

    View Slide

  8. API surface

    View Slide

  9. API surface

    View Slide

  10. API surface

    View Slide

  11. API surface

    View Slide

  12. API surface

    View Slide

  13. API surface

    View Slide

  14. API surface

    View Slide

  15. API surface

    View Slide

  16. API surface

    View Slide

  17. API surface

    View Slide

  18. API surface

    View Slide

  19. Minimal APIs

    View Slide

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

    View Slide

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

    View Slide

  22. Minimal APIs
    › Easier to maintain and change

    View Slide

  23. Minimal APIs
    › Easier to maintain and change

    View Slide

  24. Minimal APIs
    › Easier to maintain and change

    View Slide

  25. Minimal APIs
    › Easier to maintain and change

    View Slide

  26. Minimal APIs
    › Easier to maintain and change

    View Slide

  27. Minimal APIs
    › Easier to maintain and change

    View Slide

  28. Minimal APIs
    › Easier to maintain and change

    View Slide

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

    View Slide

  30. 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 Slide

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

  32. 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 Slide

  33. 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 Slide

  34. 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 Slide

  35. 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 Slide

  36. 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 Slide

  37. 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 Slide

  38. Minimal APIs
    › Easier to maintain and change

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  43. Planning

    View Slide

  44. Planning

    View Slide

  45. Planning

    View Slide

  46. Planning

    View Slide

  47. Visibility in Kotlin
    public

    View Slide

  48. Visibility in Kotlin
    public
    private

    View Slide

  49. Visibility in Kotlin
    public
    private internal

    View Slide

  50. Visibility in Kotlin
    public
    private internal

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  54. Internal visibility

    View Slide

  55. Testing

    View Slide

  56. Testing
    private var state: State

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  61. Java interop

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  68. Explicit API mode

    View Slide

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

    View Slide

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

    View Slide

  71. Explicit API mode
    kotlin {
    explicitApi()
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  77. 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 Slide

  78. 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 Slide

  79. 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 Slide

  80. 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  90. Published API

    View Slide

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

    View Slide

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

    View Slide

  93. 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

  97. 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 Slide

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

    View Slide

  99. View Slide

  100. Opt-in APIs
    core

    View Slide

  101. Opt-in APIs
    core
    addon

    View Slide

  102. Opt-in APIs
    core
    addon

    View Slide

  103. core
    addon
    Opt-in APIs

    View Slide

  104. core
    addon
    Opt-in APIs

    View Slide

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

    View Slide

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

    View Slide

  107. 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 Slide

  108. 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 Slide

  109. 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 Slide

  110. 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 Slide

  111. 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 Slide

  112. 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 Slide

  113. 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 Slide

  114. 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 Slide

  115. 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 Slide

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

    View Slide

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

    View Slide

  118. 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 Slide

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

    View Slide

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

    View Slide

  121. 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 Slide

  122. 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 Slide

  123. 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 Slide

  124. 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 Slide

  125. 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 Slide

  126. Opt-in conventions
    InternalCoroutinesApi
    DelicateCoroutinesApi
    ExperimentalCoroutinesApi
    ObsoleteCoroutinesApi

    View Slide

  127. Android time

    View Slide

  128. Android time

    View Slide

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

    View Slide

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

    View Slide

  131. core
    Validating API

    View Slide

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

    View Slide

  133. 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 Slide

  134. 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 Slide

  135. 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 Slide

  136. 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 Slide

  137. core
    Validating API gradlew apiDump

    View Slide

  138. 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 Slide

  139. core
    Validating API gradlew apiCheck

    View Slide

  140. 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 Slide

  141. 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 Slide

  142. 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 Slide

  143. core
    Validating API

    View Slide

  144. • 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 Slide

  145. zsmb13
    zsmb.co/talks

    View Slide

  146. 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 Slide