blob: 2fa9b8fba5c03528c4db26219e80e352bad4adba [file] [log] [blame] [view]
## Annotations {#annotation}
### Annotation processors {#annotation-processor}
Annotation processors should opt-in to incremental annotation processing to
avoid triggering a full recompilation on every client source code change. See
Gradle's
[Incremental annotation processing](https://docs.gradle.org/current/userguide/java_plugin.html#sec:incremental_annotation_processing)
documentation for information on how to opt-in.
### `@RequiresOptIn` APIs {#experimental-api}
Jetpack libraries may choose to annotate API surfaces as unstable using either
Kotlin's
[`@RequiresOptIn` meta-annotation](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-requires-opt-in/)
for APIs written in Kotlin or Jetpack's
[`@RequiresOptIn` meta-annotation](https://developer.android.com/reference/kotlin/androidx/annotation/RequiresOptIn)
for APIs written in Java.
> `@RequiresOptIn` at-a-glance:
>
> * Use for unstable API surfaces
> * Can be called by anyone
> * Documented in public documentation
> * Does not maintain compatibility
For either annotation, API surfaces marked as opt-in are considered alpha and
will be excluded from API compatibility guarantees. Due to the lack of
compatibility guarantees, stable libraries *must never* call experimental APIs
exposed by other libraries outside of their
[same-version group](#same-version-atomic-groups) and *may not* use the `@OptIn`
annotation except in the following cases:
* A library within a same-version group *may* call an experimental API exposed
by another library **within its same-version group**. In this case, API
compatibility guarantees are covered under the same-version group policies
and the library *may* use the `@OptIn` annotation to prevent propagation of
the experimental property. **Library owners must exercise care to ensure
that post-alpha APIs backed by experimental APIs actually meet the release
criteria for post-alpha APIs.**
* An `alpha` library may use experimental APIs from outside its same-version
group. These usages must be removed when the library moves to `beta`.
NOTE JetBrains's own usage of `@RequiresOptIn` in Kotlin language libraries
varies and may indicate binary instability, functional instability, or simply
that an API is really difficult to use. Jetpack libraries should treat instances
of `@RequiresOptIn` in JetBrains libraries as indicating **binary instability**
and avoid using them outside of `alpha`; however, teams are welcome to obtain
written assurance from JetBrains regarding binary stability of specific APIs.
`@RequiresOptIn` APIs that are guaranteed to remain binary compatible *may* be
used in `beta`, but usages must be removed when the library moves to `rc`.
#### When to mark an API surface as experimental
*Do not* use `@RequiresOptIn` for a stable API surface that is difficult to use.
It is not a substitute for a properly-designed API surface.
*Do not* use `@RequiresOptIn` for an API surface that is unreliable or unstable
because it is missing tests. It is not a substitute for a properly-tested API
surface, and all APIs -- including those in `alpha` -- are expected to be
functionally stable.
*Do not* use `@RequiresOptIn` for an internal-facing API surface. Use either the
appropriate language visibility (ex. `private` or `internal`) or `@RestrictTo`.
*Do not* use `@RequiresOptIn` for an API that you expect library developers to
call. Experimental APIs do not maintain binary compatibility guarantees, and you
will put external clients in a difficult situation.
*Do* use `@RequiresOptIn` for API surfaces that must be publicly available and
documented but need the flexibility to stay in `alpha` during the rest of the
library's `beta`, `rc`, or stable cycles, and continue to break compatibility in
`beta`.
#### How to mark an API surface as experimental
All libraries using `@RequiresOptIn` annotations *must* depend on the
`androidx.annotation:annotation-experimental` artifact regardless of whether
they are using the `androidx` or Kotlin annotation. This artifact provides Lint
enforcement of experimental usage restrictions for Kotlin callers as well as
Java (which the Kotlin annotation doesn't handle on its own, since it's a Kotlin
compiler feature). Libraries *may* include the dependency as `api`-type to make
`@OptIn` available to Java clients; however, this will also unnecessarily expose
the `@RequiresOptIn` annotation.
```java
dependencies {
implementation(project(":annotation:annotation-experimental"))
}
```
See Kotlin's
[opt-in requirements documentation](https://kotlinlang.org/docs/reference/opt-in-requirements.html)
for general usage information. If you are writing experimental Java APIs, you
will use the Jetpack
[`@RequiresOptIn` annotation](https://developer.android.com/reference/kotlin/androidx/annotation/RequiresOptIn)
rather than the Kotlin compiler's annotation.
#### How to transition an API out of experimental
When an API surface is ready to transition out of experimental, the annotation
may only be removed during an alpha pre-release stage. Removing the experimental
marker from an API is equivalent to adding the API to the current API surface.
When transitioning an entire feature surface out of experimental, you *should*
remove the definition for the associated experimental marker annotation.
When making any change to the experimental API surface, you *must* run
`./gradlew updateApi` prior to uploading your change.
NOTE Experimental marker annotation *are themselves* experimental, meaning that
it's considered binary compatible to refactor or remove an experimental marker
annotation.
### `@RestrictTo` APIs {#restricted-api}
Jetpack's library tooling supports hiding Java-visible (ex. `public` and
`protected`) APIs from developers using a combination of the `@RestrictTo`
source annotation, and the `@hide` docs annotation (`@suppress` in Kotlin).
These annotations **must** be paired together when used, and are validated as
part of presubmit checks for Java code.
> `@RestrictTo` at-a-glance:
>
> * Use for internal-facing API surfaces
> * Can be called within the specified `Scope`
> * Does not appear in public documentation
> * Does not maintain compatibility in most scopes
While restricted APIs do not appear in documentation and Android Studio will
warn against calling them, hiding an API does *not* provide strong guarantees
about usage:
* There are no runtime restrictions on calling hidden APIs
* Android Studio will not warn if hidden APIs are called using reflection
* Hidden APIs will still show in Android Studio's auto-complete
#### When to use `@hide` {#restricted-api-usage}
In other cases, avoid using `@hide` / `@suppress`. These annotations indicates
that developers should not call an API that is *technically* public from a Java
visibility perspective. Hiding APIs is often a sign of a poorly-abstracted API
surface, and priority should be given to creating public, maintainable APIs and
using Java visibility modifiers.
*Do not* use `@hide`/`@suppress` to bypass API tracking and review for
production APIs; instead, rely on API+1 and API Council review to ensure APIs
are reviewed on a timely basis.
*Do not* use `@hide`/`@suppress` for implementation detail APIs that are used
between libraries and could reasonably be made public.
*Do* use `@hide`/`@suppress` paired with `@RestrictTo(LIBRARY)` for
implementation detail APIs used within a single library (but prefer Java
language `private` or `default` visibility).
#### `RestrictTo.Scope` and inter- versus intra-library API surfaces {#private-api-types}
To maintain binary compatibility between different versions of libraries,
restricted API surfaces that are used between libraries within Jetpack
(inter-library APIs) must follow the same Semantic Versioning rules as public
APIs. Inter-library APIs should be annotated with the
`@RestrictTo(LIBRARY_GROUP)` source annotation and `@hide` docs annotation.
Restricted API surfaces used within a single library (intra-library APIs), on
the other hand, may be added or removed without any compatibility
considerations. It is safe to assume that developers *never* call these APIs,
even though it is technically feasible. Intra-library APIs should be annotated
with the `@RestrictTo(LIBRARY)` source annotation and `@hide` docs annotation.
In all cases, correctness and compatibility tracking are handled by AndroidX's
build system and lint checks.
The following table shows the visibility of a hypothetical API within Maven
coordinate `androidx.concurrent:concurrent` when annotated with a variety of
scopes:
<table>
<tr>
<td><code>RestrictTo.Scope</code></td>
<td>Visibility by Maven coordinate</td>
<td>Versioning</td>
<td>Note</td>
</tr>
<tr>
<td><code>LIBRARY</code></td>
<td><code>androidx.concurrent:concurrent</code></td>
<td>No compatibility guarantees (same as private)</td>
<td></td>
</tr>
<tr>
<td><code>LIBRARY_GROUP</code></td>
<td><code>androidx.concurrent:*</code></td>
<td>Semantic versioning (including deprecation)</td>
<td></td>
</tr>
<tr>
<td><code>LIBRARY_GROUP_PREFIX</code></td>
<td><code>androidx.*:*</code></td>
<td>Semantic versioning (including deprecation)</td>
<td></td>
</tr>
<tr>
<td><code>TEST</code></td>
<td><code>*</code></td>
<td>No compatibility guarantees (same as private)</td>
<td>Not recommended. Prefer language visibility, e.g. `internal` or package-private.</td>
</tr>
</table>
#### `@IntDef` `@StringDef` and `@LongDef` and visibility
All `@IntDef`, `@StringDef`, and `@LongDef` will be stripped from resulting
artifacts to avoid issues where compiler inlining constants removes information
as to which `@IntDef` defined the value of `1`. The annotations are extracted
and packaged separately to be read by Android Studio and lint which enforces the
types in application code.
* Libraries *must* `@hide` all `@IntDef`, `@StringDef`, and `@LongDef`
declarations.
* Libraries *must* expose constants used to define the `@IntDef` etc at the
same Java visibility as the hidden `@IntDef`
* Libraries *must* use `@RestrictTo` to create a warning when the type is used
incorrectly.
Here is a complete example of an `@IntDef`
```java
// constants match Java visibility of ExifStreamType
// code outside this module interacting with ExifStreamType uses these constants
public static final int STREAM_TYPE_FULL_IMAGE_DATA = 1;
public static final int STREAM_TYPE_EXIF_DATA_ONLY = 2;
/** @hide */
@RestrictTo(RestrictTo.Scope.LIBRARY) // Don't export ExifStreamType outside module
@Retention(RetentionPolicy.SOURCE)
@IntDef({
STREAM_TYPE_FULL_IMAGE_DATA,
STREAM_TYPE_EXIF_DATA_ONLY,
})
public @interface ExifStreamType {}
```
Java visibilty should be set as appropriate for the code in question (`private`,
`package` or `public`) and is unrelated to hiding.
For more, read the section in
[Android API Council Guidelines](https://android.googlesource.com/platform/developers/docs/+/refs/heads/master/api-guidelines/index.md#no-public-typedefs)
#### `*current.txt` File Explanation {#currenttxt}
In this example, `1.3.0-beta02.txt` is just used for an example. This will match
the current library version.
<table>
<tr>
<td><code>api/current.txt</code></td>
<td>All public APIs.</td>
</tr>
<tr>
<td><code>api/1.3.0-beta02.txt</code></td>
<td>All public APIs available in version <code>1.3.0-beta02</code>.
Used to enforce compatibility in later versions. This file is only
generated during Beta.</td>
</tr>
<tr>
<td><code>api/public_plus_experimental_current.txt </code></td>
<td>Superset of all public APIs (<code>api/current.txt</code>) and all
experimental/<code>RequiresOptIn</code> APIs.
</td>
</tr>
<tr>
<td><code>api/public_plus_experimental_1.3.0-beta03.txt</code></td>
<td>Superset of all public APIs (<code>api/1.3.0-beta02.txt.txt</code>) and all
experimental/RequiresOptIn APIs, as available in version
<code>1.3.0-beta02.txt</code>. Only generated during Beta.</td>
<tr>
<td><code>api/restricted_current.txt</code></td>
<td>Superset of all public APIs (<code>api/current.txt</code>) and
all <code>RestrictTo</code> APIs that require compatibility across
versions.
<p/>Specifically, includes <code>@RestrictTo(LIBRARY_GROUP)</code> and
<code>@RestrictTo(LIBRARY_GROUP_PREFIX)</code>.</td>
</tr>
<tr>
<td><code>api/restricted_1.3.0-beta02.txt.txt</code></td>
<td>Superset of all public APIs (<code>api/current.txt</code>) and
all <code>RestrictTo</code> APIs that require compatibility across
versions, as available in version <code>1.3.0-beta02.txt</code>.
<p/>
Specifically, includes <code>@RestrictTo(LIBRARY_GROUP)</code> and
<code>@RestrictTo(LIBRARY_GROUP_PREFIX)</code>. This file is only
generated during Beta.</td>
</tr>
</table>
## Constructors {#constructors}
### View constructors {#view-constructors}
The four-arg View constructor -- `View(Context, AttributeSet, int, int)` -- was
added in SDK 21 and allows a developer to pass in an explicit default style
resource rather than relying on a theme attribute to resolve the default style
resource. Because this API was added in SDK 21, care must be taken to ensure
that it is not called through any < SDK 21 code path.
Views *may* implement a four-arg constructor in one of the following ways:
1. Do not implement.
1. Implement and annotate with `@RequiresApi(21)`. This means the three-arg
constructor **must not** call into the four-arg constructor.
## Asynchronous work {#async}
### With return values {#async-return}
#### Kotlin
Traditionally, asynchronous work on Android that results in an output value
would use a callback; however, better alternatives exist for libraries.
Kotlin libraries should consider
[coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html) and
`suspend` functions for APIs according to the following rules, but please refer
to the guidance on [allowable dependencies](#dependencies-coroutines) before
adding a new dependency on coroutines.
Kotlin suspend fun vs blocking | Behavior
------------------------------------ | --------------------------
blocking function with @WorkerThread | API is blocking
suspend | API is async (e.g. Future)
In general, do not introduce a suspend function entirely to switch threads for
blocking calls. To do so correctly requires that we allow the developer to
configure the Dispatcher. As there is already a coroutines-based API for
changing dispatchers (withContext) that the caller may use to switch threads, it
is unecessary API overhead to provide a duplicate mechanism. In addition, it
unecessary limits callers to coroutine contexts.
```kotlin
// DO expose blocking calls as blocking calls
@WorkerThread
fun blockingCall()
// DON'T wrap in suspend functions (only to switch threads)
suspend fun blockingCallWrappedInSuspend(
dispatcher: CoroutineDispatcher = Dispatchers.Default
) = withContext(dispatcher) { /* ... */ }
// DO expose async calls as suspend funs
suspend fun asyncCall(): ReturnValue
// DON'T expose async calls as a callback-based API (for the main API)
fun asyncCall(executor: Executor, callback: (ReturnValue) -> Unit)
```
#### Java
Java libraries should prefer `ListenableFuture` and the
[`CallbackToFutureAdapter`](https://developer.android.com/reference/androidx/concurrent/futures/CallbackToFutureAdapter)
implementation provided by the `androidx.concurrent:concurrent-futures` library.
Functions and methods that return `ListenableFuture` should be suffixed by,
`Async` to reserve the shorter, unmodified name for a `suspend` method or
extension function in Kotlin that returns the value normally in accordance with
structured concurrency.
Libraries **must not** use `java.util.concurrent.CompletableFuture`, as it has a
large API surface that permits arbitrary mutation of the future's value and has
error-prone defaults.
See the [Dependencies](#dependencies) section for more information on using
Kotlin coroutines and Guava in your library.
### Cancellation
Libraries that expose APIs for performing asynchronous work should support
cancellation. There are *very few* cases where it is not feasible to support
cancellation.
Libraries that use `ListenableFuture` must be careful to follow the exact
specification of
[`Future.cancel(boolean mayInterruptIfRunning)`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html?is-external=true#cancel-boolean-)
behavior.
```java {.bad}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
// Does not support cancellation.
return false;
}
```
```java {.bad}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
// Aggressively does not support cancellation.
throw new UnsupportedOperationException();
}
```
```java {.good}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
// Pseudocode that ignores threading but follows the spec.
if (mCompleted
|| mCancelled
|| mRunning && !mayInterruptIfRunning) {
return false;
}
mCancelled = true;
return true;
}
```
### Avoid `synchronized` methods
Whenever multiple threads are interacting with shared (mutable) references those
reads and writes must be synchronized in some way. However synchronized blocks
make your code thread-safe at the expense of concurrent execution. Any time
execution enters a synchronized block or method any other thread trying to enter
a synchronized block on the same object has to wait; even if in practice the
operations are unrelated (e.g. they interact with different fields). This can
dramatically reduce the benefit of trying to write multi-threaded code in the
first place.
Locking with synchronized is a heavyweight form of ensuring ordering between
threads, and there are a number of common APIs and patterns that you can use
that are more lightweight, depending on your use case:
* Compute a value once and make it available to all threads
* Update Set and Map data structures across threads
* Allow a group of threads to process a stream of data concurrently
* Provide instances of a non-thread-safe type to multiple threads
* Update a value from multiple threads atomically
* Maintain granular control of your concurrency invariants
## 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
### 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.
### 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`.
#### 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.
### Function paremeters 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.
## Proguard configuration
Proguard configurations allow libraries to specify how post-processing tools
like optimizers and shrinkers should operate on library bytecode. Note that
while Proguard is the name of a specific tool, a Proguard configuration may be
read by R8 or any number of other post-processing tools.
NOTE Jetpack libraries **must not** run Proguard on their release artifacts. Do
not specify `minifyEnabled`, `shrinkResources`, or `proguardFiles` in your build
configuration.
### Bundling with a library
**Android libraries (AARs)** can bundle consumer-facing Proguard rules using the
`consumerProguardFiles` (*not* `proguardFiles`) field in their `build.gradle`
file's `defaultConfig`:
```
android {
defaultConfig {
consumerProguardFiles 'proguard-rules.pro'
}
}
```
Libraries *do not* need to specify this field on `buildTypes.all`.
**Java-only libraries (JARs)** can bundle consumer-facing Proguard rules by
placing the file under the `META-INF` resources directory. The file **must** be
named using the library's unique Maven coordinate to avoid build-time merging
issues:
```
<project>/src/main/resources/META-INF/proguard/androidx.core_core.pro
```