blob: 16a00531f087eade54c8e766b49fba18ef3967c4 [file] [log] [blame] [view]
# Library API guidelines
[TOC]
## Introduction {#introduction}
This guide is an addendum to
s.android.com/api-guidelines,
which covers standard and practices for designing platform APIs.
All platform API design guidelines also apply to Jetpack libraries, with any
additional guidelines or exceptions noted in this document. Jetpack libraries
also follow
[explicit API mode](https://kotlinlang.org/docs/reference/whatsnew14.html#explicit-api-mode-for-library-authors)
for Kotlin libraries.
## Modules {#module}
### Packaging and naming {#module-naming}
Java packages within Jetpack follow the format `androidx.<feature-name>`. All
classes within a feature's artifact must reside within this package, and may
further subdivide into `androidx.<feature-name>.<layer>` using standard Android
layers (app, widget, etc.) or layers specific to the feature.
Maven specifications use the groupId format `androidx.<feature-name>` and
artifactId format `<feature-name>` to match the Java package. For example,
`androidx.core.role` uses the Maven spec `androidx.core:role`.
Sub-features that can be separated into their own artifact are recommended to
use the following formats:
- Java package: `androidx.<feature-name>.<sub-feature>.<layer>`
- Maven groupId: `androidx.<feature-name>`
- Maven artifactId: `<feature-name>-<sub-feature>`
Gradle project names and directories follow the Maven spec format, substituting
the project name separator `:` or directory separator `/` for the Maven
separators `.` or `:`. For example, `androidx.core:core-role` would use project
name `:core:core-role` and directory `/core/core-role`.
New modules in androidx can be created using the
[project creator script](#module-creator).
#### Project directory structure {#module-structure}
Libraries developed in AndroidX follow a consistent project naming and directory
structure.
Library groups should organize their projects into directories and project names
(in brackets) as:
```
<feature-name>/
<feature-name>-<sub-feature>/ [<feature-name>:<feature-name>-<sub-feature>]
samples/ [<feature-name>:<feature-name>-<sub-feature>:samples]
integration-tests/
testapp/ [<feature-name>:testapp]
testlib/ [<feature-name>:testlib]
```
For example, the `navigation` library group's directory structure is:
```
navigation/
navigation-benchmark/ [navigation:navigation-benchmark]
...
navigation-ui/ [navigation:navigation-ui]
navigation-ui-ktx/ [navigation:navigation-ui-ktx]
integration-tests/
testapp/ [navigation:integration-tests:testapp]
```
#### Project creator script {#module-creation}
Note: The terms *project*, *module*, and *library* are often used
interchangeably within AndroidX, with project being the technical term used by
Gradle to describe a build target, e.g. a library that maps to a single AAR.
New projects can be created using our
[project creation script](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:development/project-creator/?q=project-creator&ss=androidx%2Fplatform%2Fframeworks%2Fsupport)
available in our repo.
It will create a new project with the proper structure and configuration based
on your project needs!
To use it:
```sh
cd ~/androidx-main/frameworks/support && \
cd development/project-creator && \
./create_project.py androidx.foo foo-bar
```
#### Common sub-feature names {#module-naming-subfeature}
* `-testing` for an artifact intended to be used while testing usages of your
library, e.g. `androidx.room:room-testing`
* `-core` for a low-level artifact that *may* contain public APIs but is
primarily intended for use by other libraries in the group
* `-ktx` for an Kotlin artifact that exposes idiomatic Kotlin APIs as an
extension to a Java-only library (see
[additional -ktx guidance](#module-ktx))
* `-<third-party>` for an artifact that integrates an optional third-party API
surface, e.g. `-proto` or `-rxjava2`. Note that a major version is included
in the sub-feature name for third-party API surfaces where the major version
indicates binary compatibility (only needed for post-1.x).
Artifacts **should not** use `-impl` or `-base` to indicate that a library is an
implementation detail shared within the group. Instead, use `-core`.
#### Splitting existing modules
Existing modules *should not* be split into smaller modules; doing so creates
the potential for class duplication issues when a developer depends on a new
sub-module alongside the older top-level module. Consider the following
scenario:
* `androidx.library:1.0.0`
* contains class `androidx.library.A`
* contains class `androidx.library.util.B`
This module is split, moving `androidx.library.util.B` to a new module:
* `androidx.library:1.1.0`
* contains class `androidx.library.A`
* depends on `androidx.library.util:1.1.0`
* `androidx.library.util:1.1.0`
* contains class `androidx.library.util.B`
A developer writes an app that depends directly on `androidx.library.util:1.1.0`
and also transitively pulls in `androidx.library:1.0.0`. Their app will no
longer compile due to class duplication of `androidx.library.util.B`.
While it is possible for the developer to fix this by manually specifying a
dependency on `androidx.library:1.1.0`, there is no easy way for the developer
to discover this solution from the class duplication error raised at compile
time.
Same-version groups are a special case for this rule. Existing modules that are
already in a same-version group may be split into sub-modules provided that (a)
the sub-modules are also in the same-version group and (b) the full API surface
of the existing module is preserved through transitive dependencies, e.g. the
sub-modules are added as dependencies of the existing module.
#### Same-version (atomic) groups {#modules-atomic}
Library groups are encouraged to opt-in to a same-version policy whereby all
libraries in the group use the same version and express exact-match dependencies
on libraries within the group. Such groups must increment the version of every
library at the same time and release all libraries at the same time.
Atomic groups are specified in
[`LibraryGroups.kt`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:buildSrc/src/main/kotlin/androidx/build/LibraryGroups.kt):
```kotlin
// Non-atomic library group
val APPCOMPAT = LibraryGroup("androidx.appcompat", null)
// Atomic library group
val APPSEARCH = LibraryGroup("androidx.appsearch", LibraryVersions.APPSEARCH)
```
Libraries within an atomic group should not specify a version in their
`build.gradle`:
```groovy
androidx {
name = 'AppSearch'
publish = Publish.SNAPSHOT_AND_RELEASE
mavenGroup = LibraryGroups.APPSEARCH
inceptionYear = '2019'
description = 'Provides local and centralized app indexing'
}
```
The benefits of using an atomic group are:
- Easier for developers to understand dependency versioning
- `@RestrictTo(LIBRARY_GROUP)` APIs are treated as private APIs and not
tracked for binary compatibility
- `@RequiresOptIn` APIs defined within the group may be used without any
restrictions between libraries in the group
Potential drawbacks include:
- All libraries within the group must be versioned identically at head
- All libraries within the group must release at the same time
#### Early-stage development {#modules-atomic-alpha}
There is one exception to the same-version policy: newly-added libraries within
an atomic group may be "quarantined" from other libraries to allow for rapid
iteration until they are API-stable.
A quarantined library must stay within the `1.0.0-alphaXX` cycle until it is
ready to conform to the same-version policy. While in quarantime, a library is
treated at though it is in a separate group from its nomical same-version group:
- Must stay in `1.0.0-alphaXX`, e.g. same-version policy is not enforced
- May use `project` or pinned version dependencies, e.g. strict-match
dependencies are not enforced
- May release on a separate cadence from other libraries within group
- Must not reference restricted `LIBRARY-GROUP`-scoped APIs
When the library would like to leave quarantine, it must wait for its atomic
group to be within a `beta` cycle and then match the version. It is okay for a
library in this situation to skip versions, e.g. move directly from
`1.0.0-alpha02` to `2.1.3-beta06`.
### Choosing a `minSdkVersion` {#module-minsdkversion}
The recommended minimum SDK version for new Jetpack libraries is currently
**19** (Android 4.4, KitKat). This SDK was chosen to represent 99% of active
devices based on Play Store check-ins (see Android Studio
[distribution metadata](https://dl.google.com/android/studio/metadata/distributions.json)
for current statistics). This maximizes potential users for external developers
while minimizing the amount of overhead necessary to support legacy versions.
However, if no explicit minimum SDK version is specified for a library, the
default is **14** (Android 4.0, Ice Cream Sandwich).
Note that a library **must not** depend on another library with a higher
`minSdkVersion` that its own, so it may be necessary for a new library to match
its dependent libraries' `minSdkVersion`.
Individual modules may choose a higher minimum SDK version for business or
technical reasons. This is common for device-specific modules such as Auto or
Wear.
Individual classes or methods may be annotated with the
[@RequiresApi](https://developer.android.com/reference/android/annotation/RequiresApi.html)
annotation to indicate divergence from the overall module's minimum SDK version.
Note that this pattern is *not recommended* because it leads to confusion for
external developers and should be considered a last-resort when backporting
behavior is not feasible.
### Kotlin extension `-ktx` libraries {#module-ktx}
New libraries should prefer Kotlin sources with built-in Java compatibility via
`@JvmName` and other affordances of the Kotlin language; however, existing Java
sourced libraries may benefit from extending their API surface with
Kotlin-friendly APIs in a `-ktx` library.
A Kotlin extension library **may only** provide extensions for a single base
library's API surface and its name **must** match the base library exactly. For
example, `work:work-ktx` may only provide extensions for APIs exposed by
`work:work`.
Additionally, an extension library **must** specify an `api`-type dependency on
the base library and **must** be versioned and released identically to the base
library.
Kotlin extension libraries *should not* expose new functionality; they should
only provide Kotlin-friendly versions of existing Java-facing functionality.
## Platform compatibility API patterns {#platform-compatibility-apis}
NOTE For all library APIs that wrap or provide parity with platform APIs,
*parity with the platform APIs overrides API guidelines*. For example, if the
platform API being wrapped has incorrect `Executor` and `Callback` ordering
according to the API Guidelines, the corresponding library API should have the
exact same (incorrect) ordering.
### Static shims (ex. [ViewCompat](https://developer.android.com/reference/android/support/v4/view/ViewCompat.html)) {#static-shim}
When to use?
* Platform class exists at module's `minSdkVersion`
* Compatibility implementation does not need to store additional metadata
Implementation requirements
* Class name **must** be `<PlatformClass>Compat`
* Package name **must** be `androidx.<feature>.<platform.package>`
* Superclass **must** be `Object`
* Class **must** be non-instantiable, i.e. constructor is private no-op
* Static fields and static methods **must** match match signatures with
`PlatformClass`
* Static fields that can be inlined, ex. integer constants, **must not**
be shimmed
* Public method names **must** match platform method names
* Public methods **must** be static and take `PlatformClass` as first
parameter
* Implementation *may* delegate to `PlatformClass` methods when available
#### Sample {#static-shim-sample}
The following sample provides static helper methods for the platform class
`android.os.Process`.
```java
/**
* Helper for accessing features in {@link Process}.
*/
public final class ProcessCompat {
private ProcessCompat() {
// This class is non-instantiable.
}
/**
* [Docs should match platform docs.]
*
* Compatibility behavior:
* <ul>
* <li>SDK 24 and above, this method matches platform behavior.
* <li>SDK 16 through 23, this method is a best-effort to match platform behavior, but may
* default to returning {@code true} if an accurate result is not available.
* <li>SDK 15 and below, this method always returns {@code true} as application UIDs and
* isolated processes did not exist yet.
* </ul>
*
* @param [match platform docs]
* @return [match platform docs], or a value based on platform-specific fallback behavior
*/
public static boolean isApplicationUid(int uid) {
if (Build.VERSION.SDK_INT >= 24) {
return Api24Impl.isApplicationUid(uid);
} else if (Build.VERSION.SDK_INT >= 17) {
return Api17Impl.isApplicationUid(uid);
} else if (Build.VERSION.SDK_INT == 16) {
return Api16Impl.isApplicationUid(uid);
} else {
return true;
}
}
@RequiresApi(24)
static class Api24Impl {
static boolean isApplicationUid(int uid) {
// In N, the method was made public on android.os.Process.
return Process.isApplicationUid(uid);
}
}
@RequiresApi(17)
static class Api17Impl {
private static Method sMethod_isAppMethod;
private static boolean sResolved;
static boolean isApplicationUid(int uid) {
// In JELLY_BEAN_MR2, the equivalent isApp(int) hidden method moved to public class
// android.os.UserHandle.
try {
if (!sResolved) {
sResolved = true;
sMethod_isAppMethod = UserHandle.class.getDeclaredMethod("isApp",int.class);
}
if (sMethod_isAppMethod != null) {
return (Boolean) sMethod_isAppMethod.invoke(null, uid);
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
}
...
}
```
### Wrapper (ex. [AccessibilityNodeInfoCompat](https://developer.android.com/reference/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.html)) {#wrapper}
When to use?
* Platform class may not exist at module's `minSdkVersion`
* Compatibility implementation may need to store additional metadata
* Needs to integrate with platform APIs as return value or method argument
* **Note:** Should be avoided when possible, as using wrapper classes makes it
very difficult to deprecate classes and migrate source code when the
`minSdkVersion` is raised
#### Sample {#wrapper-sample}
The following sample wraps a hypothetical platform class `ModemInfo` that was
added to the platform SDK in API level 23:
```java
public final class ModemInfoCompat {
// Only guaranteed to be non-null on SDK_INT >= 23. Note that referencing the
// class itself directly is fine -- only references to class members need to
// be pushed into static inner classes.
private final ModemInfo wrappedObj;
/**
* [Copy platform docs for matching constructor.]
*/
public ModemInfoCompat() {
if (SDK_INT >= 23) {
wrappedObj = Api23Impl.create();
} else {
wrappedObj = null;
}
...
}
@RequiresApi(23)
private ModemInfoCompat(@NonNull ModemInfo obj) {
mWrapped = obj;
}
/**
* Provides a backward-compatible wrapper for {@link ModemInfo}.
* <p>
* This method is not supported on devices running SDK < 23 since the platform
* class will not be available.
*
* @param info platform class to wrap
* @return wrapped class, or {@code null} if parameter is {@code null}
*/
@RequiresApi(23)
@NonNull
public static ModemInfoCompat toModemInfoCompat(@NonNull ModemInfo info) {
return new ModemInfoCompat(obj);
}
/**
* Provides the {@link ModemInfo} represented by this object.
* <p>
* This method is not supported on devices running SDK < 23 since the platform
* class will not be available.
*
* @return platform class object
* @see ModemInfoCompat#toModemInfoCompat(ModemInfo)
*/
@RequiresApi(23)
@NonNull
public ModemInfo toModemInfo() {
return mWrapped;
}
/**
* [Docs should match platform docs.]
*
* Compatibility behavior:
* <ul>
* <li>API level 23 and above, this method matches platform behavior.
* <li>API level 18 through 22, this method ...
* <li>API level 17 and earlier, this method always returns false.
* </ul>
*
* @return [match platform docs], or platform-specific fallback behavior
*/
public boolean isLteSupported() {
if (SDK_INT >= 23) {
return Api23Impl.isLteSupported(mWrapped);
} else if (SDK_INT >= 18) {
// Smart fallback behavior based on earlier APIs.
...
}
// Default behavior.
return false;
}
// All references to class members -- including the constructor -- must be
// made on an inner class to avoid soft-verification errors that slow class
// loading and prevent optimization.
@RequiresApi(23)
private static class Api23Impl {
@NonNull
static ModemInfo create() {
return new ModemInfo();
}
static boolean isLteSupported(PlatformClass obj) {
return obj.isLteSupported();
}
}
}
```
Note that libraries written in Java should express conversion to and from the
platform class differently than Kotlin classes. For Java classes, conversion
from the platform class to the wrapper should be expressed as a `static` method,
while conversion from the wrapper to the platform class should be a method on
the wrapper object:
```java
@NonNull
public static ModemInfoCompat toModemInfoCompat(@NonNull ModemInfo info);
@NonNull
public ModemInfo toModemInfo();
```
In cases where the primary library is written in Java and has an accompanying
`-ktx` Kotlin extensions library, the following conversion should be provided as
an extension function:
```kotlin
fun ModemInfo.toModemInfoCompat() : ModemInfoCompat
```
Whereas in cases where the primary library is written in Kotlin, the conversion
should be provided as an extension factory:
```kotlin
class ModemInfoCompat {
fun toModemInfo() : ModemInfo
companion object {
@JvmStatic
@JvmName("toModemInfoCompat")
fun ModemInfo.toModemInfoCompat() : ModemInfoCompat
}
}
```
#### API guidelines {#wrapper-api-guidelines}
##### Naming {#wrapper-naming}
* Class name **must** be `<PlatformClass>Compat`
* Package name **must** be `androidx.core.<platform.package>`
* Superclass **must not** be `<PlatformClass>`
##### Construction {#wrapper-construction}
* Class *may* have public constructor(s) to provide parity with public
`PlatformClass` constructors
* Constructor used to wrap `PlatformClass` **must not** be public
* Class **must** implement a static `PlatformClassCompat
toPlatformClassCompat(PlatformClass)` method to wrap `PlatformClass` on
supported SDK levels
* If class does not exist at module's `minSdkVersion`, method must be
annotated with `@RequiresApi(<sdk>)` for SDK version where class was
introduced
#### Implementation {#wrapper-implementation}
* Class **must** implement a `PlatformClass toPlatformClass()` method to
unwrap `PlatformClass` on supported SDK levels
* If class does not exist at module's `minSdkVersion`, method must be
annotated with `@RequiresApi(<sdk>)` for SDK version where class was
introduced
* Implementation *may* delegate to `PlatformClass` methods when available (see
below note for caveats)
* To avoid runtime class verification issues, all operations that interact
with the internal structure of `PlatformClass` must be implemented in inner
classes targeted to the SDK level at which the operation was added.
* See the [sample](#wrapper-sample) for an example of interacting with a
method that was added in SDK level 23.
### Standalone (ex. [ArraySet](https://developer.android.com/reference/android/support/v4/util/ArraySet.html), [Fragment](https://developer.android.com/reference/android/support/v4/app/Fragment.html)) {#standalone}
When to use?
* Platform class may exist at module's `minSdkVersion`
* Does not need to integrate with platform APIs
* Does not need to coexist with platform class, ex. no potential `import`
collision due to both compatibility and platform classes being referenced
within the same source file
Implementation requirements
* Class name **must** be `<PlatformClass>`
* Package name **must** be `androidx.<platform.package>`
* Superclass **must not** be `<PlatformClass>`
* Class **must not** expose `PlatformClass` in public API
* In exceptional cases, a *released* standalone class may add conversion
between itself and the equivalent platform class; however, *new* classes
that support conversion should follow the [Wrapper](#wrapper)
guidelines. In these cases, use a `toPlatform<PlatformClass>` and
`static toCompat<PlatformClass>` method naming convention.
* Implementation *may* delegate to `PlatformClass` methods when available
### Standalone JAR library (no Android dependencies) {#standalone-jar-library-no-android-dependencies}
When to use
* General purpose library with minimal interaction with Android types
* or when abstraction around types can be used (e.g. Room's SQLite
wrapper)
* Lib used in parts of app with minimal Android dependencies
* ex. Repository, ViewModel
* When Android dependency can sit on top of common library
* Clear separation between android dependent and independent parts of your
library
* Clear that future integration with android dependencies can be layered
separately
**Examples:**
The **Paging Library** pages data from DataSources (such as DB content from Room
or network content from Retrofit) into PagedLists, so they can be presented in a
RecyclerView. Since the included Adapter receives a PagedList, and there are no
other Android dependencies, Paging is split into two parts - a no-android
library (paging-common) with the majority of the paging code, and an android
library (paging-runtime) with just the code to present a PagedList in a
RecyclerView Adapter. This way, tests of Repositories and their components can
be tested in host-side tests.
**Room** loads SQLite data on Android, but provides an abstraction for those
that want to use a different SQL implementation on device. This abstraction, and
the fact that Room generates code dynamically, means that Room interfaces can be
used in host-side tests (though actual DB code should be tested on device, since
DB impls may be significantly different on host).
## Implementing compatibility {#compat}
### Referencing new APIs {#compat-newapi}
Generally, methods on extension library classes should be available to all
devices above the library's `minSdkVersion`.
#### Checking device SDK version {#compat-sdk}
The most common way of delegating to platform or backport implementations is to
compare the device's `Build.VERSION.SDK_INT` field to a known-good SDK version;
for example, the SDK in which a method first appeared or in which a critical bug
was first fixed.
Non-reflective calls to new APIs gated on `SDK_INT` **must** be made from
version-specific static inner classes to avoid verification errors that
negatively affect run-time performance. For more information, see Chromium's
guide to
[Class Verification Failures](https://chromium.googlesource.com/chromium/src/+/HEAD/build/android/docs/class_verification_failures.md).
Methods in implementation-specific classes **must** be paired with the
`@DoNotInline` annotation to prevent them from being inlined.
```java {.good}
public static void saveAttributeDataForStyleable(@NonNull View view, ...) {
if (Build.VERSION.SDK_INT >= 29) {
Api29Impl.saveAttributeDataForStyleable(view, ...);
}
}
@RequiresApi(29)
private static class Api29Impl {
@DoNotInline
static void saveAttributeDataForStyleable(@NonNull View view, ...) {
view.saveAttributeDataForStyleable(...);
}
}
```
Alternatively, in Kotlin sources:
```kotlin {.good}
@RequiresApi(29)
private object Api29Impl {
@JvmStatic
@DoNotInline
fun saveAttributeDataForStyleable(view: View, ...) { ... }
}
```
When developing against pre-release SDKs where the `SDK_INT` has not been
finalized, SDK checks **must** use `BuildCompat.isAtLeastX()` methods.
```java {.good}
@NonNull
public static List<Window> getAllWindows() {
if (BuildCompat.isAtLeastR()) {
return ApiRImpl.getAllWindows();
}
return Collections.emptyList();
}
```
#### Device-specific issues {#compat-oem}
Library code may work around device- or manufacturer-specific issues -- issues
not present in AOSP builds of Android -- *only* if a corresponding CTS test
and/or CDD policy is added to the next revision of the Android platform. Doing
so ensures that such issues can be detected and fixed by OEMs.
#### Handling `minSdkVersion` disparity {#compat-minsdk}
Methods that only need to be accessible on newer devices, including
`to<PlatformClass>()` methods, may be annotated with `@RequiresApi(<sdk>)` to
indicate they will fail to link on older SDKs. This annotation is enforced at
build time by Lint.
#### Handling `targetSdkVersion` behavior changes {#compat-targetsdk}
To preserve application functionality, device behavior at a given API level may
change based on an application's `targetSdkVersion`. For example, if an app with
`targetSdkVersion` set to API level 22 runs on a device with API level 29, all
required permissions will be granted at installation time and the run-time
permissions framework will emulate earlier device behavior.
Libraries do not have control over the app's `targetSdkVersion` and -- in rare
cases -- may need to handle variations in platform behavior. Refer to the
following pages for version-specific behavior changes:
* API level 29:
[Android Q behavior changes: apps targeting Q](https://developer.android.com/preview/behavior-changes-q)
* API level 28:
[Behavior changes: apps targeting API level 28+](https://developer.android.com/about/versions/pie/android-9.0-changes-28)
* API level 26:
[Changes for apps targeting Android 8.0](https://developer.android.com/about/versions/oreo/android-8.0-changes#o-apps)
* API level 24:
[Changes for apps targeting Android 7.0](https://developer.android.com/about/versions/nougat/android-7.0-changes#n-apps)
* API level 21:
[Android 5.0 Behavior Changes](https://developer.android.com/about/versions/android-5.0-changes)
* API level 19:
[Android 4.4 APIs](https://developer.android.com/about/versions/android-4.4)
#### Working around Lint issues {#compat-lint}
In rare cases, Lint may fail to interpret API usages and yield a `NewApi` error
and require the use of `@TargetApi` or `@SuppressLint('NewApi')` annotations.
Both of these annotations are strongly discouraged and may only be used
temporarily. They **must never** be used in a stable release. Any usage of these
annotation **must** be associated with an active bug, and the usage must be
removed when the bug is resolved.
### Delegating to API-specific implementations {#delegating-to-api-specific-implementations}
#### SDK-dependent reflection
Starting in API level 28, the platform restricts which
[non-SDK interfaces](https://developer.android.com/distribute/best-practices/develop/restrictions-non-sdk-interfaces)
can be accessed via reflection by apps and libraries. As a general rule, you
will **not** be able to use reflection to access hidden APIs on devices with
`SDK_INT` greater than `Build.VERSION_CODES.P` (28).
On earlier devices, reflection on hidden platform APIs is allowed **only** when
an alternative public platform API exists in a later revision of the Android
SDK. For example, the following implementation is allowed:
```java
public AccessibilityDelegate getAccessibilityDelegate(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// Retrieve the delegate using a public API.
return v.getAccessibilityDelegate();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// Retrieve the delegate by reflecting on a private field. If the
// field does not exist or cannot be accessed, this will no-op.
if (sAccessibilityDelegateField == null) {
try {
sAccessibilityDelegateField = View.class
.getDeclaredField("mAccessibilityDelegate");
sAccessibilityDelegateField.setAccessible(true);
} catch (Throwable t) {
sAccessibilityDelegateCheckFailed = true;
return null;
}
}
try {
Object o = sAccessibilityDelegateField.get(v);
if (o instanceof View.AccessibilityDelegate) {
return (View.AccessibilityDelegate) o;
}
return null;
} catch (Throwable t) {
sAccessibilityDelegateCheckFailed = true;
return null;
}
} else {
// There is no way to retrieve the delegate, even via reflection.
return null;
}
```
Calls to public APIs added in pre-release revisions *must* be gated using
`BuildCompat`:
```java
if (BuildCompat.isAtLeastQ()) {
// call new API added in Q
} else if (Build.SDK_INT.VERSION >= Build.VERSION_CODES.SOME_RELEASE) {
// make a best-effort using APIs that we expect to be available
} else {
// no-op or best-effort given no information
}
```
### Inter-process communication {#inter-process-communication}
Protocols and data structures used for IPC must support interoperability between
different versions of libraries and should be treated similarly to public API.
#### Data structures
**Do not** use Parcelable for any class that may be used for IPC or otherwise
exposed as public API. The data format used by Parcelable does not provide any
compatibility guarantees and will result in crashes if fields are added or
removed between library versions.
**Do not** design your own serialization mechanism or wire format for disk
storage or inter-process communication. Preserving and verifying compatibility
is difficult and error-prone.
Developers **should** use protocol buffers for most cases. See
[Protobuf](#dependencies-protobuf) for more information on using protocol
buffers in your library.
Developers **may** use `Bundle` in simple cases that require sending `Binder`s
or `FileDescriptor`s across IPC. Note that `Bundle` has several caveats:
- Accessing *any* entry in a `Bundle` will result in the platform attempting
to load *every* entry. If a single entry cannot be loaded -- for example if
a developer added a custom `Parcelable` that doesn't exist in the receiver's
classpath -- an exception will be thrown when accessing *any* entry. Library
code that accesses `Bundle`s received from outside the process **must** do
so defensively. Library code that sends `Bundle`s outside the process
*should* discourage clients from passing custom `Parcelable`s.
- `Bundle` provides no versioning and Jetpack provides no affordances for
tracking the keys or value types associated with a `Bundle`. Library owners
are responsible for providing their own system for guaranteeing wire format
compatibility between versions.
#### Communication protocols
Any communication prototcol, handshake, etc. must maintain compatibility
consistent with SemVer guidelines. Consider how your protocol will handle
addition and removal of operations or constants, compatibility-breaking changes,
and other modifications without crashing either the host or client process.
## Deprecation and removal
While SemVer's binary compatibility guarantees restrict the types of changes
that may be made within a library revision and make it difficult to remove an
API, there are many other ways to influence how developers interact with your
library.
### Deprecation (`@deprecated`)
Deprecation lets a developer know that they should stop using an API or class.
All deprecations must be marked with a `@Deprecated` Java annotation as well as
a `@deprecated <migration-docs>` docs annotation explaining how the developer
should migrate away from the API.
Deprecation is an non-breaking API change that must occur in a **major** or
**minor** release.
APIs that are added during a pre-release cycle and marked as `@Deprecated`
within the same cycle, e.g. added in `alpha01` and deprecated in `alpha06`,
[must be removed](versioning.md#beta-checklist) before moving to `beta01`.
### Soft removal (@removed)
Soft removal preserves binary compatibility while preventing source code from
compiling against an API. It is a *source-breaking change* and not recommended.
Soft removals **must** do the following:
* Mark the API as deprecated for at least one stable release prior to removal.
* Mark the API with a `@RestrictTo(LIBRARY)` Java annotation as well as a
`@removed <reason>` docs annotation explaining why the API was removed.
* Maintain binary compatibility, as the API may still be called by existing
dependent libraries.
* Maintain behavioral compatibility and existing tests.
This is a disruptive change and should be avoided when possible.
Soft removal is a source-breaking API change that must occur in a **major** or
**minor** release.
### Hard removal
Hard removal entails removing the entire implementation of an API that was
exposed in a public release. Prior to removal, an API must be marked as
`@deprecated` for a full **minor** version (`alpha`->`beta`->`rc`->stable),
prior to being hard removed.
This is a disruptive change and should be avoided when possible.
Hard removal is a binary-breaking API change that must occur in a **major**
release.
### For entire artifacts
We do not typically deprecate or remove entire artifacts; however, it may be
useful in cases where we want to halt development and focus elsewhere or
strongly discourage developers from using a library.
Halting development, either because of staffing or prioritization issues, leaves
the door open for future bug fixes or continued development. This quite simply
means we stop releasing updates but retain the source in our tree.
Deprecating an artifact provides developers with a migration path and strongly
encourages them -- through Lint warnings -- to migrate elsewhere. This is
accomplished by adding a `@Deprecated` and `@deprecated` (with migration
comment) annotation pair to *every* class and interface in the artifact.
Entire packages (including Kotlin) can be deprecated by using a
`package-info.java` file and applying the `@Deprecated` annotation there.
The fully-deprecated artifact will be released as a deprecation release -- it
will ship normally with accompanying release notes indicating the reason for
deprecation and migration strategy, and it will be the last version of the
artifact that ships. It will ship as a new minor stable release. For example, if
`1.0.0` was the last stable release, then the deprecation release will be
`1.1.0`. This is so Android Studio users will get a suggestion to update to a
new stable version, which will contain the `@deprecated` annotations.
After an artifact has been released as fully-deprecated, it can be removed from
the source tree.
## Resources {#resources}
Generally, follow the official Android guidelines for
[app resources](https://developer.android.com/guide/topics/resources/providing-resources).
Special guidelines for library resources are noted below.
### Defining new resources
Libraries may define new value and attribute resources using the standard
application directory structure used by Android Gradle Plugin:
```
src/main/res/
values/
attrs.xml Theme attributes and styleables
dimens.xml Dimensional values
public.xml Public resource definitions
...
```
However, some libraries may still be using non-standard, legacy directory
structures such as `res-public` for their public resource declarations or a
top-level `res` directory and accompanying custom source set in `build.gradle`.
These libraries will eventually be migrated to follow standard guidelines.
#### Naming conventions
Libraries follow the Android platform's resource naming conventions, which use
`camelCase` for attributes and `underline_delimited` for values. For example,
`R.attr.fontProviderPackage` and `R.dimen.material_blue_grey_900`.
#### Attribute formats
At build time, attribute definitions are pooled globally across all libraries
used in an application, which means attribute `format`s *must* be identical for
a given `name` to avoid a conflict.
Within Jetpack, new attribute names *must* be globally unique. Libraries *may*
reference existing public attributes from their dependencies. See below for more
information on public attributes.
When adding a new attribute, the format should be defined *once* in an `<attr
/>` element in the definitions block at the top of `src/main/res/attrs.xml`.
Subsequent references in `<declare-styleable>` elements *must* not include a
`format`:
`src/main/res/attrs.xml`
```xml
<resources>
<attr name="fontProviderPackage" format="string" />
<declare-styleable name="FontFamily">
<attr name="fontProviderPackage" />
</declare-styleable>
</resources>
```
### Public resources
Library resources are private by default, which means developers are discouraged
from referencing any defined attributes or values from XML or code; however,
library resources may be declared public to make them available to developers.
Public library resources are considered API surface and are thus subject to the
same API consistency and documentation requirements as Java APIs.
Libraries will typically only expose theme attributes, ex. `<attr />` elements,
as public API so that developers can set and retrieve the values stored in
styles and themes. Exposing values -- such as `<dimen />` and `<string />` -- or
images -- such as drawable XML and PNGs -- locks the current state of those
elements as public API that cannot be changed without a major version bump. That
means changing a publicly-visible icon would be considered a breaking change.
#### Documentation
All public resource definitions should be documented, including top-level
definitions and re-uses inside `<styleable>` elements:
`src/main/res/attrs.xml`
```xml
<resources>
<!-- String specifying the application package for a Font Provider. -->
<attr name="fontProviderPackage" format="string" />
<!-- Attributes that are read when parsing a <fontfamily> tag. -->
<declare-styleable name="FontFamily">
<!-- The package for the Font Provider to be used for the request. This is
used to verify the identity of the provider. -->
<attr name="fontProviderPackage" />
</declare-styleable>
</resources>
```
`src/main/res/colors.xml`
```xml
<resources>
<!-- Color for Material Blue-Grey 900. -->
<color name="material_blue_grey_900">#ff263238</color>
</resources>
```
#### Public declaration
Resources are declared public by providing a separate `<public />` element with
a matching type:
`src/main/res/public.xml`
```xml
<resources>
<public name="fontProviderPackage" type="attr" />
<public name="material_blue_grey_900" type="color" />
</resources>
```
#### More information
See also the official Android Gradle Plugin documentation for
[Private Resources](https://developer.android.com/studio/projects/android-library#PrivateResources).
### Manifest entries (`AndroidManifest.xml`) {#resources-manifest}
#### Metadata tags (`<meta-data>`) {#resources-manifest-metadata}
Developers **must not** add `<application>`-level `<meta-data>` tags to library
manifests or advise developers to add such tags to their application manifests.
Doing so may *inadvertently cause denial-of-service attacks against other apps*.
Assume a library adds a single item of meta-data at the application level. When
an app uses the library, that meta-data will be merged into the resulting app's
application entry via manifest merger.
If another app attempts to obtain a list of all activities associated with the
primary app, that list will contain multiple copies of the `ApplicationInfo`,
each of which in turn contains a copy of the library's meta-data. As a result,
one `<metadata>` tag may become hundreds of KB on the binder call to obtain the
list -- resulting in apps hitting transaction too large exceptions and crashing.
```xml {.bad}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="androidx.librarypackage">
<application>
<meta-data
android:name="keyName"
android:value="@string/value" />
</application>
</manifest>
```
Instead, developers may consider adding `<metadata>` nested inside of
placeholder `<service>` tags.
```xml {.good}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="androidx.librarypackage">
<application>
<service
android:name="androidx.librarypackage.MetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="androidx.librarypackage.MetadataHolderService.KEY_NAME"
android:resource="@string/value" />
</service>
</application>
```
```java {.good}
package androidx.libraryname.featurename;
/**
* A placeholder service to avoid adding application-level metadata. The service
* is only used to expose metadata defined in the library's manifest. It is
* never invoked.
*/
public final class MetadataHolderService {
private MetadataHolderService() {}
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException();
}
}
```
## Dependencies {#dependencies}
Artifacts may depend on other artifacts within AndroidX as well as sanctioned
third-party libraries.
### Versioned artifacts {#dependencies-versioned}
One of the most difficult aspects of independently-versioned releases is
maintaining compatibility with public artifacts. In a mono repo such as Google's
repository or Android Git at master revision, it's easy for an artifact to
accidentally gain a dependency on a feature that may not be released on the same
schedule.
- Project `project(":core:core")` uses the tip-of-tree sources for the
`androidx.core:core` library and requires that they be loaded in the
workspace.
- Playground `projectOrArtifact(":core:core")` is used for
[Playground](playground.md) projects and will use tip-of-tree sources, if
present in the workspace, or `SNAPSHOT` prebuilt artifacts from
[androidx.dev](http://androidx.dev) otherwise.
- Explicit `"androidx.core:core:1.4.0"` uses the prebuilt AAR and requires
that it be checked in to the `prebuilts/androidx/internal` local Maven
repository.
Libraries should prefer explicit dependencies with the lowest possible versions
that include the APIs or behaviors required by the library, using project or
Playground specs only in cases where tip-of-tree APIs or behaviors are required.
#### Pre-release dependencies {#dependencies-pre-release}
Pre-release suffixes **must** propagate up the dependency tree. For example, if
your artifact has API-type dependencies on pre-release artifacts, ex.
`1.1.0-alpha01`, then your artifact must also carry the `alpha` suffix. If you
only have implementation-type dependencies, your artifact may carry either the
`alpha` or `beta` suffix.
Note: This does not apply to test dependencies: suffixes of test dependencies do
*not* carry over to your artifact.
#### Pinned versions {#dependencies-prebuilt}
To avoid issues with dependency versioning, consider pinning your artifact's
dependencies to the oldest version (available via local `maven_repo` or Google
Maven) that satisfies the artifact's API requirements. This will ensure that the
artifact's release schedule is not accidentally tied to that of another artifact
and will allow developers to use older libraries if desired.
```
dependencies {
api("androidx.collection:collection:1.0.0")
...
}
```
Artifacts should be built and tested against both pinned and tip-of-tree
versions of their dependencies to ensure behavioral compatibility.
#### Tip-of-tree versions {#dependencies-project}
Below is an example of a non-pinned dependency. It ties the artifact's release
schedule to that of the dependency artifact, because the dependency will need to
be released at the same time.
```
dependencies {
api(project(":collection"))
...
}
```
### Non-public APIs {#dependencies-non-public-apis}
Artifacts may depend on non-public (e.g. `@hide`) APIs exposed within their own
artifact or another artifact in the same `groupId`; however, cross-artifact
usages are subject to binary compatibility guarantees and
`@RestrictTo(Scope.LIBRARY_GROUP)` APIs must be tracked like public APIs.
```
Dependency versioning policies are enforced at build time in the createArchive task. This task will ensure that pre-release version suffixes are propagated appropriately.
Cross-artifact API usage policies are enforced by the checkApi and checkApiRelease tasks (see Life of a release).
```
### Third-party libraries {#dependencies-3p}
Artifacts may depend on libraries developed outside of AndroidX; however, they
must conform to the following guidelines:
* Prebuilt **must** be checked into Android Git with both Maven and Make
artifacts
* `prebuilts/maven_repo` is recommended if this dependency is only
intended for use with AndroidX artifacts, otherwise please use
`external`
* Prebuilt directory **must** contains an `OWNERS` file identifying one or
more individual owners (e.g. NOT a group alias)
* Library **must** be approved by legal
Please see Jetpack's [open-source policy page](open_source.md) for more details
on using third-party libraries.
### Types of dependencies {#dependencies-types}
AndroidX allows dependencies to be specified as `api` or `implementation` with a
"pinned" Maven spec (ex. `androidx.core:core:1.0.0`) or a "tip-of-tree" project
spec (ex. `project(":core:core")`).
Projects used in Playground, the experimental GitHub workflow, should use a
"recent" project or artifact spec (ex. `projectOrArtifact(":core:core")`) which
will default to tip-of-tree when used outside of the Playground workflow or a
pinned `SNAPSHOT` artifact otherwise.
Regardless of which dependency spec is used, all projects are built against
tip-of-tree dependencies in CI to prevent regressions and enforce Jetpack's
compatible-at-head policy.
#### `api` versus `implementation` {#dependencies-api-vs-impl}
`api`-type dependencies will appear in clients' auto-complete as though they had
added the dependency directly to their project, and Studio will run any lint
checks bundled with `api`-type dependencies.
Dependencies whose APIs are exposed in a library's API surface **must** be
included as `api`-type. For example, if your library's API surface includes
`AccessibilityNodeInfoCompat` then you will use an `api`-type dependency on the
`androidx.core:core` library.
NOTE Libraries that provide client-facing lint checks, including
`annotation-experimental`, **must** be included as `api`-type to ensure that
lint checks are run in the clients' dependent projects.
`implementation`-type dependencies will be included in the classpath, but will
not be made available at design time (ex. in auto-complete) unless the client
explicitly adds them.
### System health {#dependencies-health}
Generally, Jetpack libraries should avoid dependencies that negatively impact
developers without providing substantial benefit. Libraries should consider the
system health implications of their dependencies, including:
- Large dependencies where only a small portion is needed (e.g. APK bloat)
- Dependencies that slow down build times through annotation processing or
compiler overhead
#### Kotlin {#dependencies-kotlin}
Kotlin is *strongly recommended* for new libraries; however, it's important to
consider its size impact on clients. Currently, the Kotlin stdlib adds a minimum
of 40kB post-optimization. It may not make sense to use Kotlin for a library
that targets Java-only clients or space-constrained (ex. Android Go) clients.
Existing Java-based libraries are *strongly discouraged* from using Kotlin,
primarily because our documentation system does not currently provide a
Java-facing version of Kotlin API reference docs. Java-based libraries *may*
migrate to Kotlin, but they must consider the docs usability and size impacts on
existing Java-only and space-constrained clients.
#### Kotlin coroutines {#dependencies-coroutines}
Kotlin's coroutine library adds around 100kB post-shrinking. New libraries that
are written in Kotlin should prefer coroutines over `ListenableFuture`, but
existing libraries must consider the size impact on their clients. See
[Asynchronous work with return values](#async-return) for more details on using
Kotlin coroutines in Jetpack libraries.
#### Guava {#dependencies-guava}
The full Guava library is very large and *must not* be used. Libraries that
would like to depend on Guava's `ListenableFuture` may instead depend on the
standalone `com.google.guava:listenablefuture` artifact. See
[Asynchronous work with return values](#async-return) for more details on using
`ListenableFuture` in Jetpack libraries.
#### Java 8 {#dependencies-java8}
Libraries that take a dependency on a library targeting Java 8 must *also*
target Java 8, which will incur a ~5% build performance (as of 8/2019) hit for
clients. New libraries targeting Java 8 may use Java 8 dependencies.
The default language level for `androidx` libraries is Java 8, and we encourage
libraries to stay on Java 8. However, if you have a business need to target Java
7, you can specify Java 7 in your `build.gradle` as follows:
```groovy
android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
}
```
#### Protobuf {#dependencies-protobuf}
[Protocol buffers](https://developers.google.com/protocol-buffers) provide a
language- and platform-neutral mechanism for serializing structured data. The
implementation enables developers to maintain protocol compatibility across
library versions, meaning that two clients can communicate regardless of the
library versions included in their APKs.
The Protobuf library itself, however, does not guarantee ABI compatibility
across minor versions and a specific version **must** be bundled with a library
to avoid conflict with other dependencies used by the developer.
Additionally, the Java API surface generated by the Protobuf compiler is not
guaranteed to be stable and **must not** be exposed to developers. Library
owners should wrap the generated API surface with well-documented public APIs
that follow an appropriate language-specific paradigm for constructing data
classes, e.g. the Java `Builder` pattern.
### Open-source compatibility {#dependencies-aosp}
[Jetpack Principles](principles.md) require that libraries consider the
open-source compatibility implications of their dependencies, including:
- Closed-source or proprietary libraries or services that may not be available
on AOSP devices
- Dependencies that may prevent developers from effectively isolating their
tests from third-party libraries or services
Primary artifacts, e.g. `workmanager`, **must not** depend on closed-source
components including libraries and hard-coded references to packages,
permissions, or IPC mechanisms that may only be fulfulled by closed-source
components.
Optional artifacts, e.g. `workmanager-gcm`, *may* depend on closed-source
components or configure a primary artifact to be backed by a closed-source
component via service discovery or initialization.
Some examples of safely depending on closed-source components include:
- WorkManager's GCM Network Manager integration, which uses
[manifest metadata](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:work/workmanager-gcm/src/main/AndroidManifest.xml)
for service discovery and provides an optional artifact exposing the
service.
- Ads Identifier's Play Services integration, which provides a default backend
and uses
[`Intent` handling](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:ads/ads-identifier-provider/src/main/java/androidx/ads/identifier/provider/AdvertisingIdProviderManager.java;l=108)
as a service discovery mechanism for Play Services.
- Downloadable Fonts integration with Play Services, which plugs in via a
[`ContentProvider`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:core/core/src/androidTest/java/androidx/core/provider/MockFontProvider.java)
as a service discovery mechanism with developer-specified
[signature verification](https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts#adding-certificates)
for additional security.
Note that in all cases, the developer is not *required* to use GCM or Play
Services and may instead use another compatible service implementing the same
publicly-defined protocols.
## More API guidelines {#more-api-guidelines}
### 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.
### Experimental `@RequiresOptIn` APIs {#experimental-api}
Jetpack libraries may choose to annotate API surfaces as unstable using either
Kotlin's
[`@RequiresOptIn` annotation](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-requires-opt-in/)
for APIs written in Kotlin or Jetpack's
[`@RequiresOptIn` annotation](https://developer.android.com/reference/kotlin/androidx/annotation/RequiresOptIn)
for APIs written in Java.
In both cases, API surfaces marked as experimental 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`.
#### 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 since 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 associated annotations.
When making any change to the experimental API surface, you *must* run
`./gradlew updateApi` prior to uploading your change.
### Restricted 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 (Kotlin not yet supported by Checkstyle).
The effects of hiding an API are as follows:
* The API will not appear in documentation
* Android Studio will warn the developer not to use the API
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>
</tr>
<tr>
<td><code>LIBRARY</code></td>
<td><code>androidx.concurrent:concurrent</code></td>
<td>No compatibility gurantees (same as private)</td>
</tr>
<tr>
<td><code>LIBRARY_GROUP</code></td>
<td><code>androidx.concurrent:*</code></td>
<td>Semantic versioning (including deprecation)</td>
</tr>
<tr>
<td><code>LIBRARY_GROUP_PREFIX</code></td>
<td><code>androidx.*:*</code></td>
<td>Semantic versioning (including deprecation)</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)
### 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}
Traditionally, asynchronous work on Android that results in an output value
would use a callback; however, better alternatives exist for libraries.
Kotlin libraries should prefer
[coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html) and
`suspend` functions, but please refer to the guidance on
[allowable dependencies](#dependencies-coroutines) before adding a new
dependency on coroutines.
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 {#kotlin}
#### Nullability from Java (new 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;
}
```
#### Nullability from Java (existing APIs)
Adding `@Nullable` or `@NonNull` annotations to existing APIs to document their
existing nullability is OK. 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 breaking change.
#### Extending APIs that expose types without nullability 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 libraries that only work in Kotlin (think
Compose).
## Testing Guidelines
### [Do not Mock, AndroidX](do_not_mock.md)
## Android Lint Guidelines
### Suppression vs Baselines
Lint sometimes flags false positives, even though it is safe to ignore these
errors (for example WeakerAccess warnings when you are avoiding synthetic
access). There may also be lint failures when your library is in the middle of a
beta / rc / stable release, and cannot make the breaking changes needed to fix
the root cause. There are two ways of ignoring lint errors:
1. Suppression - using `@SuppressLint` (for Java) or `@Suppress` annotations to
ignore the warning per call site, per method, or per file. *Note
`@SuppressLint` - Requires Android dependency*.
2. Baselines - allowlisting errors in a lint-baseline.xml file at the root of
the project directory.
Where possible, you should use a **suppression annotation at the call site**.
This helps ensure that you are only suppressing the *exact* failure, and this
also keeps the failure visible so it can be fixed later on. Only use a baseline
if you are in a Java library without Android dependencies, or when enabling a
new lint check, and it is prohibitively expensive / not possible to fix the
errors generated by enabling this lint check.
To update a lint baseline (`lint-baseline.xml`) after you have fixed issues,
first **manually delete the `lint-baseline.xml` file** for your project and then
run the `lintDebug` task for your project with the argument
`-PupdateLintBaseline`.
```shell
rm core/core/lint-baseline.xml
./gradlew :core:core:lintDebug -PupdateLintBaseline
```
## Metalava API Lint
As well as Android Lint, which runs on all source code, Metalava will also run
checks on the public API surface of each library. Similar to with Android Lint,
there can sometimes be false positives / intended deviations from the API
guidelines that Metalava will lint your API surface against. When this happens,
you can suppress Metalava API lint issues using `@SuppressLint` (for Java) or
`@Suppress` annotations. In cases where it is not possible, update Metalava's
baseline with the `updateApiLintBaseline` task.
```shell
./gradlew core:updateApiLintBaseline
```
This will create/amend the `api_lint.ignore` file that lives in a library's
`api` directory.
## Build Output Guidelines
In order to more easily identify the root cause of build failures, we want to
keep the amount of output generated by a successful build to a minimum.
Consequently, we track build output similarly to the way in which we track Lint
warnings.
### Invoking build output validation
You can add `-Pandroidx.validateNoUnrecognizedMessages` to any other AndroidX
gradlew command to enable validation of build output. For example:
```shell
/gradlew -Pandroidx.validateNoUnrecognizedMessages :help
```
### Exempting new build output messages
Please avoid exempting new build output and instead fix or suppress the warnings
themselves, because that will take effect not only on the build server but also
in Android Studio, and will also run more quickly.
If you cannot prevent the message from being generating and must exempt the
message anyway, follow the instructions in the error:
```shell
$ ./gradlew -Pandroidx.validateNoUnrecognizedMessages :help
Error: build_log_simplifier.py found 15 new messages found in /usr/local/google/workspace/aosp-androidx-git/out/dist/gradle.log.
Please fix or suppress these new messages in the tool that generates them.
If you cannot, then you can exempt them by doing:
1. cp /usr/local/google/workspace/aosp-androidx-git/out/dist/gradle.log.ignore /usr/local/google/workspace/aosp-androidx-git/frameworks/support/development/build_log_simplifier/messages.ignore
2. modify the new lines to be appropriately generalized
```
Each line in this exemptions file is a regular expressing matching one or more
lines of output to be exempted. You may want to make these expressions as
specific as possible to ensure that the addition of new, similar messages will
also be detected (for example, discovering an existing warning in a new source
file).
## Behavior changes
### Changes that affect API documentation
Do not make behavior changes that require altering API documentation in a way
that would break existing clients, even if such changes are technically binary
compatible. For example, changing the meaning of a method's return value to
return true rather than false in a given state would be considered a breaking
change. Because this change is binary-compatible, it will not be caught by
tooling and is effectively invisible to clients.
Instead, add new methods and deprecate the existing ones if necessary, noting
behavior changes in the deprecation message.
### High-risk behavior changes
Behavior changes that conform to documented API contracts but are highly complex
and difficult to comprehensively test are considered high-risk and should be
implemented using behavior flags. These changes may be flagged on initially, but
the original behaviors must be preserved until the library enters release
candidate stage and the behavior changes have been appropriately verified by
integration testing against public pre-release
revisions.
It may be necessary to soft-revert a high-risk behavior change with only 24-hour
notice, which should be achievable by flipping the behavior flag to off.
```java
// Flag for whether to throw exceptions when the state is known to be bad. This
// is expected to be a high-risk change since apps may be working fine even with
// a bad state, so we may need to disable this as a hotfix.
private static final boolean FLAG_EXCEPTION_ON_BAD_STATE = false;
```
```java
/**
* Allows a developer to toggle throwing exceptions when the state is known to
* be bad. This method is intended to give developers time to update their code.
* It is temporary and will be removed in a future release.
*/
@TemporaryFeatureFlag
public void setExceptionOnBadStateEnabled(boolean enabled);
```
Avoid adding multiple high-risk changes during a feature cycle, as verifying the
interaction of multiple feature flags leads to unnecessary complexity and
exposes clients to high risk even when a single change is flagged off. Instead,
wait until one high-risk change has landed in RC before moving on to the next.
#### Testing
Relevant tests should be run for the behavior change in both the on and off
flagged states to prevent regressions.
## Sample code in Kotlin modules
### Background
Public API can (and should!) have small corresponding code snippets that
demonstrate functionality and usage of a particular API. These are often exposed
inline in the documentation for the function / class - this causes consistency
and correctness issues as this code is not compiled against, and the underlying
implementation can easily change.
KDoc (JavaDoc for Kotlin) supports a `@sample` tag, which allows referencing the
body of a function from documentation. This means that code samples can be just
written as a normal function, compiled and linted against, and reused from other
modules such as tests! This allows for some guarantees on the correctness of a
sample, and ensuring that it is always kept up to date.
### Enforcement
There are still some visibility issues here - it can be hard to tell if a
function is a sample, and is used from public documentation - so as a result we
have lint checks to ensure sample correctness.
Primarily, there are three requirements when using sample links:
1. All functions linked to from a `@sample` KDoc tag must be annotated with
`@Sampled`
2. All sample functions annotated with `@Sampled` must be linked to from a
`@sample` KDoc tag
3. All sample functions must live inside a separate `samples` library
submodule - see the section on module configuration below for more
information.
This enforces visibility guarantees, and make it easier to know that a sample is
a sample. This also prevents orphaned samples that aren't used, and remain
unmaintained and outdated.
### Sample usage
The follow demonstrates how to reference sample functions from public API. It is
also recommended to reuse these samples in unit tests / integration tests / test
apps / library demos where possible.
**Public API:**
```
/*
* Fancy prints the given [string]
*
* @sample androidx.printer.samples.fancySample
*/
fun fancyPrint(str: String) ...
```
**Sample function:**
```
package androidx.printer.samples
import androidx.printer.fancyPrint
@Sampled
fun fancySample() {
fancyPrint("Fancy!")
}
```
**Generated documentation visible on d.android.com\***
```
fun fancyPrint(str: String)
Fancy prints the given [string]
<code>
import androidx.printer.fancyPrint
fancyPrint("Fancy!")
<code>
```
\**still some improvements to be made to DAC side, such as syntax highlighting*
### Module configuration
The following module setups should be used for sample functions, and are
enforced by lint:
**Group-level samples**
For library groups with strongly related samples that want to share code.
Gradle project name: `:foo-library:foo-library-samples`
```
foo-library/
foo-module/
bar-module/
samples/
```
**Per-module samples**
For library groups with complex, relatively independent sub-libraries
Gradle project name: `:foo-library:foo-module:foo-module-samples`
```
foo-library/
foo-module/
samples/
```
**Samples module configuration**
Samples modules are published to GMaven so that they are available to Android
Studio, which displays code in @Sample annotations as hover-over pop-ups.
To achieve this, samples modules must declare the same MavenGroup and `publish`
as the library(s) they are samples for.