1## Kotlin-specific guidelines {#kotlin} 2 3Generally speaking, Kotlin code should follow the compatibility guidelines 4outlined at: 5 6- The official Android Developers 7 [Kotlin-Java interop guide](https://developer.android.com/kotlin/interop) 8- Android API guidelines for 9 [Kotlin-Java interop](https://android.googlesource.com/platform/developers/docs/+/refs/heads/master/api-guidelines/index.md#kotin-interop) 10- Android API guidelines for 11 [asynchronous and non-blocking APIs](https://android.googlesource.com/platform/developers/docs/+/refs/heads/master/api-guidelines/async.md) 12- Library-specific guidance outlined below 13 14### Target language version {#kotlin-target} 15 16All projects in AndroidX compile using the same version of the Kotlin 17compiler -- typically the latest stable version -- and by default use a matching 18*target language version*. The target language version specifies which Kotlin 19features may be used in source code, which in turn specifies (1) which version 20of `kotlin-stdlib` is used as a dependency and thus (2) which version of the 21Kotlin compiler is required when the library is used as a dependency. 22 23Libraries may specify `kotlinTarget` in their `build.gradle` to override the 24default target language version. Using a higher language version will force 25clients to use a newer, typically less-stable Kotlin compiler but allows use of 26newer language features. Using a lower language version will allow clients to 27use an older Kotlin compiler when building their own projects. 28 29``` 30androidx { 31 kotlinTarget = KotlinVersion.KOTLIN_1_7 32} 33``` 34 35NOTE The client's Kotlin compiler version is bounded by their *transitive 36dependencies*. If your library uses target language version 1.7 but you depend 37on a library with target language version 1.9, the client will be forced to use 381.9 or higher. 39 40### Nullability 41 42#### Annotations on new Java APIs 43 44All new Java APIs should be annotated either `@Nullable` or `@NonNull` for all 45reference parameters and reference return types. 46 47```java 48 @Nullable 49 public Object someNewApi(@NonNull Thing arg1, @Nullable List<WhatsIt> arg2) { 50 if(/** something **/) { 51 return someObject; 52 } else { 53 return null; 54 } 55``` 56 57#### Adding annotations to existing Java APIs 58 59Adding `@Nullable` or `@NonNull` annotations to existing APIs to document their 60existing nullability is allowed. This is a source-breaking change for Kotlin 61consumers, and you should ensure that it's noted in the release notes and try to 62minimize the frequency of these updates in releases. 63 64Changing the nullability of an API is a behavior-breaking change and should be 65avoided. 66 67#### Extending APIs that are missing annotations 68 69[Platform types](https://kotlinlang.org/docs/java-interop.html#null-safety-and-platform-types) 70are exposed by Java types that do not have a `@Nullable` or `@NonNull` 71annotation. In Kotlin they are indicated with the `!` suffix. 72 73When interacting with an Android platform API that exposes APIs with unknown 74nullability follow these rules: 75 761. If wrapping the type in a new API, define and handle `@Nullable` or 77 `@NonNull` in the library. Treat types with unknown nullability passed into 78 or return from Android as `@Nullable` in the library. 792. If extending an existing API (e.g. `@Override`), pass through the existing 80 types with unknown nullability and annotate each with 81 `@SuppressLint("UnknownNullness")` 82 83In Kotlin, a type with unknown nullability is exposed as a "platform type" 84(indicated with a `!` suffix) which has unknown nullability in the type checker, 85and may bypass type checking leading to runtime errors. When possible, do not 86directly expose types with unknown nullability in new public APIs. 87 88#### Extending `@RecentlyNonNull` and `@RecentlyNullable` APIs 89 90Platform APIs are annotated in the platform SDK artifacts with fake annotations 91`@RecentlyNonNull` and `@RecentlyNullable` to avoid breaking builds when we 92annotated platform APIs with nullability. These annotations cause warnings 93instead of build failures. The `RecentlyNonNull` and `RecentlyNullable` 94annotations are added by Metalava and do not appear in platform code. 95 96When extending an API that is annotated `@RecentlyNonNull`, you should annotate 97the override with `@NonNull`, and the same for `@RecentlyNullable` and 98`@Nullable`. 99 100For example `SpannableStringBuilder.append` is annotated `RecentlyNonNull` and 101an override should look like: 102 103```java 104 @NonNull 105 @Override 106 public SpannableStringBuilder append(@SuppressLint("UnknownNullness") CharSequence text) { 107 super.append(text); 108 return this; 109 } 110``` 111 112### Data classes {#kotlin-data} 113 114Kotlin `data` classes provide a convenient way to define simple container 115objects, where Kotlin will generate `equals()` and `hashCode()` for you. 116However, they are not designed to preserve API/binary compatibility when members 117are added. This is due to other methods which are generated for you - 118[destructuring declarations](https://kotlinlang.org/docs/reference/multi-declarations.html), 119and [copying](https://kotlinlang.org/docs/reference/data-classes.html#copying). 120 121Example data class as tracked by metalava: 122 123<pre> 124 public final class TargetAnimation { 125 ctor public TargetAnimation(float target, androidx.animation.AnimationBuilder animation); 126 <b>method public float component1();</b> 127 <b>method public androidx.animation.AnimationBuilder component2();</b> 128 <b>method public androidx.animation.TargetAnimation copy(float target, androidx.animation.AnimationBuilder animation);</b> 129 method public androidx.animation.AnimationBuilder getAnimation(); 130 method public float getTarget(); 131 } 132</pre> 133 134Because members are exposed as numbered components for destructuring, you can 135only safely add members at the end of the member list. As `copy` is generated 136with every member name in order as well, you'll also have to manually 137re-implement any old `copy` variants as items are added. If these constraints 138are acceptable, data classes may still be useful to you. 139 140As a result, Kotlin `data` classes are *strongly discouraged* in library APIs. 141Instead, follow best-practices for Java data classes including implementing 142`equals`, `hashCode`, and `toString`. 143 144See Jake Wharton's article on 145[Public API challenges in Kotlin](https://jakewharton.com/public-api-challenges-in-kotlin/) 146for more details. 147 148### Flow return type {#flow-return-type} 149 150Always prefer non-null for `Flow` objects, return a Flow that does not emit 151items as a default. One option is `emptyFlow()` which will complete. Another 152option is `flow { awaitCancellation() }` which will not emit and not complete. 153Choose the option that best suites the use-case. 154 155```kotlin 156fun myFlowFunction(): Flow<Data> { 157 return if (canCreateFlow()) { 158 createFlow() 159 } else { 160 emptyFlow() 161 } 162} 163``` 164 165### Exhaustive `when` and `sealed class`/`enum class` {#exhaustive-when} 166 167A key feature of Kotlin's `sealed class` and `enum class` declarations is that 168they permit the use of **exhaustive `when` expressions.** For example: 169 170```kotlin 171enum class CommandResult { Permitted, DeniedByUser } 172 173val message = when (commandResult) { 174 Permitted -> "the operation was permitted" 175 DeniedByUser -> "the user said no" 176} 177 178println(message) 179``` 180 181This highlights challenges for library API design and compatibility. Consider 182the following addition to the `CommandResult` possibilities: 183 184```kotlin {.bad} 185enum class CommandResult { 186 Permitted, 187 DeniedByUser, 188 DeniedByAdmin // New in androidx.mylibrary:1.1.0! 189} 190``` 191 192This change is both **source and binary breaking.** 193 194It is **source breaking** because the author of the `when` block above will see 195a compiler error about not handling the new result value. 196 197It is **binary breaking** because if the `when` block above was compiled as part 198of a library `com.example.library:1.0.0` that transitively depends on 199`androidx.mylibrary:1.0.0`, and an app declares the dependencies: 200 201```kotlin 202implementation("com.example.library:1.0.0") 203implementation("androidx.mylibrary:1.1.0") // Updated! 204``` 205 206`com.example.library:1.0.0` does not handle the new result value, leading to a 207runtime exception. 208 209**Note:** The above example is one where Kotlin's `enum class` is the correct 210tool and the library should **not** add a new constant! Kotlin turns this 211semantic API design problem into a compiler or runtime error. This type of 212library API change could silently cause app logic errors or data corruption 213without the protection provided by exhaustive `when`. See 214[When to use exhaustive types](#when-to-use-exhaustive-types). 215 216`sealed class` exhibits the same characteristic; adding a new subtype of an 217existing sealed class is a breaking change for the following code: 218 219```kotlin 220val message = when (command) { 221 is Command.Migrate -> "migrating to ${command.destination}" 222 is Command.Quack -> "quack!" 223} 224``` 225 226#### Non-exhaustive alternatives to `enum class` 227 228Kotlin's `@JvmInline value class` with a `private constructor` can be used to 229create type-safe sets of non-exhaustive constants as of Kotlin 1.5. Compose's 230`BlendMode` uses the following pattern: 231 232```kotlin {.good} 233@JvmInline 234value class BlendMode private constructor(val value: Int) { 235 companion object { 236 /** Drop both the source and destination images, leaving nothing. */ 237 val Clear = BlendMode(0) 238 /** Drop the destination image, only paint the source image. */ 239 val Src = BlendMode(1) 240 // ... 241 } 242} 243``` 244 245**Note:** This recommendation may be temporary. Kotlin may add new annotations 246or other language features to declare non-exhaustive enum classes in the future. 247 248Alternatively, the existing `@IntDef` mechanism used in Java-language androidx 249libraries may also be used, but type checking of constants will only be 250performed by lint, and functions overloaded with parameters of different value 251class types are not supported. Prefer the `@JvmInline value class` solution for 252new code unless it would break local consistency with other API in the same 253module that already uses `@IntDef` or compatibility with Java is required. 254 255#### Non-exhaustive alternatives to `sealed class` 256 257Abstract classes with constructors marked as `internal` or `private` can 258represent the same subclassing restrictions of sealed classes as seen from 259outside of a library module's own codebase: 260 261```kotlin 262abstract class Command private constructor() { 263 class Migrate(val destination: String) : Command() 264 object Quack : Command() 265} 266``` 267 268Using an `internal` constructor will permit non-nested subclasses, but will 269**not** restrict subclasses to the same package within the module, as sealed 270classes do. 271 272#### When to use exhaustive types 273 274Use `enum class` or `sealed class` when the values or subtypes are intended to 275be exhaustive by design from the API's initial release. Use non-exhaustive 276alternatives when the set of constants or subtypes might expand in a minor 277version release. 278 279Consider using an **exhaustive** (`enum class` or `sealed class`) type 280declaration if: 281 282* The developer is expected to **accept** values of the type 283* The developer is expected to **act** on **any and all** values received 284 285Consider using a **non-exhaustive** type declaration if: 286 287* The developer is expected to **provide** values of the type to APIs exposed 288 by the same module **only** 289* The developer is expected to **ignore** unknown values received 290 291The `CommandResult` example above is a good example of a type that **should** 292use the exhaustive `enum class`; `CommandResult`s are **returned** to the 293developer and the developer cannot implement correct app behavior by ignoring 294unrecognized result values. Adding a new result value would semantically break 295existing code regardless of the language facility used to express the type. 296 297```kotlin {.good} 298enum class CommandResult { Permitted, DeniedByUser, DeniedByAdmin } 299``` 300 301Compose's `BlendMode` is a good example of a type that **should not** use the 302exhaustive `enum class`; blending modes are used as arguments to Compose 303graphics APIs and are not intended for interpretation by app code. Additionally, 304there is historical precedent from `android.graphics` for new blending modes to 305be added in the future. 306 307### Extension and top-level functions {#kotlin-extension-functions} 308 309If your Kotlin file contains any symbols outside of class-like types 310(extension/top-level functions, properties, etc), the file must be annotated 311with `@JvmName`. This ensures unanticipated use-cases from Java callers don't 312get stuck using `BlahKt` files. 313 314Example: 315 316```kotlin {.bad} 317package androidx.example 318 319fun String.foo() = // ... 320``` 321 322```kotlin {.good} 323@file:JvmName("StringUtils") 324 325package androidx.example 326 327fun String.foo() = // ... 328``` 329 330NOTE This guideline may be ignored for APIs that will only be referenced from 331Kotlin sources, such as Compose. 332 333### Extension functions on platform classes {#kotlin-extension-platform} 334 335While it may be tempting to backport new platform APIs using extension 336functions, the Kotlin compiler will always resolve collisions between extension 337functions and platform-defined methods by calling the platform-defined method -- 338even if the method doesn't exist on earlier SDKs. 339 340```kotlin {.bad} 341fun AccessibilityNodeInfo.getTextSelectionEnd() { 342 // ... delegate to platform on SDK 18+ ... 343} 344``` 345 346For the above example, any calls to `getTextSelectionEnd()` will resolve to the 347platform method -- the extension function will never be used -- and crash with 348`MethodNotFoundException` on older SDKs. 349 350Even when an extension function on a platform class does not collide with an 351existing API *yet*, there is a possibility that a conflicting API with a 352matching signature will be added in the future. As such, Jetpack libraries 353should avoid adding extension functions on platform classes. 354 355### Extension functions related to classes in the same module or file 356 357When the core type is in Java, one good use for extension functions is to create 358more Kotlin friendly versions of the API. 359 360```java 361public class MyClass { 362 public void addMyListener(Executor e, Consumer<Data> listener) { ... } 363 public void removeMyListener(Consumer<Data> listener) { ... } 364} 365``` 366 367```kotlin 368fun MyClass.dataFlow(): Flow<Data> 369``` 370 371When the core type is in Kotlin, extension functions may or may not be a good 372fit for the API. Ask if the extension is part of the core abstraction or a new 373layer of abstraction. One example of a new layer of abstraction is using a new 374dependency that does not otherwise interact with the core class. Another example 375is an abstraction that stands on its own such as a `Comparator` that implements 376a non-canonical ordering. 377 378In general when adding extension functions, consider splitting them across 379different files and naming the Java version of the files related to the use case 380as opposed to putting everything in one file and using a `Util` suffix. 381 382```kotlin {.bad} 383@file:JvmName("WindowSizeClassUtil") 384 385fun Set<WindowSizeClass>.widestClass() : WindowSizeClass { ... } 386 387fun WindowSizeClass.scoreWithinWidthDp(widthDp: Int) { ... } 388``` 389 390```kotlin 391@file:JvmName("WindowSizeClassSelector") 392 393fun Set<WindowSizeClass>.widestClass() : WindowSizeClass { ... } 394 395// In another file 396@file:JvmName("WindowSizeClassScoreCalculator") 397 398fun WindowSizeClass.scoreWithinWidthDp(widthDp: Int) { ... } 399``` 400 401### Function parameters order {#kotlin-params-order} 402 403In Kotlin function parameters can have default values, which are used when you 404skip the corresponding argument. 405 406If a default parameter precedes a parameter with no default value, the default 407value can only be used by calling the function with named arguments: 408 409```kotlin 410fun foo( 411 someBoolean: Boolean = true, 412 someInt: Int, 413) { /*...*/ } 414 415// usage: 416foo(1) // does not compile as we try to set 1 as a value for "someBoolean" and 417 // didn't specify "someInt". 418foo(someInt = 1) // this compiles as we used named arguments syntax. 419``` 420 421To not force our users to use named arguments we enforce the following 422parameters order for the public Kotlin functions: 423 4241. All parameters without default values. 4252. All parameters with default values. 4263. An optional last parameter without default value which can be used as a 427 trailing lambda. 428 429### Default interface methods {#kotlin-jvm-default} 430 431The Kotlin compiler is capable of generating Kotlin-specific default interface 432methods that are compatible with Java 7 language level; however, Jetpack 433libraries ship as Java 8 language level and should use the native Java 434implementation of default methods. 435 436To maximize compatibility, Jetpack libraries should pass `-Xjvm-default=all` to 437the Kotlin compiler: 438 439``` 440tasks.withType(KotlinCompile).configureEach { 441 kotlinOptions { 442 freeCompilerArgs += ["-Xjvm-default=all"] 443 } 444} 445``` 446 447Before adding this argument, library owners must ensure that existing interfaces 448with default methods in stable API surfaces are annotated with 449`@JvmDefaultWithCompatibility` to preserve binary compatibility: 450 4511. Any interface with stable default method implementations from before the 452 `all` conversion 4531. Any interface with stable methods that have default argument values from 454 before the `all` conversion 4551. Any interface that extends another `@JvmDefaultWithCompatibility` interface 456 457Unstable API surfaces do not need to be annotated, e.g. if the methods or whole 458interface is `@RequiresOptIn` or was never released in a stable library version. 459 460One way to handle this task is to search the API `.txt` file from the latest 461release for `default` or `optional` and add the annotation by hand, then look 462for public sub-interfaces and add the annotation there as well. 463