| ## Kotlin-specific guidelines {#kotlin} |
| |
| Generally speaking, Kotlin code should follow the compatibility guidelines |
| outlined at: |
| |
| - The official Android Developers |
| [Kotlin-Java interop guide](https://developer.android.com/kotlin/interop) |
| - Android API guidelines for |
| [Kotlin-Java interop](https://android.googlesource.com/platform/developers/docs/+/refs/heads/master/api-guidelines/index.md#kotin-interop) |
| - Android API guidelines for |
| [asynchronous and non-blocking APIs](https://android.googlesource.com/platform/developers/docs/+/refs/heads/master/api-guidelines/async.md) |
| - Library-specific guidance outlined below |
| |
| ### Target language version {#kotlin-target} |
| |
| All projects in AndroidX compile using the same version of the Kotlin |
| compiler -- typically the latest stable version -- and by default use a matching |
| *target language version*. The target language version specifies which Kotlin |
| features may be used in source code, which in turn specifies (1) which version |
| of `kotlin-stdlib` is used as a dependency and thus (2) which version of the |
| Kotlin compiler is required when the library is used as a dependency. |
| |
| Libraries may specify `kotlinTarget` in their `build.gradle` to override the |
| default target language version. Using a higher language version will force |
| clients to use a newer, typically less-stable Kotlin compiler but allows use of |
| newer language features. Using a lower language version will allow clients to |
| use an older Kotlin compiler when building their own projects. |
| |
| ``` |
| androidx { |
| kotlinTarget = KotlinVersion.KOTLIN_1_7 |
| } |
| ``` |
| |
| NOTE The client's Kotlin compiler version is bounded by their *transitive |
| dependencies*. If your library uses target language version 1.7 but you depend |
| on a library with target language version 1.9, the client will be forced to use |
| 1.9 or higher. |
| |
| ### Nullability |
| |
| #### Annotations on new Java APIs |
| |
| All new Java APIs should be annotated either `@Nullable` or `@NonNull` for all |
| reference parameters and reference return types. |
| |
| ```java |
| @Nullable |
| public Object someNewApi(@NonNull Thing arg1, @Nullable List<WhatsIt> arg2) { |
| if(/** something **/) { |
| return someObject; |
| } else { |
| return null; |
| } |
| ``` |
| |
| #### Adding annotations to existing Java APIs |
| |
| Adding `@Nullable` or `@NonNull` annotations to existing APIs to document their |
| existing nullability is allowed. This is a source-breaking change for Kotlin |
| consumers, and you should ensure that it's noted in the release notes and try to |
| minimize the frequency of these updates in releases. |
| |
| Changing the nullability of an API is a behavior-breaking change and should be |
| avoided. |
| |
| #### Extending APIs that are missing annotations |
| |
| [Platform types](https://kotlinlang.org/docs/java-interop.html#null-safety-and-platform-types) |
| are exposed by Java types that do not have a `@Nullable` or `@NonNull` |
| annotation. In Kotlin they are indicated with the `!` suffix. |
| |
| When interacting with an Android platform API that exposes APIs with unknown |
| nullability follow these rules: |
| |
| 1. If wrapping the type in a new API, define and handle `@Nullable` or |
| `@NonNull` in the library. Treat types with unknown nullability passed into |
| or return from Android as `@Nullable` in the library. |
| 2. If extending an existing API (e.g. `@Override`), pass through the existing |
| types with unknown nullability and annotate each with |
| `@SuppressLint("UnknownNullness")` |
| |
| In Kotlin, a type with unknown nullability is exposed as a "platform type" |
| (indicated with a `!` suffix) which has unknown nullability in the type checker, |
| and may bypass type checking leading to runtime errors. When possible, do not |
| directly expose types with unknown nullability in new public APIs. |
| |
| #### Extending `@RecentlyNonNull` and `@RecentlyNullable` APIs |
| |
| Platform APIs are annotated in the platform SDK artifacts with fake annotations |
| `@RecentlyNonNull` and `@RecentlyNullable` to avoid breaking builds when we |
| annotated platform APIs with nullability. These annotations cause warnings |
| instead of build failures. The `RecentlyNonNull` and `RecentlyNullable` |
| annotations are added by Metalava and do not appear in platform code. |
| |
| When extending an API that is annotated `@RecentlyNonNull`, you should annotate |
| the override with `@NonNull`, and the same for `@RecentlyNullable` and |
| `@Nullable`. |
| |
| For example `SpannableStringBuilder.append` is annotated `RecentlyNonNull` and |
| an override should look like: |
| |
| ```java |
| @NonNull |
| @Override |
| public SpannableStringBuilder append(@SuppressLint("UnknownNullness") CharSequence text) { |
| super.append(text); |
| return this; |
| } |
| ``` |
| |
| ### Data classes {#kotlin-data} |
| |
| Kotlin `data` classes provide a convenient way to define simple container |
| objects, where Kotlin will generate `equals()` and `hashCode()` for you. |
| However, they are not designed to preserve API/binary compatibility when members |
| are added. This is due to other methods which are generated for you - |
| [destructuring declarations](https://kotlinlang.org/docs/reference/multi-declarations.html), |
| and [copying](https://kotlinlang.org/docs/reference/data-classes.html#copying). |
| |
| Example data class as tracked by metalava: |
| |
| <pre> |
| public final class TargetAnimation { |
| ctor public TargetAnimation(float target, androidx.animation.AnimationBuilder animation); |
| <b>method public float component1();</b> |
| <b>method public androidx.animation.AnimationBuilder component2();</b> |
| <b>method public androidx.animation.TargetAnimation copy(float target, androidx.animation.AnimationBuilder animation);</b> |
| method public androidx.animation.AnimationBuilder getAnimation(); |
| method public float getTarget(); |
| } |
| </pre> |
| |
| Because members are exposed as numbered components for destructuring, you can |
| only safely add members at the end of the member list. As `copy` is generated |
| with every member name in order as well, you'll also have to manually |
| re-implement any old `copy` variants as items are added. If these constraints |
| are acceptable, data classes may still be useful to you. |
| |
| As a result, Kotlin `data` classes are *strongly discouraged* in library APIs. |
| Instead, follow best-practices for Java data classes including implementing |
| `equals`, `hashCode`, and `toString`. |
| |
| See Jake Wharton's article on |
| [Public API challenges in Kotlin](https://jakewharton.com/public-api-challenges-in-kotlin/) |
| for more details. |
| |
| ### Flow return type {#flow-return-type} |
| |
| Always prefer non-null for `Flow` objects, return a Flow that does not emit |
| items as a default. One option is `emptyFlow()` which will complete. Another |
| option is `flow { awaitCancellation() }` which will not emit and not complete. |
| Choose the option that best suites the use-case. |
| |
| ```kotlin |
| fun myFlowFunction(): Flow<Data> { |
| return if (canCreateFlow()) { |
| createFlow() |
| } else { |
| emptyFlow() |
| } |
| } |
| ``` |
| |
| ### Exhaustive `when` and `sealed class`/`enum class` {#exhaustive-when} |
| |
| A key feature of Kotlin's `sealed class` and `enum class` declarations is that |
| they permit the use of **exhaustive `when` expressions.** For example: |
| |
| ```kotlin |
| enum class CommandResult { Permitted, DeniedByUser } |
| |
| val message = when (commandResult) { |
| Permitted -> "the operation was permitted" |
| DeniedByUser -> "the user said no" |
| } |
| |
| println(message) |
| ``` |
| |
| This highlights challenges for library API design and compatibility. Consider |
| the following addition to the `CommandResult` possibilities: |
| |
| ```kotlin {.bad} |
| enum class CommandResult { |
| Permitted, |
| DeniedByUser, |
| DeniedByAdmin // New in androidx.mylibrary:1.1.0! |
| } |
| ``` |
| |
| This change is both **source and binary breaking.** |
| |
| It is **source breaking** because the author of the `when` block above will see |
| a compiler error about not handling the new result value. |
| |
| It is **binary breaking** because if the `when` block above was compiled as part |
| of a library `com.example.library:1.0.0` that transitively depends on |
| `androidx.mylibrary:1.0.0`, and an app declares the dependencies: |
| |
| ```kotlin |
| implementation("com.example.library:1.0.0") |
| implementation("androidx.mylibrary:1.1.0") // Updated! |
| ``` |
| |
| `com.example.library:1.0.0` does not handle the new result value, leading to a |
| runtime exception. |
| |
| **Note:** The above example is one where Kotlin's `enum class` is the correct |
| tool and the library should **not** add a new constant! Kotlin turns this |
| semantic API design problem into a compiler or runtime error. This type of |
| library API change could silently cause app logic errors or data corruption |
| without the protection provided by exhaustive `when`. See |
| [When to use exhaustive types](#when-to-use-exhaustive-types). |
| |
| `sealed class` exhibits the same characteristic; adding a new subtype of an |
| existing sealed class is a breaking change for the following code: |
| |
| ```kotlin |
| val message = when (command) { |
| is Command.Migrate -> "migrating to ${command.destination}" |
| is Command.Quack -> "quack!" |
| } |
| ``` |
| |
| #### Non-exhaustive alternatives to `enum class` |
| |
| Kotlin's `@JvmInline value class` with a `private constructor` can be used to |
| create type-safe sets of non-exhaustive constants as of Kotlin 1.5. Compose's |
| `BlendMode` uses the following pattern: |
| |
| ```kotlin {.good} |
| @JvmInline |
| value class BlendMode private constructor(val value: Int) { |
| companion object { |
| /** Drop both the source and destination images, leaving nothing. */ |
| val Clear = BlendMode(0) |
| /** Drop the destination image, only paint the source image. */ |
| val Src = BlendMode(1) |
| // ... |
| } |
| } |
| ``` |
| |
| **Note:** This recommendation may be temporary. Kotlin may add new annotations |
| or other language features to declare non-exhaustive enum classes in the future. |
| |
| Alternatively, the existing `@IntDef` mechanism used in Java-language androidx |
| libraries may also be used, but type checking of constants will only be |
| performed by lint, and functions overloaded with parameters of different value |
| class types are not supported. Prefer the `@JvmInline value class` solution for |
| new code unless it would break local consistency with other API in the same |
| module that already uses `@IntDef` or compatibility with Java is required. |
| |
| #### Non-exhaustive alternatives to `sealed class` |
| |
| Abstract classes with constructors marked as `internal` or `private` can |
| represent the same subclassing restrictions of sealed classes as seen from |
| outside of a library module's own codebase: |
| |
| ```kotlin |
| abstract class Command private constructor() { |
| class Migrate(val destination: String) : Command() |
| object Quack : Command() |
| } |
| ``` |
| |
| Using an `internal` constructor will permit non-nested subclasses, but will |
| **not** restrict subclasses to the same package within the module, as sealed |
| classes do. |
| |
| #### When to use exhaustive types |
| |
| Use `enum class` or `sealed class` when the values or subtypes are intended to |
| be exhaustive by design from the API's initial release. Use non-exhaustive |
| alternatives when the set of constants or subtypes might expand in a minor |
| version release. |
| |
| Consider using an **exhaustive** (`enum class` or `sealed class`) type |
| declaration if: |
| |
| * The developer is expected to **accept** values of the type |
| * The developer is expected to **act** on **any and all** values received |
| |
| Consider using a **non-exhaustive** type declaration if: |
| |
| * The developer is expected to **provide** values of the type to APIs exposed |
| by the same module **only** |
| * The developer is expected to **ignore** unknown values received |
| |
| The `CommandResult` example above is a good example of a type that **should** |
| use the exhaustive `enum class`; `CommandResult`s are **returned** to the |
| developer and the developer cannot implement correct app behavior by ignoring |
| unrecognized result values. Adding a new result value would semantically break |
| existing code regardless of the language facility used to express the type. |
| |
| ```kotlin {.good} |
| enum class CommandResult { Permitted, DeniedByUser, DeniedByAdmin } |
| ``` |
| |
| Compose's `BlendMode` is a good example of a type that **should not** use the |
| exhaustive `enum class`; blending modes are used as arguments to Compose |
| graphics APIs and are not intended for interpretation by app code. Additionally, |
| there is historical precedent from `android.graphics` for new blending modes to |
| be added in the future. |
| |
| ### Extension and top-level functions {#kotlin-extension-functions} |
| |
| If your Kotlin file contains any symbols outside of class-like types |
| (extension/top-level functions, properties, etc), the file must be annotated |
| with `@JvmName`. This ensures unanticipated use-cases from Java callers don't |
| get stuck using `BlahKt` files. |
| |
| Example: |
| |
| ```kotlin {.bad} |
| package androidx.example |
| |
| fun String.foo() = // ... |
| ``` |
| |
| ```kotlin {.good} |
| @file:JvmName("StringUtils") |
| |
| package androidx.example |
| |
| fun String.foo() = // ... |
| ``` |
| |
| NOTE This guideline may be ignored for APIs that will only be referenced from |
| Kotlin sources, such as Compose. |
| |
| ### Extension functions on platform classes {#kotlin-extension-platform} |
| |
| While it may be tempting to backport new platform APIs using extension |
| functions, the Kotlin compiler will always resolve collisions between extension |
| functions and platform-defined methods by calling the platform-defined method -- |
| even if the method doesn't exist on earlier SDKs. |
| |
| ```kotlin {.bad} |
| fun AccessibilityNodeInfo.getTextSelectionEnd() { |
| // ... delegate to platform on SDK 18+ ... |
| } |
| ``` |
| |
| For the above example, any calls to `getTextSelectionEnd()` will resolve to the |
| platform method -- the extension function will never be used -- and crash with |
| `MethodNotFoundException` on older SDKs. |
| |
| Even when an extension function on a platform class does not collide with an |
| existing API *yet*, there is a possibility that a conflicting API with a |
| matching signature will be added in the future. As such, Jetpack libraries |
| should avoid adding extension functions on platform classes. |
| |
| ### Extension functions related to classes in the same module or file |
| |
| When the core type is in Java, one good use for extension functions is to create |
| more Kotlin friendly versions of the API. |
| |
| ```java |
| public class MyClass { |
| public void addMyListener(Executor e, Consumer<Data> listener) { ... } |
| public void removeMyListener(Consumer<Data> listener) { ... } |
| } |
| ``` |
| |
| ```kotlin |
| fun MyClass.dataFlow(): Flow<Data> |
| ``` |
| |
| When the core type is in Kotlin, extension functions may or may not be a good |
| fit for the API. Ask if the extension is part of the core abstraction or a new |
| layer of abstraction. One example of a new layer of abstraction is using a new |
| dependency that does not otherwise interact with the core class. Another example |
| is an abstraction that stands on its own such as a `Comparator` that implements |
| a non-canonical ordering. |
| |
| In general when adding extension functions, consider splitting them across |
| different files and naming the Java version of the files related to the use case |
| as opposed to putting everything in one file and using a `Util` suffix. |
| |
| ```kotlin {.bad} |
| @file:JvmName("WindowSizeClassUtil") |
| |
| fun Set<WindowSizeClass>.widestClass() : WindowSizeClass { ... } |
| |
| fun WindowSizeClass.scoreWithinWidthDp(widthDp: Int) { ... } |
| ``` |
| |
| ```kotlin |
| @file:JvmName("WindowSizeClassSelector") |
| |
| fun Set<WindowSizeClass>.widestClass() : WindowSizeClass { ... } |
| |
| // In another file |
| @file:JvmName("WindowSizeClassScoreCalculator") |
| |
| fun WindowSizeClass.scoreWithinWidthDp(widthDp: Int) { ... } |
| ``` |
| |
| ### Function parameters order {#kotlin-params-order} |
| |
| In Kotlin function parameters can have default values, which are used when you |
| skip the corresponding argument. |
| |
| If a default parameter precedes a parameter with no default value, the default |
| value can only be used by calling the function with named arguments: |
| |
| ```kotlin |
| fun foo( |
| someBoolean: Boolean = true, |
| someInt: Int, |
| ) { /*...*/ } |
| |
| // usage: |
| foo(1) // does not compile as we try to set 1 as a value for "someBoolean" and |
| // didn't specify "someInt". |
| foo(someInt = 1) // this compiles as we used named arguments syntax. |
| ``` |
| |
| To not force our users to use named arguments we enforce the following |
| parameters order for the public Kotlin functions: |
| |
| 1. All parameters without default values. |
| 2. All parameters with default values. |
| 3. An optional last parameter without default value which can be used as a |
| trailing lambda. |
| |
| ### Default interface methods {#kotlin-jvm-default} |
| |
| The Kotlin compiler is capable of generating Kotlin-specific default interface |
| methods that are compatible with Java 7 language level; however, Jetpack |
| libraries ship as Java 8 language level and should use the native Java |
| implementation of default methods. |
| |
| To maximize compatibility, Jetpack libraries should pass `-Xjvm-default=all` to |
| the Kotlin compiler: |
| |
| ``` |
| tasks.withType(KotlinCompile).configureEach { |
| kotlinOptions { |
| freeCompilerArgs += ["-Xjvm-default=all"] |
| } |
| } |
| ``` |
| |
| Before adding this argument, library owners must ensure that existing interfaces |
| with default methods in stable API surfaces are annotated with |
| `@JvmDefaultWithCompatibility` to preserve binary compatibility: |
| |
| 1. Any interface with stable default method implementations from before the |
| `all` conversion |
| 1. Any interface with stable methods that have default argument values from |
| before the `all` conversion |
| 1. Any interface that extends another `@JvmDefaultWithCompatibility` interface |
| |
| Unstable API surfaces do not need to be annotated, e.g. if the methods or whole |
| interface is `@RequiresOptIn` or was never released in a stable library version. |
| |
| One way to handle this task is to search the API `.txt` file from the latest |
| release for `default` or `optional` and add the annotation by hand, then look |
| for public sub-interfaces and add the annotation there as well. |