blob: 378120007e97983df4a6aa9813d349f2f61fe255 [file] [log] [blame] [view]
# Library API guidelines
[TOC]
## Introduction {#introduction}
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 artifacts use the groupId format `androidx.<feature-name>` and artifactId
format `<feature-name>` to match the Java package.
Sub-features that can be separated into their own artifact should use the
following formats:
Java package: `androidx.<feature-name>.<sub-feature>.<layer>`
Maven groupId: `androidx.<feature-name>`
Maven artifactId: `<feature-name>-<sub-feature>`
#### 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
* `-java8` for a Java 8 artifact that exposes idiomatic Java 8 APIs as an
extension to a Java 7 library
* `-<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 classes `androidx.library.A` and `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.0.0`
* `androidx.library.util:1.0.0`
* depends on `androidx.library.util.B`
A developer writes an app that depends directly on `androidx.library.util:1.0.0`
and 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 (atomic) groups
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-master-dev: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'
}
```
There is one exception to this policy. Newly-added libraries within an atomic
group may stay within the `1.0.0-alphaXX` before conforming to the same-version
policy. When the library would like to move to `beta`, it must match the version
used by the atomic group (which must be `beta` at the time).
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
### Choosing a `minSdkVersion` {#module-minsdkversion}
The recommended minimum SDK version for new Jetpack libraries is currently
**17** (Android 4.2, Jelly Bean). 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.
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.
## Platform compatibility API patterns {#platform-compatibility-apis}
### 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
* 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)
object Api25 {
@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.
If you expose a `Bundle` to callers that can cross processes, you should
[prevent apps from adding their own custom parcelables](https://android.googlesource.com/platform/frameworks/base/+/6cddbe14e1ff67dc4691a013fe38a2eb0893fe03)
as top-level entries; if *any* entry in a `Bundle` can't be loaded, even if it's
not actually accessed, the receiving process is likely to crash.
**Do** use protocol buffers or, in some simpler cases, `VersionedParcelable`.
#### 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.
### 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.
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}
Generally, Jetpack libraries should avoid dependencies that negatively impact
developers without providing substantial benefit. This includes large
dependencies where only a small portion is needed, dependencies that slow down
build times through annotation processing or compiler overhead, and generally
any dependency that negatively affects system health.
### Kotlin {#dependencies-kotlin}
Kotlin is _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.
### 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; however,
existing libraries targeting Java 7 should not.
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
}
}
```
## 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 APIs {#experimental-api}
Jetpack libraries may choose to annotate API surfaces as unstable using either
Kotlin's
[`@Experimental` annotation](https://kotlinlang.org/docs/reference/experimental.html)
for APIs written in Kotlin or Jetpack's
[`@Experimental` annotation](https://developer.android.com/reference/kotlin/androidx/annotation/experimental/Experimental)
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, libraries *must never* call experimental APIs exposed by other
libraries and *may not* use the `@UseExperimental` 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 `@UsesExperimental` 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.**
#### How to mark an API surface as experimental
All libraries using `@Experimental` 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
`@UseExperimental` available to Java clients; however, this will also
unnecessarily expose the `@Experimental` annotation.
```java
dependencies {
implementation(project(":annotation:annotation-experimental"))
}
```
See Kotlin's
[experimental marker documentation](https://kotlinlang.org/docs/reference/experimental.html)
for general usage information. If you are writing experimental Java APIs, you
will use the Jetpack
[`@Experimental` annotation](https://developer.android.com/reference/kotlin/androidx/annotation/experimental/Experimental)
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 `@hide` docs
annotation and `@RestrictTo` source annotation. 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}
Generally, avoid using `@hide`. The `@hide` annotation 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` 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` for implementation detail APIs that are used between
libraries and could reasonably be made public.
*Do* use `@hide` 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 (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.
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.
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>
</tr>
<tr>
<td><code>LIBRARY</code></td>
<td><code>androidx.concurrent:concurrent</code></td>
</tr>
<tr>
<td><code>LIBRARY_GROUP</code></td>
<td><code>androidx.concurrent:*</code></td>
</tr>
<tr>
<td><code>LIBRARY_GROUP_PREFIX</code></td>
<td><code>androidx.*:*</code></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}
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.
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.
#### 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}
#### 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.
#### Extension and top-level functions {#kotlin-extension-functions}
If your Kotlin file contains any sybmols 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, add
`-PupdateLintBaseline` to the end of your lint command. This will delete and
then regenerate the baseline file.
```shell
./gradlew 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
[example code pending]
```
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: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:samples`
```
foo-library/
foo-module/
samples/
```