AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1 | # Library API guidelines |
| 2 | |
| 3 | [TOC] |
| 4 | |
| 5 | ## Introduction {#introduction} |
| 6 | |
alanv | 195c35f | 2021-06-10 14:29:40 -0700 | [diff] [blame] | 7 | This guide is an addendum to |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 8 | s.android.com/api-guidelines, |
| 9 | which covers standard and practices for designing platform APIs. |
| 10 | |
| 11 | All platform API design guidelines also apply to Jetpack libraries, with any |
| 12 | additional guidelines or exceptions noted in this document. Jetpack libraries |
| 13 | also follow |
| 14 | [explicit API mode](https://kotlinlang.org/docs/reference/whatsnew14.html#explicit-api-mode-for-library-authors) |
| 15 | for Kotlin libraries. |
| 16 | |
| 17 | ## Modules {#module} |
| 18 | |
| 19 | ### Packaging and naming {#module-naming} |
| 20 | |
| 21 | Java packages within Jetpack follow the format `androidx.<feature-name>`. All |
| 22 | classes within a feature's artifact must reside within this package, and may |
| 23 | further subdivide into `androidx.<feature-name>.<layer>` using standard Android |
| 24 | layers (app, widget, etc.) or layers specific to the feature. |
| 25 | |
alanv | b865912 | 2021-06-14 08:55:32 -0700 | [diff] [blame] | 26 | Maven specifications use the groupId format `androidx.<feature-name>` and |
| 27 | artifactId format `<feature-name>` to match the Java package. For example, |
| 28 | `androidx.core.role` uses the Maven spec `androidx.core:role`. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 29 | |
alanv | b865912 | 2021-06-14 08:55:32 -0700 | [diff] [blame] | 30 | Sub-features that can be separated into their own artifact are recommended to |
| 31 | use the following formats: |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 32 | |
alanv | b865912 | 2021-06-14 08:55:32 -0700 | [diff] [blame] | 33 | - Java package: `androidx.<feature-name>.<sub-feature>.<layer>` |
| 34 | - Maven groupId: `androidx.<feature-name>` |
| 35 | - Maven artifactId: `<feature-name>-<sub-feature>` |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 36 | |
alanv | b865912 | 2021-06-14 08:55:32 -0700 | [diff] [blame] | 37 | Gradle project names and directories follow the Maven spec format, substituting |
| 38 | the project name separator `:` or directory separator `/` for the Maven |
| 39 | separators `.` or `:`. For example, `androidx.core:core-role` would use project |
| 40 | name `:core:core-role` and directory `/core/core-role`. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 41 | |
AndroidX Core Team | 9f9538a | 2021-06-07 12:03:58 -0700 | [diff] [blame] | 42 | New modules in androidx can be created using the |
alanv | 93da5db | 2021-08-11 13:05:51 -0700 | [diff] [blame] | 43 | [project creator script](#module-creator). |
| 44 | |
AndroidX Core Team | f74ae23 | 2022-04-25 11:17:51 -0400 | [diff] [blame] | 45 | NOTE Modules for OEM-implemented shared libraries (also known as extensions or |
| 46 | sidecars) that ship on-device and are referenced via the `<uses-library>` tag |
| 47 | should follow the naming convention `com.android.extensions.<feature-name>` to |
| 48 | avoid placing `androidx`-packaged code in the platform's boot classpath. |
| 49 | |
alanv | 93da5db | 2021-08-11 13:05:51 -0700 | [diff] [blame] | 50 | #### Project directory structure {#module-structure} |
| 51 | |
| 52 | Libraries developed in AndroidX follow a consistent project naming and directory |
| 53 | structure. |
| 54 | |
| 55 | Library groups should organize their projects into directories and project names |
| 56 | (in brackets) as: |
| 57 | |
| 58 | ``` |
| 59 | <feature-name>/ |
| 60 | <feature-name>-<sub-feature>/ [<feature-name>:<feature-name>-<sub-feature>] |
AndroidX Core Team | 4cc85fa | 2021-11-23 15:58:34 +0000 | [diff] [blame] | 61 | samples/ [<feature-name>:<feature-name>-<sub-feature>-samples] |
alanv | 93da5db | 2021-08-11 13:05:51 -0700 | [diff] [blame] | 62 | integration-tests/ |
| 63 | testapp/ [<feature-name>:testapp] |
| 64 | testlib/ [<feature-name>:testlib] |
| 65 | ``` |
| 66 | |
| 67 | For example, the `navigation` library group's directory structure is: |
| 68 | |
| 69 | ``` |
| 70 | navigation/ |
| 71 | navigation-benchmark/ [navigation:navigation-benchmark] |
| 72 | ... |
| 73 | navigation-ui/ [navigation:navigation-ui] |
| 74 | navigation-ui-ktx/ [navigation:navigation-ui-ktx] |
| 75 | integration-tests/ |
| 76 | testapp/ [navigation:integration-tests:testapp] |
| 77 | ``` |
| 78 | |
| 79 | #### Project creator script {#module-creation} |
| 80 | |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 81 | Note: The terms *project*, *module*, and *library* are often used |
alanv | 93da5db | 2021-08-11 13:05:51 -0700 | [diff] [blame] | 82 | interchangeably within AndroidX, with project being the technical term used by |
| 83 | Gradle to describe a build target, e.g. a library that maps to a single AAR. |
| 84 | |
| 85 | New projects can be created using our |
| 86 | [project creation script](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:development/project-creator/?q=project-creator&ss=androidx%2Fplatform%2Fframeworks%2Fsupport) |
| 87 | available in our repo. |
| 88 | |
| 89 | It will create a new project with the proper structure and configuration based |
| 90 | on your project needs! |
| 91 | |
| 92 | To use it: |
| 93 | |
| 94 | ```sh |
AndroidX Core Team | fa35270 | 2021-08-16 10:06:19 -0700 | [diff] [blame] | 95 | cd ~/androidx-main/frameworks/support && \ |
alanv | 93da5db | 2021-08-11 13:05:51 -0700 | [diff] [blame] | 96 | cd development/project-creator && \ |
| 97 | ./create_project.py androidx.foo foo-bar |
| 98 | ``` |
AndroidX Core Team | 9f9538a | 2021-06-07 12:03:58 -0700 | [diff] [blame] | 99 | |
AndroidX Core Team | 21ccf65 | 2022-04-01 14:53:07 +0000 | [diff] [blame] | 100 | If you see an error message `No module named 'toml'` try the following steps. |
| 101 | |
| 102 | * Install necessary tools if they are not already installed |
| 103 | * (Linux) `sudo apt-get install virtualenv python3-venv` |
| 104 | * (Mac) `pip3 install virtualenv` |
| 105 | * Create a virtual environment with `virtualenv androidx_project_creator` (you |
| 106 | can choose another name for your virtualenv if you wish). |
| 107 | * Install the `toml` library in your virtual env with |
| 108 | `androidx_project_creator/bin/pip3 install toml` |
| 109 | * Run the project creator script from your virtual env with |
| 110 | `androidx_project_creator/bin/python3 |
| 111 | ./development/project-creator/create_project.py androidx.foo foo-bar` |
| 112 | * Delete your virtual env with `rm -rf ./androidx-project_creator` |
| 113 | * virtualenv will automatically .gitignore itself, but you may want to to |
| 114 | remove it anyway. |
| 115 | |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 116 | #### Common sub-feature names {#module-naming-subfeature} |
| 117 | |
| 118 | * `-testing` for an artifact intended to be used while testing usages of your |
| 119 | library, e.g. `androidx.room:room-testing` |
| 120 | * `-core` for a low-level artifact that *may* contain public APIs but is |
| 121 | primarily intended for use by other libraries in the group |
| 122 | * `-ktx` for an Kotlin artifact that exposes idiomatic Kotlin APIs as an |
AndroidX Core Team | 0db91f0 | 2021-05-06 22:45:18 +0000 | [diff] [blame] | 123 | extension to a Java-only library (see |
| 124 | [additional -ktx guidance](#module-ktx)) |
AndroidX Core Team | 4cc85fa | 2021-11-23 15:58:34 +0000 | [diff] [blame] | 125 | * `-samples` for sample code which can be inlined in documentation (see |
| 126 | [Sample code in Kotlin modules](#sample-code-in-kotlin-modules) |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 127 | * `-<third-party>` for an artifact that integrates an optional third-party API |
| 128 | surface, e.g. `-proto` or `-rxjava2`. Note that a major version is included |
| 129 | in the sub-feature name for third-party API surfaces where the major version |
| 130 | indicates binary compatibility (only needed for post-1.x). |
| 131 | |
| 132 | Artifacts **should not** use `-impl` or `-base` to indicate that a library is an |
| 133 | implementation detail shared within the group. Instead, use `-core`. |
| 134 | |
| 135 | #### Splitting existing modules |
| 136 | |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 137 | Existing modules *should not* be split into smaller modules; doing so creates |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 138 | the potential for class duplication issues when a developer depends on a new |
| 139 | sub-module alongside the older top-level module. Consider the following |
| 140 | scenario: |
| 141 | |
| 142 | * `androidx.library:1.0.0` |
AndroidX Core Team | 4320124 | 2021-01-26 21:38:01 +0000 | [diff] [blame] | 143 | * contains class `androidx.library.A` |
| 144 | * contains class `androidx.library.util.B` |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 145 | |
| 146 | This module is split, moving `androidx.library.util.B` to a new module: |
| 147 | |
| 148 | * `androidx.library:1.1.0` |
| 149 | * contains class `androidx.library.A` |
AndroidX Core Team | 4320124 | 2021-01-26 21:38:01 +0000 | [diff] [blame] | 150 | * depends on `androidx.library.util:1.1.0` |
| 151 | * `androidx.library.util:1.1.0` |
| 152 | * contains class `androidx.library.util.B` |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 153 | |
AndroidX Core Team | 4320124 | 2021-01-26 21:38:01 +0000 | [diff] [blame] | 154 | A developer writes an app that depends directly on `androidx.library.util:1.1.0` |
| 155 | and also transitively pulls in `androidx.library:1.0.0`. Their app will no |
| 156 | longer compile due to class duplication of `androidx.library.util.B`. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 157 | |
| 158 | While it is possible for the developer to fix this by manually specifying a |
| 159 | dependency on `androidx.library:1.1.0`, there is no easy way for the developer |
| 160 | to discover this solution from the class duplication error raised at compile |
| 161 | time. |
| 162 | |
AndroidX Core Team | ee9c1aa | 2021-04-06 17:29:05 +0000 | [diff] [blame] | 163 | Same-version groups are a special case for this rule. Existing modules that are |
| 164 | already in a same-version group may be split into sub-modules provided that (a) |
| 165 | the sub-modules are also in the same-version group and (b) the full API surface |
| 166 | of the existing module is preserved through transitive dependencies, e.g. the |
| 167 | sub-modules are added as dependencies of the existing module. |
| 168 | |
AndroidX Core Team | ee1457a | 2021-02-25 16:13:10 +0000 | [diff] [blame] | 169 | #### Same-version (atomic) groups {#modules-atomic} |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 170 | |
| 171 | Library groups are encouraged to opt-in to a same-version policy whereby all |
| 172 | libraries in the group use the same version and express exact-match dependencies |
| 173 | on libraries within the group. Such groups must increment the version of every |
| 174 | library at the same time and release all libraries at the same time. |
| 175 | |
| 176 | Atomic groups are specified in |
AndroidX Core Team | 179f25b | 2022-02-04 15:20:48 -0800 | [diff] [blame] | 177 | [libraryversions.toml](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:libraryversions.toml): |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 178 | |
AndroidX Core Team | 179f25b | 2022-02-04 15:20:48 -0800 | [diff] [blame] | 179 | ``` |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 180 | // Non-atomic library group |
AndroidX Core Team | 179f25b | 2022-02-04 15:20:48 -0800 | [diff] [blame] | 181 | APPCOMPAT = { group = "androidx.appcompat" } |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 182 | // Atomic library group |
AndroidX Core Team | 179f25b | 2022-02-04 15:20:48 -0800 | [diff] [blame] | 183 | APPSEARCH = { group = "androidx.appsearch", atomicGroupVersion = "versions.APPSEARCH" } |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 184 | ``` |
| 185 | |
| 186 | Libraries within an atomic group should not specify a version in their |
| 187 | `build.gradle`: |
| 188 | |
| 189 | ```groovy |
| 190 | androidx { |
| 191 | name = 'AppSearch' |
| 192 | publish = Publish.SNAPSHOT_AND_RELEASE |
| 193 | mavenGroup = LibraryGroups.APPSEARCH |
| 194 | inceptionYear = '2019' |
| 195 | description = 'Provides local and centralized app indexing' |
| 196 | } |
| 197 | ``` |
| 198 | |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 199 | The benefits of using an atomic group are: |
| 200 | |
| 201 | - Easier for developers to understand dependency versioning |
| 202 | - `@RestrictTo(LIBRARY_GROUP)` APIs are treated as private APIs and not |
| 203 | tracked for binary compatibility |
| 204 | - `@RequiresOptIn` APIs defined within the group may be used without any |
| 205 | restrictions between libraries in the group |
| 206 | |
| 207 | Potential drawbacks include: |
| 208 | |
| 209 | - All libraries within the group must be versioned identically at head |
| 210 | - All libraries within the group must release at the same time |
| 211 | |
AndroidX Core Team | ee1457a | 2021-02-25 16:13:10 +0000 | [diff] [blame] | 212 | #### Early-stage development {#modules-atomic-alpha} |
| 213 | |
| 214 | There is one exception to the same-version policy: newly-added libraries within |
| 215 | an atomic group may be "quarantined" from other libraries to allow for rapid |
| 216 | iteration until they are API-stable. |
| 217 | |
| 218 | A quarantined library must stay within the `1.0.0-alphaXX` cycle until it is |
| 219 | ready to conform to the same-version policy. While in quarantime, a library is |
| 220 | treated at though it is in a separate group from its nomical same-version group: |
| 221 | |
| 222 | - Must stay in `1.0.0-alphaXX`, e.g. same-version policy is not enforced |
| 223 | - May use `project` or pinned version dependencies, e.g. strict-match |
| 224 | dependencies are not enforced |
| 225 | - May release on a separate cadence from other libraries within group |
| 226 | - Must not reference restricted `LIBRARY-GROUP`-scoped APIs |
| 227 | |
| 228 | When the library would like to leave quarantine, it must wait for its atomic |
| 229 | group to be within a `beta` cycle and then match the version. It is okay for a |
| 230 | library in this situation to skip versions, e.g. move directly from |
| 231 | `1.0.0-alpha02` to `2.1.3-beta06`. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 232 | |
| 233 | ### Choosing a `minSdkVersion` {#module-minsdkversion} |
| 234 | |
| 235 | The recommended minimum SDK version for new Jetpack libraries is currently |
alanv | e2f875c | 2021-07-09 08:58:30 -0700 | [diff] [blame] | 236 | **19** (Android 4.4, KitKat). This SDK was chosen to represent 99% of active |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 237 | devices based on Play Store check-ins (see Android Studio |
| 238 | [distribution metadata](https://dl.google.com/android/studio/metadata/distributions.json) |
| 239 | for current statistics). This maximizes potential users for external developers |
| 240 | while minimizing the amount of overhead necessary to support legacy versions. |
| 241 | |
| 242 | However, if no explicit minimum SDK version is specified for a library, the |
alanv | 195c35f | 2021-06-10 14:29:40 -0700 | [diff] [blame] | 243 | default is **14** (Android 4.0, Ice Cream Sandwich). |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 244 | |
| 245 | Note that a library **must not** depend on another library with a higher |
| 246 | `minSdkVersion` that its own, so it may be necessary for a new library to match |
| 247 | its dependent libraries' `minSdkVersion`. |
| 248 | |
| 249 | Individual modules may choose a higher minimum SDK version for business or |
| 250 | technical reasons. This is common for device-specific modules such as Auto or |
| 251 | Wear. |
| 252 | |
| 253 | Individual classes or methods may be annotated with the |
| 254 | [@RequiresApi](https://developer.android.com/reference/android/annotation/RequiresApi.html) |
| 255 | annotation to indicate divergence from the overall module's minimum SDK version. |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 256 | Note that this pattern is *not recommended* because it leads to confusion for |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 257 | external developers and should be considered a last-resort when backporting |
| 258 | behavior is not feasible. |
| 259 | |
AndroidX Core Team | 0db91f0 | 2021-05-06 22:45:18 +0000 | [diff] [blame] | 260 | ### Kotlin extension `-ktx` libraries {#module-ktx} |
| 261 | |
| 262 | New libraries should prefer Kotlin sources with built-in Java compatibility via |
| 263 | `@JvmName` and other affordances of the Kotlin language; however, existing Java |
| 264 | sourced libraries may benefit from extending their API surface with |
| 265 | Kotlin-friendly APIs in a `-ktx` library. |
| 266 | |
| 267 | A Kotlin extension library **may only** provide extensions for a single base |
| 268 | library's API surface and its name **must** match the base library exactly. For |
| 269 | example, `work:work-ktx` may only provide extensions for APIs exposed by |
| 270 | `work:work`. |
| 271 | |
| 272 | Additionally, an extension library **must** specify an `api`-type dependency on |
| 273 | the base library and **must** be versioned and released identically to the base |
| 274 | library. |
| 275 | |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 276 | Kotlin extension libraries *should not* expose new functionality; they should |
AndroidX Core Team | 0db91f0 | 2021-05-06 22:45:18 +0000 | [diff] [blame] | 277 | only provide Kotlin-friendly versions of existing Java-facing functionality. |
| 278 | |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 279 | ## Platform compatibility API patterns {#platform-compatibility-apis} |
| 280 | |
AndroidX Core Team | 3758414 | 2021-02-25 17:58:46 +0000 | [diff] [blame] | 281 | NOTE For all library APIs that wrap or provide parity with platform APIs, |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 282 | *parity with the platform APIs overrides API guidelines*. For example, if the |
AndroidX Core Team | 3758414 | 2021-02-25 17:58:46 +0000 | [diff] [blame] | 283 | platform API being wrapped has incorrect `Executor` and `Callback` ordering |
| 284 | according to the API Guidelines, the corresponding library API should have the |
| 285 | exact same (incorrect) ordering. |
| 286 | |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 287 | ### Static shims (ex. [ViewCompat](https://developer.android.com/reference/android/support/v4/view/ViewCompat.html)) {#static-shim} |
| 288 | |
| 289 | When to use? |
| 290 | |
| 291 | * Platform class exists at module's `minSdkVersion` |
| 292 | * Compatibility implementation does not need to store additional metadata |
| 293 | |
| 294 | Implementation requirements |
| 295 | |
| 296 | * Class name **must** be `<PlatformClass>Compat` |
| 297 | * Package name **must** be `androidx.<feature>.<platform.package>` |
| 298 | * Superclass **must** be `Object` |
| 299 | * Class **must** be non-instantiable, i.e. constructor is private no-op |
| 300 | * Static fields and static methods **must** match match signatures with |
AndroidX Core Team | 4cc85fa | 2021-11-23 15:58:34 +0000 | [diff] [blame] | 301 | `<PlatformClass>` |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 302 | * Static fields that can be inlined, ex. integer constants, **must not** |
| 303 | be shimmed |
| 304 | * Public method names **must** match platform method names |
AndroidX Core Team | 4cc85fa | 2021-11-23 15:58:34 +0000 | [diff] [blame] | 305 | * Public methods **must** be static and take `<PlatformClass>` as first |
| 306 | parameter (except in the case of static methods on the platform class, as |
| 307 | shown below) |
| 308 | * Implementation *may* delegate to `<PlatformClass>` methods when available |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 309 | |
| 310 | #### Sample {#static-shim-sample} |
| 311 | |
| 312 | The following sample provides static helper methods for the platform class |
| 313 | `android.os.Process`. |
| 314 | |
| 315 | ```java |
| 316 | /** |
| 317 | * Helper for accessing features in {@link Process}. |
| 318 | */ |
| 319 | public final class ProcessCompat { |
| 320 | private ProcessCompat() { |
| 321 | // This class is non-instantiable. |
| 322 | } |
| 323 | |
| 324 | /** |
| 325 | * [Docs should match platform docs.] |
| 326 | * |
| 327 | * Compatibility behavior: |
| 328 | * <ul> |
| 329 | * <li>SDK 24 and above, this method matches platform behavior. |
| 330 | * <li>SDK 16 through 23, this method is a best-effort to match platform behavior, but may |
| 331 | * default to returning {@code true} if an accurate result is not available. |
| 332 | * <li>SDK 15 and below, this method always returns {@code true} as application UIDs and |
| 333 | * isolated processes did not exist yet. |
| 334 | * </ul> |
| 335 | * |
| 336 | * @param [match platform docs] |
| 337 | * @return [match platform docs], or a value based on platform-specific fallback behavior |
| 338 | */ |
| 339 | public static boolean isApplicationUid(int uid) { |
| 340 | if (Build.VERSION.SDK_INT >= 24) { |
| 341 | return Api24Impl.isApplicationUid(uid); |
| 342 | } else if (Build.VERSION.SDK_INT >= 17) { |
| 343 | return Api17Impl.isApplicationUid(uid); |
| 344 | } else if (Build.VERSION.SDK_INT == 16) { |
| 345 | return Api16Impl.isApplicationUid(uid); |
| 346 | } else { |
| 347 | return true; |
| 348 | } |
| 349 | } |
| 350 | |
| 351 | @RequiresApi(24) |
| 352 | static class Api24Impl { |
| 353 | static boolean isApplicationUid(int uid) { |
| 354 | // In N, the method was made public on android.os.Process. |
| 355 | return Process.isApplicationUid(uid); |
| 356 | } |
| 357 | } |
| 358 | |
| 359 | @RequiresApi(17) |
| 360 | static class Api17Impl { |
| 361 | private static Method sMethod_isAppMethod; |
| 362 | private static boolean sResolved; |
| 363 | |
| 364 | static boolean isApplicationUid(int uid) { |
| 365 | // In JELLY_BEAN_MR2, the equivalent isApp(int) hidden method moved to public class |
| 366 | // android.os.UserHandle. |
| 367 | try { |
| 368 | if (!sResolved) { |
| 369 | sResolved = true; |
| 370 | sMethod_isAppMethod = UserHandle.class.getDeclaredMethod("isApp",int.class); |
| 371 | } |
| 372 | if (sMethod_isAppMethod != null) { |
| 373 | return (Boolean) sMethod_isAppMethod.invoke(null, uid); |
| 374 | } |
| 375 | } catch (Exception e) { |
| 376 | e.printStackTrace(); |
| 377 | } |
| 378 | return true; |
| 379 | } |
| 380 | } |
| 381 | |
| 382 | ... |
| 383 | } |
| 384 | ``` |
| 385 | |
| 386 | ### Wrapper (ex. [AccessibilityNodeInfoCompat](https://developer.android.com/reference/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.html)) {#wrapper} |
| 387 | |
| 388 | When to use? |
| 389 | |
| 390 | * Platform class may not exist at module's `minSdkVersion` |
| 391 | * Compatibility implementation may need to store additional metadata |
| 392 | * Needs to integrate with platform APIs as return value or method argument |
| 393 | * **Note:** Should be avoided when possible, as using wrapper classes makes it |
| 394 | very difficult to deprecate classes and migrate source code when the |
| 395 | `minSdkVersion` is raised |
| 396 | |
| 397 | #### Sample {#wrapper-sample} |
| 398 | |
| 399 | The following sample wraps a hypothetical platform class `ModemInfo` that was |
| 400 | added to the platform SDK in API level 23: |
| 401 | |
| 402 | ```java |
| 403 | public final class ModemInfoCompat { |
| 404 | // Only guaranteed to be non-null on SDK_INT >= 23. Note that referencing the |
| 405 | // class itself directly is fine -- only references to class members need to |
| 406 | // be pushed into static inner classes. |
| 407 | private final ModemInfo wrappedObj; |
| 408 | |
| 409 | /** |
| 410 | * [Copy platform docs for matching constructor.] |
| 411 | */ |
| 412 | public ModemInfoCompat() { |
| 413 | if (SDK_INT >= 23) { |
| 414 | wrappedObj = Api23Impl.create(); |
| 415 | } else { |
| 416 | wrappedObj = null; |
| 417 | } |
| 418 | ... |
| 419 | } |
| 420 | |
| 421 | @RequiresApi(23) |
| 422 | private ModemInfoCompat(@NonNull ModemInfo obj) { |
| 423 | mWrapped = obj; |
| 424 | } |
| 425 | |
| 426 | /** |
| 427 | * Provides a backward-compatible wrapper for {@link ModemInfo}. |
| 428 | * <p> |
| 429 | * This method is not supported on devices running SDK < 23 since the platform |
| 430 | * class will not be available. |
| 431 | * |
| 432 | * @param info platform class to wrap |
| 433 | * @return wrapped class, or {@code null} if parameter is {@code null} |
| 434 | */ |
| 435 | @RequiresApi(23) |
| 436 | @NonNull |
| 437 | public static ModemInfoCompat toModemInfoCompat(@NonNull ModemInfo info) { |
| 438 | return new ModemInfoCompat(obj); |
| 439 | } |
| 440 | |
| 441 | /** |
| 442 | * Provides the {@link ModemInfo} represented by this object. |
| 443 | * <p> |
| 444 | * This method is not supported on devices running SDK < 23 since the platform |
| 445 | * class will not be available. |
| 446 | * |
| 447 | * @return platform class object |
| 448 | * @see ModemInfoCompat#toModemInfoCompat(ModemInfo) |
| 449 | */ |
| 450 | @RequiresApi(23) |
| 451 | @NonNull |
| 452 | public ModemInfo toModemInfo() { |
| 453 | return mWrapped; |
| 454 | } |
| 455 | |
| 456 | /** |
| 457 | * [Docs should match platform docs.] |
| 458 | * |
| 459 | * Compatibility behavior: |
| 460 | * <ul> |
| 461 | * <li>API level 23 and above, this method matches platform behavior. |
| 462 | * <li>API level 18 through 22, this method ... |
| 463 | * <li>API level 17 and earlier, this method always returns false. |
| 464 | * </ul> |
| 465 | * |
| 466 | * @return [match platform docs], or platform-specific fallback behavior |
| 467 | */ |
| 468 | public boolean isLteSupported() { |
| 469 | if (SDK_INT >= 23) { |
| 470 | return Api23Impl.isLteSupported(mWrapped); |
| 471 | } else if (SDK_INT >= 18) { |
| 472 | // Smart fallback behavior based on earlier APIs. |
| 473 | ... |
| 474 | } |
| 475 | // Default behavior. |
| 476 | return false; |
| 477 | } |
| 478 | |
| 479 | // All references to class members -- including the constructor -- must be |
| 480 | // made on an inner class to avoid soft-verification errors that slow class |
| 481 | // loading and prevent optimization. |
| 482 | @RequiresApi(23) |
| 483 | private static class Api23Impl { |
AndroidX Core Team | 4cc85fa | 2021-11-23 15:58:34 +0000 | [diff] [blame] | 484 | @DoNotInline |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 485 | @NonNull |
| 486 | static ModemInfo create() { |
| 487 | return new ModemInfo(); |
| 488 | } |
| 489 | |
AndroidX Core Team | 4cc85fa | 2021-11-23 15:58:34 +0000 | [diff] [blame] | 490 | @DoNotInline |
| 491 | static boolean isLteSupported(ModemInfo obj) { |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 492 | return obj.isLteSupported(); |
| 493 | } |
| 494 | } |
| 495 | } |
| 496 | ``` |
| 497 | |
| 498 | Note that libraries written in Java should express conversion to and from the |
| 499 | platform class differently than Kotlin classes. For Java classes, conversion |
| 500 | from the platform class to the wrapper should be expressed as a `static` method, |
| 501 | while conversion from the wrapper to the platform class should be a method on |
| 502 | the wrapper object: |
| 503 | |
| 504 | ```java |
| 505 | @NonNull |
| 506 | public static ModemInfoCompat toModemInfoCompat(@NonNull ModemInfo info); |
| 507 | |
| 508 | @NonNull |
| 509 | public ModemInfo toModemInfo(); |
| 510 | ``` |
| 511 | |
| 512 | In cases where the primary library is written in Java and has an accompanying |
| 513 | `-ktx` Kotlin extensions library, the following conversion should be provided as |
| 514 | an extension function: |
| 515 | |
| 516 | ```kotlin |
| 517 | fun ModemInfo.toModemInfoCompat() : ModemInfoCompat |
| 518 | ``` |
| 519 | |
| 520 | Whereas in cases where the primary library is written in Kotlin, the conversion |
| 521 | should be provided as an extension factory: |
| 522 | |
| 523 | ```kotlin |
| 524 | class ModemInfoCompat { |
| 525 | fun toModemInfo() : ModemInfo |
| 526 | |
| 527 | companion object { |
| 528 | @JvmStatic |
| 529 | @JvmName("toModemInfoCompat") |
| 530 | fun ModemInfo.toModemInfoCompat() : ModemInfoCompat |
| 531 | } |
| 532 | } |
| 533 | ``` |
| 534 | |
| 535 | #### API guidelines {#wrapper-api-guidelines} |
| 536 | |
| 537 | ##### Naming {#wrapper-naming} |
| 538 | |
| 539 | * Class name **must** be `<PlatformClass>Compat` |
| 540 | * Package name **must** be `androidx.core.<platform.package>` |
| 541 | * Superclass **must not** be `<PlatformClass>` |
| 542 | |
| 543 | ##### Construction {#wrapper-construction} |
| 544 | |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 545 | * Class *may* have public constructor(s) to provide parity with public |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 546 | `PlatformClass` constructors |
| 547 | * Constructor used to wrap `PlatformClass` **must not** be public |
| 548 | * Class **must** implement a static `PlatformClassCompat |
| 549 | toPlatformClassCompat(PlatformClass)` method to wrap `PlatformClass` on |
| 550 | supported SDK levels |
| 551 | * If class does not exist at module's `minSdkVersion`, method must be |
| 552 | annotated with `@RequiresApi(<sdk>)` for SDK version where class was |
| 553 | introduced |
| 554 | |
| 555 | #### Implementation {#wrapper-implementation} |
| 556 | |
| 557 | * Class **must** implement a `PlatformClass toPlatformClass()` method to |
| 558 | unwrap `PlatformClass` on supported SDK levels |
| 559 | * If class does not exist at module's `minSdkVersion`, method must be |
| 560 | annotated with `@RequiresApi(<sdk>)` for SDK version where class was |
| 561 | introduced |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 562 | * Implementation *may* delegate to `PlatformClass` methods when available (see |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 563 | below note for caveats) |
| 564 | * To avoid runtime class verification issues, all operations that interact |
| 565 | with the internal structure of `PlatformClass` must be implemented in inner |
| 566 | classes targeted to the SDK level at which the operation was added. |
| 567 | * See the [sample](#wrapper-sample) for an example of interacting with a |
| 568 | method that was added in SDK level 23. |
| 569 | |
| 570 | ### 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} |
| 571 | |
| 572 | When to use? |
| 573 | |
| 574 | * Platform class may exist at module's `minSdkVersion` |
| 575 | * Does not need to integrate with platform APIs |
| 576 | * Does not need to coexist with platform class, ex. no potential `import` |
| 577 | collision due to both compatibility and platform classes being referenced |
| 578 | within the same source file |
| 579 | |
| 580 | Implementation requirements |
| 581 | |
| 582 | * Class name **must** be `<PlatformClass>` |
| 583 | * Package name **must** be `androidx.<platform.package>` |
| 584 | * Superclass **must not** be `<PlatformClass>` |
| 585 | * Class **must not** expose `PlatformClass` in public API |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 586 | * In exceptional cases, a *released* standalone class may add conversion |
| 587 | between itself and the equivalent platform class; however, *new* classes |
AndroidX Core Team | ee1457a | 2021-02-25 16:13:10 +0000 | [diff] [blame] | 588 | that support conversion should follow the [Wrapper](#wrapper) |
| 589 | guidelines. In these cases, use a `toPlatform<PlatformClass>` and |
| 590 | `static toCompat<PlatformClass>` method naming convention. |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 591 | * Implementation *may* delegate to `PlatformClass` methods when available |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 592 | |
| 593 | ### Standalone JAR library (no Android dependencies) {#standalone-jar-library-no-android-dependencies} |
| 594 | |
| 595 | When to use |
| 596 | |
| 597 | * General purpose library with minimal interaction with Android types |
| 598 | * or when abstraction around types can be used (e.g. Room's SQLite |
| 599 | wrapper) |
| 600 | * Lib used in parts of app with minimal Android dependencies |
| 601 | * ex. Repository, ViewModel |
| 602 | * When Android dependency can sit on top of common library |
| 603 | * Clear separation between android dependent and independent parts of your |
| 604 | library |
| 605 | * Clear that future integration with android dependencies can be layered |
| 606 | separately |
| 607 | |
| 608 | **Examples:** |
| 609 | |
| 610 | The **Paging Library** pages data from DataSources (such as DB content from Room |
| 611 | or network content from Retrofit) into PagedLists, so they can be presented in a |
| 612 | RecyclerView. Since the included Adapter receives a PagedList, and there are no |
| 613 | other Android dependencies, Paging is split into two parts - a no-android |
| 614 | library (paging-common) with the majority of the paging code, and an android |
| 615 | library (paging-runtime) with just the code to present a PagedList in a |
| 616 | RecyclerView Adapter. This way, tests of Repositories and their components can |
| 617 | be tested in host-side tests. |
| 618 | |
| 619 | **Room** loads SQLite data on Android, but provides an abstraction for those |
| 620 | that want to use a different SQL implementation on device. This abstraction, and |
| 621 | the fact that Room generates code dynamically, means that Room interfaces can be |
| 622 | used in host-side tests (though actual DB code should be tested on device, since |
| 623 | DB impls may be significantly different on host). |
| 624 | |
| 625 | ## Implementing compatibility {#compat} |
| 626 | |
| 627 | ### Referencing new APIs {#compat-newapi} |
| 628 | |
| 629 | Generally, methods on extension library classes should be available to all |
| 630 | devices above the library's `minSdkVersion`. |
| 631 | |
| 632 | #### Checking device SDK version {#compat-sdk} |
| 633 | |
| 634 | The most common way of delegating to platform or backport implementations is to |
| 635 | compare the device's `Build.VERSION.SDK_INT` field to a known-good SDK version; |
| 636 | for example, the SDK in which a method first appeared or in which a critical bug |
| 637 | was first fixed. |
| 638 | |
| 639 | Non-reflective calls to new APIs gated on `SDK_INT` **must** be made from |
| 640 | version-specific static inner classes to avoid verification errors that |
| 641 | negatively affect run-time performance. For more information, see Chromium's |
| 642 | guide to |
| 643 | [Class Verification Failures](https://chromium.googlesource.com/chromium/src/+/HEAD/build/android/docs/class_verification_failures.md). |
| 644 | |
| 645 | Methods in implementation-specific classes **must** be paired with the |
| 646 | `@DoNotInline` annotation to prevent them from being inlined. |
| 647 | |
| 648 | ```java {.good} |
| 649 | public static void saveAttributeDataForStyleable(@NonNull View view, ...) { |
| 650 | if (Build.VERSION.SDK_INT >= 29) { |
| 651 | Api29Impl.saveAttributeDataForStyleable(view, ...); |
| 652 | } |
| 653 | } |
| 654 | |
| 655 | @RequiresApi(29) |
| 656 | private static class Api29Impl { |
| 657 | @DoNotInline |
| 658 | static void saveAttributeDataForStyleable(@NonNull View view, ...) { |
| 659 | view.saveAttributeDataForStyleable(...); |
| 660 | } |
| 661 | } |
| 662 | ``` |
| 663 | |
| 664 | Alternatively, in Kotlin sources: |
| 665 | |
| 666 | ```kotlin {.good} |
| 667 | @RequiresApi(29) |
AndroidX Core Team | 23c5044 | 2021-05-18 13:03:40 -0400 | [diff] [blame] | 668 | private object Api29Impl { |
| 669 | @JvmStatic |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 670 | @DoNotInline |
| 671 | fun saveAttributeDataForStyleable(view: View, ...) { ... } |
| 672 | } |
| 673 | ``` |
| 674 | |
| 675 | When developing against pre-release SDKs where the `SDK_INT` has not been |
| 676 | finalized, SDK checks **must** use `BuildCompat.isAtLeastX()` methods. |
| 677 | |
| 678 | ```java {.good} |
| 679 | @NonNull |
| 680 | public static List<Window> getAllWindows() { |
| 681 | if (BuildCompat.isAtLeastR()) { |
| 682 | return ApiRImpl.getAllWindows(); |
| 683 | } |
| 684 | return Collections.emptyList(); |
| 685 | } |
| 686 | ``` |
| 687 | |
| 688 | #### Device-specific issues {#compat-oem} |
| 689 | |
| 690 | Library code may work around device- or manufacturer-specific issues -- issues |
| 691 | not present in AOSP builds of Android -- *only* if a corresponding CTS test |
| 692 | and/or CDD policy is added to the next revision of the Android platform. Doing |
| 693 | so ensures that such issues can be detected and fixed by OEMs. |
| 694 | |
| 695 | #### Handling `minSdkVersion` disparity {#compat-minsdk} |
| 696 | |
| 697 | Methods that only need to be accessible on newer devices, including |
| 698 | `to<PlatformClass>()` methods, may be annotated with `@RequiresApi(<sdk>)` to |
| 699 | indicate they will fail to link on older SDKs. This annotation is enforced at |
| 700 | build time by Lint. |
| 701 | |
| 702 | #### Handling `targetSdkVersion` behavior changes {#compat-targetsdk} |
| 703 | |
| 704 | To preserve application functionality, device behavior at a given API level may |
| 705 | change based on an application's `targetSdkVersion`. For example, if an app with |
| 706 | `targetSdkVersion` set to API level 22 runs on a device with API level 29, all |
| 707 | required permissions will be granted at installation time and the run-time |
| 708 | permissions framework will emulate earlier device behavior. |
| 709 | |
| 710 | Libraries do not have control over the app's `targetSdkVersion` and -- in rare |
| 711 | cases -- may need to handle variations in platform behavior. Refer to the |
| 712 | following pages for version-specific behavior changes: |
| 713 | |
| 714 | * API level 29: |
| 715 | [Android Q behavior changes: apps targeting Q](https://developer.android.com/preview/behavior-changes-q) |
| 716 | * API level 28: |
| 717 | [Behavior changes: apps targeting API level 28+](https://developer.android.com/about/versions/pie/android-9.0-changes-28) |
| 718 | * API level 26: |
| 719 | [Changes for apps targeting Android 8.0](https://developer.android.com/about/versions/oreo/android-8.0-changes#o-apps) |
| 720 | * API level 24: |
| 721 | [Changes for apps targeting Android 7.0](https://developer.android.com/about/versions/nougat/android-7.0-changes#n-apps) |
| 722 | * API level 21: |
| 723 | [Android 5.0 Behavior Changes](https://developer.android.com/about/versions/android-5.0-changes) |
| 724 | * API level 19: |
| 725 | [Android 4.4 APIs](https://developer.android.com/about/versions/android-4.4) |
| 726 | |
| 727 | #### Working around Lint issues {#compat-lint} |
| 728 | |
| 729 | In rare cases, Lint may fail to interpret API usages and yield a `NewApi` error |
| 730 | and require the use of `@TargetApi` or `@SuppressLint('NewApi')` annotations. |
| 731 | Both of these annotations are strongly discouraged and may only be used |
| 732 | temporarily. They **must never** be used in a stable release. Any usage of these |
| 733 | annotation **must** be associated with an active bug, and the usage must be |
| 734 | removed when the bug is resolved. |
| 735 | |
| 736 | ### Delegating to API-specific implementations {#delegating-to-api-specific-implementations} |
| 737 | |
| 738 | #### SDK-dependent reflection |
| 739 | |
| 740 | Starting in API level 28, the platform restricts which |
| 741 | [non-SDK interfaces](https://developer.android.com/distribute/best-practices/develop/restrictions-non-sdk-interfaces) |
| 742 | can be accessed via reflection by apps and libraries. As a general rule, you |
| 743 | will **not** be able to use reflection to access hidden APIs on devices with |
| 744 | `SDK_INT` greater than `Build.VERSION_CODES.P` (28). |
| 745 | |
| 746 | On earlier devices, reflection on hidden platform APIs is allowed **only** when |
| 747 | an alternative public platform API exists in a later revision of the Android |
| 748 | SDK. For example, the following implementation is allowed: |
| 749 | |
| 750 | ```java |
| 751 | public AccessibilityDelegate getAccessibilityDelegate(View v) { |
| 752 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { |
| 753 | // Retrieve the delegate using a public API. |
| 754 | return v.getAccessibilityDelegate(); |
| 755 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { |
| 756 | // Retrieve the delegate by reflecting on a private field. If the |
| 757 | // field does not exist or cannot be accessed, this will no-op. |
| 758 | if (sAccessibilityDelegateField == null) { |
| 759 | try { |
| 760 | sAccessibilityDelegateField = View.class |
| 761 | .getDeclaredField("mAccessibilityDelegate"); |
| 762 | sAccessibilityDelegateField.setAccessible(true); |
| 763 | } catch (Throwable t) { |
| 764 | sAccessibilityDelegateCheckFailed = true; |
| 765 | return null; |
| 766 | } |
| 767 | } |
| 768 | try { |
| 769 | Object o = sAccessibilityDelegateField.get(v); |
| 770 | if (o instanceof View.AccessibilityDelegate) { |
| 771 | return (View.AccessibilityDelegate) o; |
| 772 | } |
| 773 | return null; |
| 774 | } catch (Throwable t) { |
| 775 | sAccessibilityDelegateCheckFailed = true; |
| 776 | return null; |
| 777 | } |
| 778 | } else { |
| 779 | // There is no way to retrieve the delegate, even via reflection. |
| 780 | return null; |
| 781 | } |
| 782 | ``` |
| 783 | |
| 784 | Calls to public APIs added in pre-release revisions *must* be gated using |
| 785 | `BuildCompat`: |
| 786 | |
| 787 | ```java |
| 788 | if (BuildCompat.isAtLeastQ()) { |
| 789 | // call new API added in Q |
| 790 | } else if (Build.SDK_INT.VERSION >= Build.VERSION_CODES.SOME_RELEASE) { |
| 791 | // make a best-effort using APIs that we expect to be available |
| 792 | } else { |
| 793 | // no-op or best-effort given no information |
| 794 | } |
| 795 | ``` |
| 796 | |
| 797 | ### Inter-process communication {#inter-process-communication} |
| 798 | |
| 799 | Protocols and data structures used for IPC must support interoperability between |
| 800 | different versions of libraries and should be treated similarly to public API. |
| 801 | |
| 802 | #### Data structures |
| 803 | |
alanv | df3e8b9 | 2021-11-30 13:13:05 -0800 | [diff] [blame] | 804 | **Do not** use `Parcelable` for any class that may be used for IPC or otherwise |
| 805 | exposed as public API. The wire format used by `Parcelable` does not provide any |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 806 | compatibility guarantees and will result in crashes if fields are added or |
| 807 | removed between library versions. |
| 808 | |
| 809 | **Do not** design your own serialization mechanism or wire format for disk |
| 810 | storage or inter-process communication. Preserving and verifying compatibility |
| 811 | is difficult and error-prone. |
| 812 | |
alanv | ca08330 | 2021-08-19 10:30:02 -0700 | [diff] [blame] | 813 | Developers **should** use protocol buffers for most cases. See |
| 814 | [Protobuf](#dependencies-protobuf) for more information on using protocol |
alanv | df3e8b9 | 2021-11-30 13:13:05 -0800 | [diff] [blame] | 815 | buffers in your library. **Do** use protocol buffers if your data structure is |
| 816 | complex and likely to change over time. If your data includes `FileDescriptor`s, |
| 817 | `Binder`s, or other platform-defined `Parcelable` data structures, they will |
| 818 | need to be stored alongside the protobuf bytes in a `Bundle`. |
alanv | ca08330 | 2021-08-19 10:30:02 -0700 | [diff] [blame] | 819 | |
alanv | df3e8b9 | 2021-11-30 13:13:05 -0800 | [diff] [blame] | 820 | Developers **may** use `Bundle` in simple cases that require sending `Binder`s, |
| 821 | `FileDescriptor`s, or platform `Parcelable`s across IPC |
| 822 | ([example](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:core/core/src/main/java/androidx/core/graphics/drawable/IconCompat.java;l=820)). |
| 823 | Note that `Bundle` has several caveats: |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 824 | |
AndroidX Core Team | 4e1909a | 2021-10-20 15:04:04 +0000 | [diff] [blame] | 825 | - When running on Android S and below, accessing *any* entry in a `Bundle` |
| 826 | will result in the platform attempting to deserialize *every* entry. This |
| 827 | has been fixed in Android T and later with "lazy" bundles, but developers |
| 828 | should be careful when accessing `Bundle` on earlier platforms. If a single |
| 829 | entry cannot be loaded -- for example if a developer added a custom |
| 830 | `Parcelable` that doesn't exist in the receiver's classpath -- an exception |
| 831 | will be thrown when accessing *any* entry. |
| 832 | - On all platforms, library code that receives `Bundle`s data from outside the |
| 833 | process **must** read the data defensively. See previous note regarding |
| 834 | additional concerns for Android S and below. |
| 835 | - On all platforms, library code that sends `Bundle`s outside the process |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 836 | *should* discourage clients from passing custom `Parcelable`s. |
| 837 | - `Bundle` provides no versioning and Jetpack provides no affordances for |
| 838 | tracking the keys or value types associated with a `Bundle`. Library owners |
| 839 | are responsible for providing their own system for guaranteeing wire format |
| 840 | compatibility between versions. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 841 | |
alanv | df3e8b9 | 2021-11-30 13:13:05 -0800 | [diff] [blame] | 842 | Developers **may** use `VersionedParcelable` in cases where they are already |
| 843 | using the library and understand its limitations. |
| 844 | |
| 845 | In all cases, **do not** expose your serialization mechanism in your API |
| 846 | surface. |
| 847 | |
| 848 | NOTE We are currently investigating the suitability of Square's |
| 849 | [`wire` library](https://github.com/square/wire) for handling protocol buffers |
| 850 | in Android libraries. If adopted, it will replace `proto` library dependencies. |
| 851 | Libraries that expose their serialization mechanism in their API surface *will |
| 852 | not be able to migrate*. |
| 853 | |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 854 | #### Communication protocols |
| 855 | |
| 856 | Any communication prototcol, handshake, etc. must maintain compatibility |
| 857 | consistent with SemVer guidelines. Consider how your protocol will handle |
| 858 | addition and removal of operations or constants, compatibility-breaking changes, |
| 859 | and other modifications without crashing either the host or client process. |
| 860 | |
| 861 | ## Deprecation and removal |
| 862 | |
| 863 | While SemVer's binary compatibility guarantees restrict the types of changes |
| 864 | that may be made within a library revision and make it difficult to remove an |
| 865 | API, there are many other ways to influence how developers interact with your |
| 866 | library. |
| 867 | |
| 868 | ### Deprecation (`@deprecated`) |
| 869 | |
| 870 | Deprecation lets a developer know that they should stop using an API or class. |
| 871 | All deprecations must be marked with a `@Deprecated` Java annotation as well as |
| 872 | a `@deprecated <migration-docs>` docs annotation explaining how the developer |
| 873 | should migrate away from the API. |
| 874 | |
| 875 | Deprecation is an non-breaking API change that must occur in a **major** or |
| 876 | **minor** release. |
| 877 | |
AndroidX Core Team | ee9c1aa | 2021-04-06 17:29:05 +0000 | [diff] [blame] | 878 | APIs that are added during a pre-release cycle and marked as `@Deprecated` |
| 879 | within the same cycle, e.g. added in `alpha01` and deprecated in `alpha06`, |
| 880 | [must be removed](versioning.md#beta-checklist) before moving to `beta01`. |
| 881 | |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 882 | ### Soft removal (@removed) |
| 883 | |
| 884 | Soft removal preserves binary compatibility while preventing source code from |
| 885 | compiling against an API. It is a *source-breaking change* and not recommended. |
| 886 | |
| 887 | Soft removals **must** do the following: |
| 888 | |
| 889 | * Mark the API as deprecated for at least one stable release prior to removal. |
| 890 | * Mark the API with a `@RestrictTo(LIBRARY)` Java annotation as well as a |
| 891 | `@removed <reason>` docs annotation explaining why the API was removed. |
| 892 | * Maintain binary compatibility, as the API may still be called by existing |
| 893 | dependent libraries. |
| 894 | * Maintain behavioral compatibility and existing tests. |
| 895 | |
| 896 | This is a disruptive change and should be avoided when possible. |
| 897 | |
| 898 | Soft removal is a source-breaking API change that must occur in a **major** or |
| 899 | **minor** release. |
| 900 | |
| 901 | ### Hard removal |
| 902 | |
| 903 | Hard removal entails removing the entire implementation of an API that was |
| 904 | exposed in a public release. Prior to removal, an API must be marked as |
| 905 | `@deprecated` for a full **minor** version (`alpha`->`beta`->`rc`->stable), |
| 906 | prior to being hard removed. |
| 907 | |
| 908 | This is a disruptive change and should be avoided when possible. |
| 909 | |
| 910 | Hard removal is a binary-breaking API change that must occur in a **major** |
| 911 | release. |
| 912 | |
| 913 | ### For entire artifacts |
| 914 | |
| 915 | We do not typically deprecate or remove entire artifacts; however, it may be |
| 916 | useful in cases where we want to halt development and focus elsewhere or |
| 917 | strongly discourage developers from using a library. |
| 918 | |
| 919 | Halting development, either because of staffing or prioritization issues, leaves |
| 920 | the door open for future bug fixes or continued development. This quite simply |
| 921 | means we stop releasing updates but retain the source in our tree. |
| 922 | |
| 923 | Deprecating an artifact provides developers with a migration path and strongly |
| 924 | encourages them -- through Lint warnings -- to migrate elsewhere. This is |
| 925 | accomplished by adding a `@Deprecated` and `@deprecated` (with migration |
| 926 | comment) annotation pair to *every* class and interface in the artifact. |
| 927 | |
alanv | f6ca094d | 2022-05-09 09:02:52 -0700 | [diff] [blame] | 928 | To deprecate an entire artifact: |
| 929 | |
| 930 | 1. Mark every top-level API (class, interface, extension function, etc.) in the |
| 931 | artifact as `@Deprecated` and update the API files |
| 932 | ([example CL](https://android-review.googlesource.com/c/platform/frameworks/support/+/1938773)) |
| 933 | 1. Schedule a release of the artifact as a new minor version. When you populate |
| 934 | the release notes, explain that the entire artifact has been deprecated. |
| 935 | Include the reason for deprecation and the migration strategy. |
| 936 | 1. After the artifact has been released, remove the artifact from the source |
| 937 | tree, versions file, and tip-of-tree docs configuration |
| 938 | ([example CL](https://android-review.googlesource.com/c/platform/frameworks/support/+/2061731/)) |
AndroidX Core Team | 95f812d | 2021-07-07 12:52:35 -0700 | [diff] [blame] | 939 | |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 940 | The fully-deprecated artifact will be released as a deprecation release -- it |
| 941 | will ship normally with accompanying release notes indicating the reason for |
| 942 | deprecation and migration strategy, and it will be the last version of the |
| 943 | artifact that ships. It will ship as a new minor stable release. For example, if |
| 944 | `1.0.0` was the last stable release, then the deprecation release will be |
| 945 | `1.1.0`. This is so Android Studio users will get a suggestion to update to a |
| 946 | new stable version, which will contain the `@deprecated` annotations. |
| 947 | |
| 948 | After an artifact has been released as fully-deprecated, it can be removed from |
| 949 | the source tree. |
| 950 | |
| 951 | ## Resources {#resources} |
| 952 | |
| 953 | Generally, follow the official Android guidelines for |
| 954 | [app resources](https://developer.android.com/guide/topics/resources/providing-resources). |
| 955 | Special guidelines for library resources are noted below. |
| 956 | |
| 957 | ### Defining new resources |
| 958 | |
| 959 | Libraries may define new value and attribute resources using the standard |
| 960 | application directory structure used by Android Gradle Plugin: |
| 961 | |
| 962 | ``` |
| 963 | src/main/res/ |
| 964 | values/ |
| 965 | attrs.xml Theme attributes and styleables |
| 966 | dimens.xml Dimensional values |
| 967 | public.xml Public resource definitions |
| 968 | ... |
| 969 | ``` |
| 970 | |
| 971 | However, some libraries may still be using non-standard, legacy directory |
| 972 | structures such as `res-public` for their public resource declarations or a |
| 973 | top-level `res` directory and accompanying custom source set in `build.gradle`. |
| 974 | These libraries will eventually be migrated to follow standard guidelines. |
| 975 | |
| 976 | #### Naming conventions |
| 977 | |
| 978 | Libraries follow the Android platform's resource naming conventions, which use |
| 979 | `camelCase` for attributes and `underline_delimited` for values. For example, |
| 980 | `R.attr.fontProviderPackage` and `R.dimen.material_blue_grey_900`. |
| 981 | |
| 982 | #### Attribute formats |
| 983 | |
| 984 | At build time, attribute definitions are pooled globally across all libraries |
| 985 | used in an application, which means attribute `format`s *must* be identical for |
| 986 | a given `name` to avoid a conflict. |
| 987 | |
| 988 | Within Jetpack, new attribute names *must* be globally unique. Libraries *may* |
| 989 | reference existing public attributes from their dependencies. See below for more |
| 990 | information on public attributes. |
| 991 | |
| 992 | When adding a new attribute, the format should be defined *once* in an `<attr |
| 993 | />` element in the definitions block at the top of `src/main/res/attrs.xml`. |
| 994 | Subsequent references in `<declare-styleable>` elements *must* not include a |
| 995 | `format`: |
| 996 | |
| 997 | `src/main/res/attrs.xml` |
| 998 | |
| 999 | ```xml |
| 1000 | <resources> |
| 1001 | <attr name="fontProviderPackage" format="string" /> |
| 1002 | |
| 1003 | <declare-styleable name="FontFamily"> |
| 1004 | <attr name="fontProviderPackage" /> |
| 1005 | </declare-styleable> |
| 1006 | </resources> |
| 1007 | ``` |
| 1008 | |
| 1009 | ### Public resources |
| 1010 | |
| 1011 | Library resources are private by default, which means developers are discouraged |
| 1012 | from referencing any defined attributes or values from XML or code; however, |
| 1013 | library resources may be declared public to make them available to developers. |
| 1014 | |
| 1015 | Public library resources are considered API surface and are thus subject to the |
| 1016 | same API consistency and documentation requirements as Java APIs. |
| 1017 | |
| 1018 | Libraries will typically only expose theme attributes, ex. `<attr />` elements, |
| 1019 | as public API so that developers can set and retrieve the values stored in |
| 1020 | styles and themes. Exposing values -- such as `<dimen />` and `<string />` -- or |
| 1021 | images -- such as drawable XML and PNGs -- locks the current state of those |
| 1022 | elements as public API that cannot be changed without a major version bump. That |
| 1023 | means changing a publicly-visible icon would be considered a breaking change. |
| 1024 | |
| 1025 | #### Documentation |
| 1026 | |
| 1027 | All public resource definitions should be documented, including top-level |
| 1028 | definitions and re-uses inside `<styleable>` elements: |
| 1029 | |
| 1030 | `src/main/res/attrs.xml` |
| 1031 | |
| 1032 | ```xml |
| 1033 | <resources> |
| 1034 | <!-- String specifying the application package for a Font Provider. --> |
| 1035 | <attr name="fontProviderPackage" format="string" /> |
| 1036 | |
| 1037 | <!-- Attributes that are read when parsing a <fontfamily> tag. --> |
| 1038 | <declare-styleable name="FontFamily"> |
| 1039 | <!-- The package for the Font Provider to be used for the request. This is |
| 1040 | used to verify the identity of the provider. --> |
| 1041 | <attr name="fontProviderPackage" /> |
| 1042 | </declare-styleable> |
| 1043 | </resources> |
| 1044 | ``` |
| 1045 | |
| 1046 | `src/main/res/colors.xml` |
| 1047 | |
| 1048 | ```xml |
| 1049 | <resources> |
| 1050 | <!-- Color for Material Blue-Grey 900. --> |
| 1051 | <color name="material_blue_grey_900">#ff263238</color> |
| 1052 | </resources> |
| 1053 | ``` |
| 1054 | |
| 1055 | #### Public declaration |
| 1056 | |
| 1057 | Resources are declared public by providing a separate `<public />` element with |
| 1058 | a matching type: |
| 1059 | |
| 1060 | `src/main/res/public.xml` |
| 1061 | |
| 1062 | ```xml |
| 1063 | <resources> |
| 1064 | <public name="fontProviderPackage" type="attr" /> |
| 1065 | <public name="material_blue_grey_900" type="color" /> |
| 1066 | </resources> |
| 1067 | ``` |
| 1068 | |
| 1069 | #### More information |
| 1070 | |
| 1071 | See also the official Android Gradle Plugin documentation for |
| 1072 | [Private Resources](https://developer.android.com/studio/projects/android-library#PrivateResources). |
| 1073 | |
| 1074 | ### Manifest entries (`AndroidManifest.xml`) {#resources-manifest} |
| 1075 | |
| 1076 | #### Metadata tags (`<meta-data>`) {#resources-manifest-metadata} |
| 1077 | |
| 1078 | Developers **must not** add `<application>`-level `<meta-data>` tags to library |
| 1079 | manifests or advise developers to add such tags to their application manifests. |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 1080 | Doing so may *inadvertently cause denial-of-service attacks against other apps*. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1081 | |
| 1082 | Assume a library adds a single item of meta-data at the application level. When |
| 1083 | an app uses the library, that meta-data will be merged into the resulting app's |
| 1084 | application entry via manifest merger. |
| 1085 | |
| 1086 | If another app attempts to obtain a list of all activities associated with the |
| 1087 | primary app, that list will contain multiple copies of the `ApplicationInfo`, |
| 1088 | each of which in turn contains a copy of the library's meta-data. As a result, |
| 1089 | one `<metadata>` tag may become hundreds of KB on the binder call to obtain the |
| 1090 | list -- resulting in apps hitting transaction too large exceptions and crashing. |
| 1091 | |
| 1092 | ```xml {.bad} |
| 1093 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
| 1094 | package="androidx.librarypackage"> |
| 1095 | <application> |
| 1096 | <meta-data |
| 1097 | android:name="keyName" |
| 1098 | android:value="@string/value" /> |
| 1099 | </application> |
| 1100 | </manifest> |
| 1101 | ``` |
| 1102 | |
| 1103 | Instead, developers may consider adding `<metadata>` nested inside of |
| 1104 | placeholder `<service>` tags. |
| 1105 | |
| 1106 | ```xml {.good} |
| 1107 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
| 1108 | package="androidx.librarypackage"> |
| 1109 | <application> |
| 1110 | <service |
| 1111 | android:name="androidx.librarypackage.MetadataHolderService" |
| 1112 | android:enabled="false" |
| 1113 | android:exported="false"> |
| 1114 | <meta-data |
| 1115 | android:name="androidx.librarypackage.MetadataHolderService.KEY_NAME" |
| 1116 | android:resource="@string/value" /> |
| 1117 | </service> |
| 1118 | </application> |
| 1119 | ``` |
| 1120 | |
| 1121 | ```java {.good} |
| 1122 | package androidx.libraryname.featurename; |
| 1123 | |
| 1124 | /** |
| 1125 | * A placeholder service to avoid adding application-level metadata. The service |
| 1126 | * is only used to expose metadata defined in the library's manifest. It is |
| 1127 | * never invoked. |
| 1128 | */ |
| 1129 | public final class MetadataHolderService { |
| 1130 | private MetadataHolderService() {} |
| 1131 | |
| 1132 | @Override |
| 1133 | public IBinder onBind(Intent intent) { |
| 1134 | throw new UnsupportedOperationException(); |
| 1135 | } |
| 1136 | } |
| 1137 | ``` |
| 1138 | |
| 1139 | ## Dependencies {#dependencies} |
| 1140 | |
alanv | 93da5db | 2021-08-11 13:05:51 -0700 | [diff] [blame] | 1141 | Artifacts may depend on other artifacts within AndroidX as well as sanctioned |
| 1142 | third-party libraries. |
| 1143 | |
| 1144 | ### Versioned artifacts {#dependencies-versioned} |
| 1145 | |
| 1146 | One of the most difficult aspects of independently-versioned releases is |
| 1147 | maintaining compatibility with public artifacts. In a mono repo such as Google's |
| 1148 | repository or Android Git at master revision, it's easy for an artifact to |
| 1149 | accidentally gain a dependency on a feature that may not be released on the same |
| 1150 | schedule. |
alanv | 11786ae | 2021-05-25 10:55:11 -0700 | [diff] [blame] | 1151 | |
| 1152 | - Project `project(":core:core")` uses the tip-of-tree sources for the |
| 1153 | `androidx.core:core` library and requires that they be loaded in the |
| 1154 | workspace. |
| 1155 | - Playground `projectOrArtifact(":core:core")` is used for |
| 1156 | [Playground](playground.md) projects and will use tip-of-tree sources, if |
| 1157 | present in the workspace, or `SNAPSHOT` prebuilt artifacts from |
| 1158 | [androidx.dev](http://androidx.dev) otherwise. |
| 1159 | - Explicit `"androidx.core:core:1.4.0"` uses the prebuilt AAR and requires |
| 1160 | that it be checked in to the `prebuilts/androidx/internal` local Maven |
| 1161 | repository. |
| 1162 | |
| 1163 | Libraries should prefer explicit dependencies with the lowest possible versions |
| 1164 | that include the APIs or behaviors required by the library, using project or |
| 1165 | Playground specs only in cases where tip-of-tree APIs or behaviors are required. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1166 | |
alanv | 93da5db | 2021-08-11 13:05:51 -0700 | [diff] [blame] | 1167 | #### Pre-release dependencies {#dependencies-pre-release} |
| 1168 | |
| 1169 | Pre-release suffixes **must** propagate up the dependency tree. For example, if |
| 1170 | your artifact has API-type dependencies on pre-release artifacts, ex. |
| 1171 | `1.1.0-alpha01`, then your artifact must also carry the `alpha` suffix. If you |
| 1172 | only have implementation-type dependencies, your artifact may carry either the |
| 1173 | `alpha` or `beta` suffix. |
| 1174 | |
| 1175 | Note: This does not apply to test dependencies: suffixes of test dependencies do |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 1176 | *not* carry over to your artifact. |
alanv | 93da5db | 2021-08-11 13:05:51 -0700 | [diff] [blame] | 1177 | |
| 1178 | #### Pinned versions {#dependencies-prebuilt} |
| 1179 | |
| 1180 | To avoid issues with dependency versioning, consider pinning your artifact's |
| 1181 | dependencies to the oldest version (available via local `maven_repo` or Google |
| 1182 | Maven) that satisfies the artifact's API requirements. This will ensure that the |
| 1183 | artifact's release schedule is not accidentally tied to that of another artifact |
| 1184 | and will allow developers to use older libraries if desired. |
| 1185 | |
| 1186 | ``` |
| 1187 | dependencies { |
| 1188 | api("androidx.collection:collection:1.0.0") |
| 1189 | ... |
| 1190 | } |
| 1191 | ``` |
| 1192 | |
| 1193 | Artifacts should be built and tested against both pinned and tip-of-tree |
| 1194 | versions of their dependencies to ensure behavioral compatibility. |
| 1195 | |
| 1196 | #### Tip-of-tree versions {#dependencies-project} |
| 1197 | |
| 1198 | Below is an example of a non-pinned dependency. It ties the artifact's release |
| 1199 | schedule to that of the dependency artifact, because the dependency will need to |
| 1200 | be released at the same time. |
| 1201 | |
| 1202 | ``` |
| 1203 | dependencies { |
| 1204 | api(project(":collection")) |
| 1205 | ... |
| 1206 | } |
| 1207 | ``` |
| 1208 | |
| 1209 | ### Non-public APIs {#dependencies-non-public-apis} |
| 1210 | |
| 1211 | Artifacts may depend on non-public (e.g. `@hide`) APIs exposed within their own |
| 1212 | artifact or another artifact in the same `groupId`; however, cross-artifact |
| 1213 | usages are subject to binary compatibility guarantees and |
| 1214 | `@RestrictTo(Scope.LIBRARY_GROUP)` APIs must be tracked like public APIs. |
| 1215 | |
| 1216 | ``` |
| 1217 | Dependency versioning policies are enforced at build time in the createArchive task. This task will ensure that pre-release version suffixes are propagated appropriately. |
| 1218 | |
| 1219 | Cross-artifact API usage policies are enforced by the checkApi and checkApiRelease tasks (see Life of a release). |
| 1220 | ``` |
| 1221 | |
| 1222 | ### Third-party libraries {#dependencies-3p} |
| 1223 | |
| 1224 | Artifacts may depend on libraries developed outside of AndroidX; however, they |
| 1225 | must conform to the following guidelines: |
| 1226 | |
| 1227 | * Prebuilt **must** be checked into Android Git with both Maven and Make |
| 1228 | artifacts |
| 1229 | * `prebuilts/maven_repo` is recommended if this dependency is only |
| 1230 | intended for use with AndroidX artifacts, otherwise please use |
| 1231 | `external` |
| 1232 | * Prebuilt directory **must** contains an `OWNERS` file identifying one or |
| 1233 | more individual owners (e.g. NOT a group alias) |
| 1234 | * Library **must** be approved by legal |
| 1235 | |
| 1236 | Please see Jetpack's [open-source policy page](open_source.md) for more details |
| 1237 | on using third-party libraries. |
| 1238 | |
alanv | a9915e8 | 2021-09-10 15:21:02 -0700 | [diff] [blame] | 1239 | ### Types of dependencies {#dependencies-types} |
| 1240 | |
| 1241 | AndroidX allows dependencies to be specified as `api` or `implementation` with a |
| 1242 | "pinned" Maven spec (ex. `androidx.core:core:1.0.0`) or a "tip-of-tree" project |
| 1243 | spec (ex. `project(":core:core")`). |
| 1244 | |
| 1245 | Projects used in Playground, the experimental GitHub workflow, should use a |
| 1246 | "recent" project or artifact spec (ex. `projectOrArtifact(":core:core")`) which |
| 1247 | will default to tip-of-tree when used outside of the Playground workflow or a |
| 1248 | pinned `SNAPSHOT` artifact otherwise. |
| 1249 | |
| 1250 | Regardless of which dependency spec is used, all projects are built against |
| 1251 | tip-of-tree dependencies in CI to prevent regressions and enforce Jetpack's |
| 1252 | compatible-at-head policy. |
| 1253 | |
| 1254 | #### `api` versus `implementation` {#dependencies-api-vs-impl} |
| 1255 | |
| 1256 | `api`-type dependencies will appear in clients' auto-complete as though they had |
| 1257 | added the dependency directly to their project, and Studio will run any lint |
| 1258 | checks bundled with `api`-type dependencies. |
| 1259 | |
| 1260 | Dependencies whose APIs are exposed in a library's API surface **must** be |
| 1261 | included as `api`-type. For example, if your library's API surface includes |
| 1262 | `AccessibilityNodeInfoCompat` then you will use an `api`-type dependency on the |
| 1263 | `androidx.core:core` library. |
| 1264 | |
| 1265 | NOTE Libraries that provide client-facing lint checks, including |
| 1266 | `annotation-experimental`, **must** be included as `api`-type to ensure that |
| 1267 | lint checks are run in the clients' dependent projects. |
| 1268 | |
| 1269 | `implementation`-type dependencies will be included in the classpath, but will |
| 1270 | not be made available at design time (ex. in auto-complete) unless the client |
| 1271 | explicitly adds them. |
| 1272 | |
AndroidX Core Team | ee1457a | 2021-02-25 16:13:10 +0000 | [diff] [blame] | 1273 | ### System health {#dependencies-health} |
| 1274 | |
alanv | a9915e8 | 2021-09-10 15:21:02 -0700 | [diff] [blame] | 1275 | Generally, Jetpack libraries should avoid dependencies that negatively impact |
| 1276 | developers without providing substantial benefit. Libraries should consider the |
| 1277 | system health implications of their dependencies, including: |
AndroidX Core Team | ee1457a | 2021-02-25 16:13:10 +0000 | [diff] [blame] | 1278 | |
| 1279 | - Large dependencies where only a small portion is needed (e.g. APK bloat) |
| 1280 | - Dependencies that slow down build times through annotation processing or |
| 1281 | compiler overhead |
| 1282 | |
| 1283 | #### Kotlin {#dependencies-kotlin} |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1284 | |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 1285 | Kotlin is *strongly recommended* for new libraries; however, it's important to |
AndroidX Core Team | 4320124 | 2021-01-26 21:38:01 +0000 | [diff] [blame] | 1286 | consider its size impact on clients. Currently, the Kotlin stdlib adds a minimum |
| 1287 | of 40kB post-optimization. It may not make sense to use Kotlin for a library |
| 1288 | that targets Java-only clients or space-constrained (ex. Android Go) clients. |
| 1289 | |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 1290 | Existing Java-based libraries are *strongly discouraged* from using Kotlin, |
AndroidX Core Team | 4320124 | 2021-01-26 21:38:01 +0000 | [diff] [blame] | 1291 | primarily because our documentation system does not currently provide a |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 1292 | Java-facing version of Kotlin API reference docs. Java-based libraries *may* |
AndroidX Core Team | 4320124 | 2021-01-26 21:38:01 +0000 | [diff] [blame] | 1293 | migrate to Kotlin, but they must consider the docs usability and size impacts on |
| 1294 | existing Java-only and space-constrained clients. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1295 | |
AndroidX Core Team | ee1457a | 2021-02-25 16:13:10 +0000 | [diff] [blame] | 1296 | #### Kotlin coroutines {#dependencies-coroutines} |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1297 | |
| 1298 | Kotlin's coroutine library adds around 100kB post-shrinking. New libraries that |
| 1299 | are written in Kotlin should prefer coroutines over `ListenableFuture`, but |
| 1300 | existing libraries must consider the size impact on their clients. See |
| 1301 | [Asynchronous work with return values](#async-return) for more details on using |
| 1302 | Kotlin coroutines in Jetpack libraries. |
| 1303 | |
AndroidX Core Team | ee1457a | 2021-02-25 16:13:10 +0000 | [diff] [blame] | 1304 | #### Guava {#dependencies-guava} |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1305 | |
| 1306 | The full Guava library is very large and *must not* be used. Libraries that |
| 1307 | would like to depend on Guava's `ListenableFuture` may instead depend on the |
| 1308 | standalone `com.google.guava:listenablefuture` artifact. See |
| 1309 | [Asynchronous work with return values](#async-return) for more details on using |
| 1310 | `ListenableFuture` in Jetpack libraries. |
| 1311 | |
AndroidX Core Team | ee1457a | 2021-02-25 16:13:10 +0000 | [diff] [blame] | 1312 | #### Java 8 {#dependencies-java8} |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1313 | |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 1314 | Libraries that take a dependency on a library targeting Java 8 must *also* |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1315 | target Java 8, which will incur a ~5% build performance (as of 8/2019) hit for |
AndroidX Core Team | 4074b46 | 2021-06-01 06:53:40 -0700 | [diff] [blame] | 1316 | clients. New libraries targeting Java 8 may use Java 8 dependencies. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1317 | |
| 1318 | The default language level for `androidx` libraries is Java 8, and we encourage |
| 1319 | libraries to stay on Java 8. However, if you have a business need to target Java |
| 1320 | 7, you can specify Java 7 in your `build.gradle` as follows: |
| 1321 | |
alanv | 93da5db | 2021-08-11 13:05:51 -0700 | [diff] [blame] | 1322 | ```groovy |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1323 | android { |
| 1324 | compileOptions { |
| 1325 | sourceCompatibility = JavaVersion.VERSION_1_7 |
| 1326 | targetCompatibility = JavaVersion.VERSION_1_7 |
| 1327 | } |
| 1328 | } |
| 1329 | ``` |
| 1330 | |
alanv | ca08330 | 2021-08-19 10:30:02 -0700 | [diff] [blame] | 1331 | #### Protobuf {#dependencies-protobuf} |
| 1332 | |
| 1333 | [Protocol buffers](https://developers.google.com/protocol-buffers) provide a |
| 1334 | language- and platform-neutral mechanism for serializing structured data. The |
| 1335 | implementation enables developers to maintain protocol compatibility across |
| 1336 | library versions, meaning that two clients can communicate regardless of the |
| 1337 | library versions included in their APKs. |
| 1338 | |
| 1339 | The Protobuf library itself, however, does not guarantee ABI compatibility |
| 1340 | across minor versions and a specific version **must** be bundled with a library |
| 1341 | to avoid conflict with other dependencies used by the developer. |
| 1342 | |
| 1343 | Additionally, the Java API surface generated by the Protobuf compiler is not |
| 1344 | guaranteed to be stable and **must not** be exposed to developers. Library |
| 1345 | owners should wrap the generated API surface with well-documented public APIs |
| 1346 | that follow an appropriate language-specific paradigm for constructing data |
| 1347 | classes, e.g. the Java `Builder` pattern. |
| 1348 | |
AndroidX Core Team | ee1457a | 2021-02-25 16:13:10 +0000 | [diff] [blame] | 1349 | ### Open-source compatibility {#dependencies-aosp} |
| 1350 | |
alanv | f3ff25e | 2022-01-05 07:16:05 -0800 | [diff] [blame] | 1351 | Jetpack's [open-source](open_source.md) principle requires that libraries |
| 1352 | consider the open-source compatibility implications of their dependencies, |
| 1353 | including: |
AndroidX Core Team | ee1457a | 2021-02-25 16:13:10 +0000 | [diff] [blame] | 1354 | |
| 1355 | - Closed-source or proprietary libraries or services that may not be available |
| 1356 | on AOSP devices |
| 1357 | - Dependencies that may prevent developers from effectively isolating their |
| 1358 | tests from third-party libraries or services |
| 1359 | |
| 1360 | Primary artifacts, e.g. `workmanager`, **must not** depend on closed-source |
| 1361 | components including libraries and hard-coded references to packages, |
AndroidX Core Team | e5ea241 | 2022-05-09 15:00:33 -0700 | [diff] [blame] | 1362 | permissions, or IPC mechanisms that may only be fulfilled by closed-source |
AndroidX Core Team | ee1457a | 2021-02-25 16:13:10 +0000 | [diff] [blame] | 1363 | components. |
| 1364 | |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 1365 | Optional artifacts, e.g. `workmanager-gcm`, *may* depend on closed-source |
AndroidX Core Team | ee1457a | 2021-02-25 16:13:10 +0000 | [diff] [blame] | 1366 | components or configure a primary artifact to be backed by a closed-source |
| 1367 | component via service discovery or initialization. |
| 1368 | |
| 1369 | Some examples of safely depending on closed-source components include: |
| 1370 | |
alanv | acbb089 | 2021-09-14 07:58:29 -0700 | [diff] [blame] | 1371 | - WorkManager's GCM Network Manager integration, which uses |
alanv | f52991b | 2022-05-06 06:58:34 -0700 | [diff] [blame] | 1372 | [manifest metadata](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:work/workmanager-gcm/src/main/AndroidManifest.xml) |
AndroidX Core Team | ee1457a | 2021-02-25 16:13:10 +0000 | [diff] [blame] | 1373 | for service discovery and provides an optional artifact exposing the |
| 1374 | service. |
| 1375 | - Ads Identifier's Play Services integration, which provides a default backend |
alanv | acbb089 | 2021-09-14 07:58:29 -0700 | [diff] [blame] | 1376 | and uses |
| 1377 | [`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) |
| 1378 | as a service discovery mechanism for Play Services. |
AndroidX Core Team | ee1457a | 2021-02-25 16:13:10 +0000 | [diff] [blame] | 1379 | - Downloadable Fonts integration with Play Services, which plugs in via a |
alanv | f52991b | 2022-05-06 06:58:34 -0700 | [diff] [blame] | 1380 | [`ContentProvider`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:core/core/src/androidTest/java/androidx/core/provider/MockFontProvider.java) |
alanv | acbb089 | 2021-09-14 07:58:29 -0700 | [diff] [blame] | 1381 | as a service discovery mechanism with developer-specified |
| 1382 | [signature verification](https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts#adding-certificates) |
| 1383 | for additional security. |
AndroidX Core Team | ee1457a | 2021-02-25 16:13:10 +0000 | [diff] [blame] | 1384 | |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 1385 | Note that in all cases, the developer is not *required* to use GCM or Play |
AndroidX Core Team | ee1457a | 2021-02-25 16:13:10 +0000 | [diff] [blame] | 1386 | Services and may instead use another compatible service implementing the same |
| 1387 | publicly-defined protocols. |
| 1388 | |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1389 | ## More API guidelines {#more-api-guidelines} |
| 1390 | |
| 1391 | ### Annotations {#annotation} |
| 1392 | |
| 1393 | #### Annotation processors {#annotation-processor} |
| 1394 | |
| 1395 | Annotation processors should opt-in to incremental annotation processing to |
| 1396 | avoid triggering a full recompilation on every client source code change. See |
| 1397 | Gradle's |
| 1398 | [Incremental annotation processing](https://docs.gradle.org/current/userguide/java_plugin.html#sec:incremental_annotation_processing) |
| 1399 | documentation for information on how to opt-in. |
| 1400 | |
AndroidX Core Team | 4e1909a | 2021-10-20 15:04:04 +0000 | [diff] [blame] | 1401 | ### `@RequiresOptIn` APIs {#experimental-api} |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1402 | |
| 1403 | Jetpack libraries may choose to annotate API surfaces as unstable using either |
| 1404 | Kotlin's |
AndroidX Core Team | 4e1909a | 2021-10-20 15:04:04 +0000 | [diff] [blame] | 1405 | [`@RequiresOptIn` meta-annotation](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-requires-opt-in/) |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1406 | for APIs written in Kotlin or Jetpack's |
AndroidX Core Team | 4e1909a | 2021-10-20 15:04:04 +0000 | [diff] [blame] | 1407 | [`@RequiresOptIn` meta-annotation](https://developer.android.com/reference/kotlin/androidx/annotation/RequiresOptIn) |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1408 | for APIs written in Java. |
| 1409 | |
AndroidX Core Team | 4e1909a | 2021-10-20 15:04:04 +0000 | [diff] [blame] | 1410 | > `@RequiresOptIn` at-a-glance: |
| 1411 | > |
| 1412 | > * Use for unstable API surfaces |
| 1413 | > * Can be called by anyone |
| 1414 | > * Documented in public documentation |
| 1415 | > * Does not maintain compatibility |
| 1416 | |
| 1417 | For either annotation, API surfaces marked as opt-in are considered alpha and |
| 1418 | will be excluded from API compatibility guarantees. Due to the lack of |
| 1419 | compatibility guarantees, stable libraries *must never* call experimental APIs |
| 1420 | exposed by other libraries outside of their |
alanv | f5ca4b9 | 2021-02-10 13:07:47 -0800 | [diff] [blame] | 1421 | [same-version group](#same-version-atomic-groups) and *may not* use the `@OptIn` |
| 1422 | annotation except in the following cases: |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1423 | |
| 1424 | * A library within a same-version group *may* call an experimental API exposed |
| 1425 | by another library **within its same-version group**. In this case, API |
| 1426 | compatibility guarantees are covered under the same-version group policies |
alanv | f5ca4b9 | 2021-02-10 13:07:47 -0800 | [diff] [blame] | 1427 | and the library *may* use the `@OptIn` annotation to prevent propagation of |
| 1428 | the experimental property. **Library owners must exercise care to ensure |
| 1429 | that post-alpha APIs backed by experimental APIs actually meet the release |
| 1430 | criteria for post-alpha APIs.** |
| 1431 | * An `alpha` library may use experimental APIs from outside its same-version |
| 1432 | group. These usages must be removed when the library moves to `beta`. |
| 1433 | |
| 1434 | NOTE JetBrains's own usage of `@RequiresOptIn` in Kotlin language libraries |
| 1435 | varies and may indicate binary instability, functional instability, or simply |
| 1436 | that an API is really difficult to use. Jetpack libraries should treat instances |
| 1437 | of `@RequiresOptIn` in JetBrains libraries as indicating **binary instability** |
| 1438 | and avoid using them outside of `alpha`; however, teams are welcome to obtain |
| 1439 | written assurance from JetBrains regarding binary stability of specific APIs. |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 1440 | `@RequiresOptIn` APIs that are guaranteed to remain binary compatible *may* be |
alanv | f5ca4b9 | 2021-02-10 13:07:47 -0800 | [diff] [blame] | 1441 | used in `beta`, but usages must be removed when the library moves to `rc`. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1442 | |
AndroidX Core Team | 4e1909a | 2021-10-20 15:04:04 +0000 | [diff] [blame] | 1443 | #### When to mark an API surface as experimental |
| 1444 | |
| 1445 | *Do not* use `@RequiresOptIn` for a stable API surface that is difficult to use. |
| 1446 | It is not a substitute for a properly-designed API surface. |
| 1447 | |
| 1448 | *Do not* use `@RequiresOptIn` for an API surface that is unreliable or unstable |
| 1449 | because it is missing tests. It is not a substitute for a properly-tested API |
| 1450 | surface, and all APIs -- including those in `alpha` -- are expected to be |
| 1451 | functionally stable. |
| 1452 | |
| 1453 | *Do not* use `@RequiresOptIn` for an internal-facing API surface. Use either the |
| 1454 | appropriate language visibility (ex. `private` or `internal`) or `@RestrictTo`. |
| 1455 | |
| 1456 | *Do not* use `@RequiresOptIn` for an API that you expect library developers to |
| 1457 | call. Experimental APIs do not maintain binary compatibility guarantees, and you |
| 1458 | will put external clients in a difficult situation. |
| 1459 | |
| 1460 | *Do* use `@RequiresOptIn` for API surfaces that must be publicly available and |
| 1461 | documented but need the flexibility to stay in `alpha` (and break compatibility) |
| 1462 | during the rest of the library's `beta`, `rc`, or stable cycles. |
| 1463 | |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1464 | #### How to mark an API surface as experimental |
| 1465 | |
alanv | f5ca4b9 | 2021-02-10 13:07:47 -0800 | [diff] [blame] | 1466 | All libraries using `@RequiresOptIn` annotations *must* depend on the |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1467 | `androidx.annotation:annotation-experimental` artifact regardless of whether |
| 1468 | they are using the `androidx` or Kotlin annotation. This artifact provides Lint |
| 1469 | enforcement of experimental usage restrictions for Kotlin callers as well as |
| 1470 | Java (which the Kotlin annotation doesn't handle on its own, since it's a Kotlin |
| 1471 | compiler feature). Libraries *may* include the dependency as `api`-type to make |
alanv | f5ca4b9 | 2021-02-10 13:07:47 -0800 | [diff] [blame] | 1472 | `@OptIn` available to Java clients; however, this will also unnecessarily expose |
| 1473 | the `@RequiresOptIn` annotation. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1474 | |
| 1475 | ```java |
| 1476 | dependencies { |
| 1477 | implementation(project(":annotation:annotation-experimental")) |
| 1478 | } |
| 1479 | ``` |
| 1480 | |
| 1481 | See Kotlin's |
alanv | f5ca4b9 | 2021-02-10 13:07:47 -0800 | [diff] [blame] | 1482 | [opt-in requirements documentation](https://kotlinlang.org/docs/reference/opt-in-requirements.html) |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1483 | for general usage information. If you are writing experimental Java APIs, you |
| 1484 | will use the Jetpack |
alanv | f5ca4b9 | 2021-02-10 13:07:47 -0800 | [diff] [blame] | 1485 | [`@RequiresOptIn` annotation](https://developer.android.com/reference/kotlin/androidx/annotation/RequiresOptIn) |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1486 | rather than the Kotlin compiler's annotation. |
| 1487 | |
| 1488 | #### How to transition an API out of experimental |
| 1489 | |
| 1490 | When an API surface is ready to transition out of experimental, the annotation |
AndroidX Core Team | 4e1909a | 2021-10-20 15:04:04 +0000 | [diff] [blame] | 1491 | may only be removed during an alpha pre-release stage. Removing the experimental |
| 1492 | marker from an API is equivalent to adding the API to the current API surface. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1493 | |
| 1494 | When transitioning an entire feature surface out of experimental, you *should* |
AndroidX Core Team | 4e1909a | 2021-10-20 15:04:04 +0000 | [diff] [blame] | 1495 | remove the definition for the associated annotation. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1496 | |
| 1497 | When making any change to the experimental API surface, you *must* run |
| 1498 | `./gradlew updateApi` prior to uploading your change. |
| 1499 | |
AndroidX Core Team | 4e1909a | 2021-10-20 15:04:04 +0000 | [diff] [blame] | 1500 | ### `@RestrictTo` APIs {#restricted-api} |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1501 | |
| 1502 | Jetpack's library tooling supports hiding Java-visible (ex. `public` and |
AndroidX Core Team | ee9c1aa | 2021-04-06 17:29:05 +0000 | [diff] [blame] | 1503 | `protected`) APIs from developers using a combination of the `@RestrictTo` |
| 1504 | source annotation, and the `@hide` docs annotation (`@suppress` in Kotlin). |
| 1505 | These annotations **must** be paired together when used, and are validated as |
AndroidX Core Team | 4e1909a | 2021-10-20 15:04:04 +0000 | [diff] [blame] | 1506 | part of presubmit checks for Java code. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1507 | |
AndroidX Core Team | 4e1909a | 2021-10-20 15:04:04 +0000 | [diff] [blame] | 1508 | > `@RestrictTo` at-a-glance: |
| 1509 | > |
| 1510 | > * Use for internal-facing API surfaces |
| 1511 | > * Can be called within the specified `Scope` |
| 1512 | > * Does not appear in public documentation |
| 1513 | > * Does not maintain compatibility in most scopes |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1514 | |
AndroidX Core Team | 4e1909a | 2021-10-20 15:04:04 +0000 | [diff] [blame] | 1515 | While restricted APIs do not appear in documentation and Android Studio will |
| 1516 | warn against calling them, hiding an API does *not* provide strong guarantees |
| 1517 | about usage: |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1518 | |
| 1519 | * There are no runtime restrictions on calling hidden APIs |
| 1520 | * Android Studio will not warn if hidden APIs are called using reflection |
| 1521 | * Hidden APIs will still show in Android Studio's auto-complete |
| 1522 | |
| 1523 | #### When to use `@hide` {#restricted-api-usage} |
| 1524 | |
AndroidX Core Team | ee9c1aa | 2021-04-06 17:29:05 +0000 | [diff] [blame] | 1525 | In other cases, avoid using `@hide` / `@suppress`. These annotations indicates |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 1526 | that developers should not call an API that is *technically* public from a Java |
AndroidX Core Team | ee9c1aa | 2021-04-06 17:29:05 +0000 | [diff] [blame] | 1527 | visibility perspective. Hiding APIs is often a sign of a poorly-abstracted API |
| 1528 | surface, and priority should be given to creating public, maintainable APIs and |
| 1529 | using Java visibility modifiers. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1530 | |
AndroidX Core Team | ee9c1aa | 2021-04-06 17:29:05 +0000 | [diff] [blame] | 1531 | *Do not* use `@hide`/`@suppress` to bypass API tracking and review for |
| 1532 | production APIs; instead, rely on API+1 and API Council review to ensure APIs |
| 1533 | are reviewed on a timely basis. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1534 | |
AndroidX Core Team | ee9c1aa | 2021-04-06 17:29:05 +0000 | [diff] [blame] | 1535 | *Do not* use `@hide`/`@suppress` for implementation detail APIs that are used |
| 1536 | between libraries and could reasonably be made public. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1537 | |
AndroidX Core Team | ee9c1aa | 2021-04-06 17:29:05 +0000 | [diff] [blame] | 1538 | *Do* use `@hide`/`@suppress` paired with `@RestrictTo(LIBRARY)` for |
| 1539 | implementation detail APIs used within a single library (but prefer Java |
| 1540 | language `private` or `default` visibility). |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1541 | |
| 1542 | #### `RestrictTo.Scope` and inter- versus intra-library API surfaces {#private-api-types} |
| 1543 | |
| 1544 | To maintain binary compatibility between different versions of libraries, |
alanv | 3e72207 | 2021-09-10 09:39:35 -0700 | [diff] [blame] | 1545 | restricted API surfaces that are used between libraries within Jetpack |
| 1546 | (inter-library APIs) must follow the same Semantic Versioning rules as public |
| 1547 | APIs. Inter-library APIs should be annotated with the |
| 1548 | `@RestrictTo(LIBRARY_GROUP)` source annotation and `@hide` docs annotation. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1549 | |
| 1550 | Restricted API surfaces used within a single library (intra-library APIs), on |
| 1551 | the other hand, may be added or removed without any compatibility |
alanv | 3e72207 | 2021-09-10 09:39:35 -0700 | [diff] [blame] | 1552 | considerations. It is safe to assume that developers *never* call these APIs, |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1553 | even though it is technically feasible. Intra-library APIs should be annotated |
alanv | 3e72207 | 2021-09-10 09:39:35 -0700 | [diff] [blame] | 1554 | with the `@RestrictTo(LIBRARY)` source annotation and `@hide` docs annotation. |
| 1555 | |
| 1556 | In all cases, correctness and compatibility tracking are handled by AndroidX's |
| 1557 | build system and lint checks. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1558 | |
| 1559 | The following table shows the visibility of a hypothetical API within Maven |
| 1560 | coordinate `androidx.concurrent:concurrent` when annotated with a variety of |
| 1561 | scopes: |
| 1562 | |
| 1563 | <table> |
| 1564 | <tr> |
| 1565 | <td><code>RestrictTo.Scope</code></td> |
| 1566 | <td>Visibility by Maven coordinate</td> |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 1567 | <td>Versioning</td> |
AndroidX Core Team | 4e1909a | 2021-10-20 15:04:04 +0000 | [diff] [blame] | 1568 | <td>Note</td> |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1569 | </tr> |
| 1570 | <tr> |
| 1571 | <td><code>LIBRARY</code></td> |
| 1572 | <td><code>androidx.concurrent:concurrent</code></td> |
AndroidX Core Team | 4e1909a | 2021-10-20 15:04:04 +0000 | [diff] [blame] | 1573 | <td>No compatibility guarantees (same as private)</td> |
| 1574 | <td></td> |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1575 | </tr> |
| 1576 | <tr> |
| 1577 | <td><code>LIBRARY_GROUP</code></td> |
| 1578 | <td><code>androidx.concurrent:*</code></td> |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 1579 | <td>Semantic versioning (including deprecation)</td> |
AndroidX Core Team | 4e1909a | 2021-10-20 15:04:04 +0000 | [diff] [blame] | 1580 | <td></td> |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1581 | </tr> |
| 1582 | <tr> |
| 1583 | <td><code>LIBRARY_GROUP_PREFIX</code></td> |
| 1584 | <td><code>androidx.*:*</code></td> |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 1585 | <td>Semantic versioning (including deprecation)</td> |
AndroidX Core Team | 4e1909a | 2021-10-20 15:04:04 +0000 | [diff] [blame] | 1586 | <td></td> |
| 1587 | </tr> |
| 1588 | <tr> |
| 1589 | <td><code>TEST</code></td> |
| 1590 | <td><code>*</code></td> |
| 1591 | <td>No compatibility guarantees (same as private)</td> |
| 1592 | <td>Not recommended. Prefer language visibility, e.g. `internal` or package-private.</td> |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1593 | </tr> |
| 1594 | </table> |
| 1595 | |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 1596 | #### `@IntDef` `@StringDef` and `@LongDef` and visibility |
| 1597 | |
| 1598 | All `@IntDef`, `@StringDef`, and `@LongDef` will be stripped from resulting |
| 1599 | artifacts to avoid issues where compiler inlining constants removes information |
| 1600 | as to which `@IntDef` defined the value of `1`. The annotations are extracted |
| 1601 | and packaged separately to be read by Android Studio and lint which enforces the |
| 1602 | types in application code. |
| 1603 | |
AndroidX Core Team | 26cc3c7 | 2021-09-08 01:05:14 -0700 | [diff] [blame] | 1604 | * Libraries *must* `@hide` all `@IntDef`, `@StringDef`, and `@LongDef` |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 1605 | declarations. |
AndroidX Core Team | 26cc3c7 | 2021-09-08 01:05:14 -0700 | [diff] [blame] | 1606 | * Libraries *must* expose constants used to define the `@IntDef` etc at the |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 1607 | same Java visibility as the hidden `@IntDef` |
AndroidX Core Team | 26cc3c7 | 2021-09-08 01:05:14 -0700 | [diff] [blame] | 1608 | * Libraries *must* use `@RestrictTo` to create a warning when the type is used |
| 1609 | incorrectly. |
AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 1610 | |
| 1611 | Here is a complete example of an `@IntDef` |
| 1612 | |
| 1613 | ```java |
| 1614 | // constants match Java visibility of ExifStreamType |
| 1615 | // code outside this module interacting with ExifStreamType uses these constants |
| 1616 | public static final int STREAM_TYPE_FULL_IMAGE_DATA = 1; |
| 1617 | public static final int STREAM_TYPE_EXIF_DATA_ONLY = 2; |
| 1618 | |
| 1619 | /** @hide */ |
| 1620 | @RestrictTo(RestrictTo.Scope.LIBRARY) // Don't export ExifStreamType outside module |
| 1621 | @Retention(RetentionPolicy.SOURCE) |
| 1622 | @IntDef({ |
| 1623 | STREAM_TYPE_FULL_IMAGE_DATA, |
| 1624 | STREAM_TYPE_EXIF_DATA_ONLY, |
| 1625 | }) |
| 1626 | public @interface ExifStreamType {} |
| 1627 | ``` |
| 1628 | |
| 1629 | Java visibilty should be set as appropriate for the code in question (`private`, |
| 1630 | `package` or `public`) and is unrelated to hiding. |
| 1631 | |
| 1632 | For more, read the section in |
| 1633 | [Android API Council Guidelines](https://android.googlesource.com/platform/developers/docs/+/refs/heads/master/api-guidelines/index.md#no-public-typedefs) |
| 1634 | |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1635 | ### Constructors {#constructors} |
| 1636 | |
| 1637 | #### View constructors {#view-constructors} |
| 1638 | |
| 1639 | The four-arg View constructor -- `View(Context, AttributeSet, int, int)` -- was |
| 1640 | added in SDK 21 and allows a developer to pass in an explicit default style |
| 1641 | resource rather than relying on a theme attribute to resolve the default style |
| 1642 | resource. Because this API was added in SDK 21, care must be taken to ensure |
| 1643 | that it is not called through any < SDK 21 code path. |
| 1644 | |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 1645 | Views *may* implement a four-arg constructor in one of the following ways: |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1646 | |
| 1647 | 1. Do not implement. |
| 1648 | 1. Implement and annotate with `@RequiresApi(21)`. This means the three-arg |
| 1649 | constructor **must not** call into the four-arg constructor. |
| 1650 | |
| 1651 | ### Asynchronous work {#async} |
| 1652 | |
| 1653 | #### With return values {#async-return} |
| 1654 | |
AndroidX Core Team | 21ccf65 | 2022-04-01 14:53:07 +0000 | [diff] [blame] | 1655 | ###### Kotlin |
| 1656 | |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1657 | Traditionally, asynchronous work on Android that results in an output value |
| 1658 | would use a callback; however, better alternatives exist for libraries. |
| 1659 | |
AndroidX Core Team | 21ccf65 | 2022-04-01 14:53:07 +0000 | [diff] [blame] | 1660 | Kotlin libraries should consider |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1661 | [coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html) and |
AndroidX Core Team | 21ccf65 | 2022-04-01 14:53:07 +0000 | [diff] [blame] | 1662 | `suspend` functions for APIs according to the following rules, but please refer |
| 1663 | to the guidance on [allowable dependencies](#dependencies-coroutines) before |
| 1664 | adding a new dependency on coroutines. |
| 1665 | |
| 1666 | Kotlin suspend fun vs blocking | Behavior |
| 1667 | ------------------------------------ | -------------------------- |
| 1668 | blocking function with @WorkerThread | API is blocking |
| 1669 | suspend | API is async (e.g. Future) |
| 1670 | |
| 1671 | In general, do not introduce a suspend function entirely to switch threads for |
| 1672 | blocking calls. To do so correctly requires that we allow the developer to |
| 1673 | configure the Dispatcher. As there is already a coroutines-based API for |
| 1674 | changing dispatchers (withContext) that the caller may use to switch threads, it |
| 1675 | is unecessary API overhead to provide a duplicate mechanism. In addition, it |
| 1676 | unecessary limits callers to coroutine contexts. |
| 1677 | |
| 1678 | ```kotlin |
| 1679 | // DO expose blocking calls as blocking calls |
| 1680 | @WorkerThread |
| 1681 | fun blockingCall() |
| 1682 | |
| 1683 | // DON'T wrap in suspend functions (only to switch threads) |
| 1684 | suspend fun blockingCallWrappedInSuspend( |
| 1685 | dispatcher: CoroutineDispatcher = Dispatchers.Default |
| 1686 | ) = withContext(dispatcher) { /* ... */ } |
| 1687 | |
| 1688 | // DO expose async calls as suspend funs |
| 1689 | suspend fun asyncCall(): ReturnValue |
| 1690 | |
| 1691 | // DON'T expose async calls as a callback-based API (for the main API) |
| 1692 | fun asyncCall(executor: Executor, callback: (ReturnValue) -> Unit) |
| 1693 | ``` |
| 1694 | |
| 1695 | ###### Java |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1696 | |
| 1697 | Java libraries should prefer `ListenableFuture` and the |
| 1698 | [`CallbackToFutureAdapter`](https://developer.android.com/reference/androidx/concurrent/futures/CallbackToFutureAdapter) |
| 1699 | implementation provided by the `androidx.concurrent:concurrent-futures` library. |
AndroidX Core Team | 673068f | 2021-09-09 11:06:18 -0700 | [diff] [blame] | 1700 | Functions and methods that return `ListenableFuture` should be suffixed by, |
| 1701 | `Async` to reserve the shorter, unmodified name for a `suspend` method or |
| 1702 | extension function in Kotlin that returns the value normally in accordance with |
| 1703 | structured concurrency. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1704 | |
| 1705 | Libraries **must not** use `java.util.concurrent.CompletableFuture`, as it has a |
| 1706 | large API surface that permits arbitrary mutation of the future's value and has |
| 1707 | error-prone defaults. |
| 1708 | |
| 1709 | See the [Dependencies](#dependencies) section for more information on using |
| 1710 | Kotlin coroutines and Guava in your library. |
| 1711 | |
AndroidX Core Team | ee1457a | 2021-02-25 16:13:10 +0000 | [diff] [blame] | 1712 | #### Cancellation |
| 1713 | |
| 1714 | Libraries that expose APIs for performing asynchronous work should support |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 1715 | cancellation. There are *very few* cases where it is not feasible to support |
AndroidX Core Team | ee1457a | 2021-02-25 16:13:10 +0000 | [diff] [blame] | 1716 | cancellation. |
| 1717 | |
| 1718 | Libraries that use `ListenableFuture` must be careful to follow the exact |
| 1719 | specification of |
| 1720 | [`Future.cancel(boolean mayInterruptIfRunning)`](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html?is-external=true#cancel-boolean-) |
| 1721 | behavior. |
| 1722 | |
| 1723 | ```java {.bad} |
| 1724 | @Override |
| 1725 | public boolean cancel(boolean mayInterruptIfRunning) { |
| 1726 | // Does not support cancellation. |
| 1727 | return false; |
| 1728 | } |
| 1729 | ``` |
| 1730 | |
| 1731 | ```java {.bad} |
| 1732 | @Override |
| 1733 | public boolean cancel(boolean mayInterruptIfRunning) { |
| 1734 | // Aggressively does not support cancellation. |
| 1735 | throw new UnsupportedOperationException(); |
| 1736 | } |
| 1737 | ``` |
| 1738 | |
| 1739 | ```java {.good} |
| 1740 | @Override |
| 1741 | public boolean cancel(boolean mayInterruptIfRunning) { |
| 1742 | // Pseudocode that ignores threading but follows the spec. |
| 1743 | if (mCompleted |
| 1744 | || mCancelled |
| 1745 | || mRunning && !mayInterruptIfRunning) { |
| 1746 | return false; |
| 1747 | } |
| 1748 | mCancelled = true; |
| 1749 | return true; |
| 1750 | } |
| 1751 | ``` |
| 1752 | |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1753 | #### Avoid `synchronized` methods |
| 1754 | |
| 1755 | Whenever multiple threads are interacting with shared (mutable) references those |
| 1756 | reads and writes must be synchronized in some way. However synchronized blocks |
| 1757 | make your code thread-safe at the expense of concurrent execution. Any time |
| 1758 | execution enters a synchronized block or method any other thread trying to enter |
| 1759 | a synchronized block on the same object has to wait; even if in practice the |
| 1760 | operations are unrelated (e.g. they interact with different fields). This can |
| 1761 | dramatically reduce the benefit of trying to write multi-threaded code in the |
| 1762 | first place. |
| 1763 | |
| 1764 | Locking with synchronized is a heavyweight form of ensuring ordering between |
| 1765 | threads, and there are a number of common APIs and patterns that you can use |
| 1766 | that are more lightweight, depending on your use case: |
| 1767 | |
| 1768 | * Compute a value once and make it available to all threads |
| 1769 | * Update Set and Map data structures across threads |
| 1770 | * Allow a group of threads to process a stream of data concurrently |
| 1771 | * Provide instances of a non-thread-safe type to multiple threads |
| 1772 | * Update a value from multiple threads atomically |
| 1773 | * Maintain granular control of your concurrency invariants |
| 1774 | |
alanv | f89295e | 2022-05-10 08:08:55 -0700 | [diff] [blame] | 1775 | ### Kotlin-specific guidelines {#kotlin} |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1776 | |
AndroidX Core Team | ee9c1aa | 2021-04-06 17:29:05 +0000 | [diff] [blame] | 1777 | #### Nullability from Java (new APIs) |
| 1778 | |
| 1779 | All new Java APIs should be annotated either `@Nullable` or `@NonNull` for all |
| 1780 | reference parameters and reference return types. |
| 1781 | |
| 1782 | ```java |
| 1783 | @Nullable |
| 1784 | public Object someNewApi(@NonNull Thing arg1, @Nullable List<WhatsIt> arg2) { |
| 1785 | if(/** something **/) { |
| 1786 | return someObject; |
| 1787 | } else { |
| 1788 | return null; |
| 1789 | } |
| 1790 | ``` |
| 1791 | |
| 1792 | #### Nullability from Java (existing APIs) |
| 1793 | |
| 1794 | Adding `@Nullable` or `@NonNull` annotations to existing APIs to document their |
| 1795 | existing nullability is OK. This is a source breaking change for Kotlin |
| 1796 | consumers, and you should ensure that it's noted in the release notes and try to |
| 1797 | minimize the frequency of these updates in releases. |
| 1798 | |
| 1799 | Changing the nullability of an API is a breaking change. |
| 1800 | |
| 1801 | #### Extending APIs that expose types without nullability annotations |
| 1802 | |
| 1803 | [Platform types](https://kotlinlang.org/docs/java-interop.html#null-safety-and-platform-types) |
| 1804 | are exposed by Java types that do not have a `@Nullable` or `@NonNull` |
| 1805 | annotation. In Kotlin they are indicated with the `!` suffix. |
| 1806 | |
| 1807 | When interacting with an Android platform API that exposes APIs with unknown |
| 1808 | nullability follow these rules: |
| 1809 | |
| 1810 | 1. If wrapping the type in a new API, define and handle `@Nullable` or |
| 1811 | `@NonNull` in the library. Treat types with unknown nullability passed into |
| 1812 | or return from Android as `@Nullable` in the library. |
| 1813 | 2. If extending an existing API (e.g. `@Override`), pass through the existing |
| 1814 | types with unknown nullability and annotate each with |
| 1815 | `@SuppressLint("UnknownNullness")` |
| 1816 | |
| 1817 | In Kotlin, a type with unknown nullability is exposed as a "platform type" |
| 1818 | (indicated with a `!` suffix) which has unknown nullability in the type checker, |
| 1819 | and may bypass type checking leading to runtime errors. When possible, do not |
| 1820 | directly expose types with unknown nullability in new public APIs. |
| 1821 | |
| 1822 | #### Extending `@RecentlyNonNull` and `@RecentlyNullable` APIs |
| 1823 | |
| 1824 | Platform APIs are annotated in the platform SDK artifacts with fake annotations |
| 1825 | `@RecentlyNonNull` and `@RecentlyNullable` to avoid breaking builds when we |
| 1826 | annotated platform APIs with nullability. These annotations cause warnings |
| 1827 | instead of build failures. The `RecentlyNonNull` and `RecentlyNullable` |
| 1828 | annotations are added by Metalava and do not appear in platform code. |
| 1829 | |
| 1830 | When extending an API that is annotated `@RecentlyNonNull`, you should annotate |
| 1831 | the override with `@NonNull`, and the same for `@RecentlyNullable` and |
| 1832 | `@Nullable`. |
| 1833 | |
| 1834 | For example `SpannableStringBuilder.append` is annotated `RecentlyNonNull` and |
| 1835 | an override should look like: |
| 1836 | |
| 1837 | ```java |
| 1838 | @NonNull |
| 1839 | @Override |
| 1840 | public SpannableStringBuilder append(@SuppressLint("UnknownNullness") CharSequence text) { |
| 1841 | super.append(text); |
| 1842 | return this; |
| 1843 | } |
| 1844 | ``` |
| 1845 | |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1846 | #### Data classes {#kotlin-data} |
| 1847 | |
| 1848 | Kotlin `data` classes provide a convenient way to define simple container |
| 1849 | objects, where Kotlin will generate `equals()` and `hashCode()` for you. |
| 1850 | However, they are not designed to preserve API/binary compatibility when members |
| 1851 | are added. This is due to other methods which are generated for you - |
| 1852 | [destructuring declarations](https://kotlinlang.org/docs/reference/multi-declarations.html), |
| 1853 | and [copying](https://kotlinlang.org/docs/reference/data-classes.html#copying). |
| 1854 | |
| 1855 | Example data class as tracked by metalava: |
| 1856 | |
| 1857 | <pre> |
| 1858 | public final class TargetAnimation { |
| 1859 | ctor public TargetAnimation(float target, androidx.animation.AnimationBuilder animation); |
| 1860 | <b>method public float component1();</b> |
| 1861 | <b>method public androidx.animation.AnimationBuilder component2();</b> |
| 1862 | <b>method public androidx.animation.TargetAnimation copy(float target, androidx.animation.AnimationBuilder animation);</b> |
| 1863 | method public androidx.animation.AnimationBuilder getAnimation(); |
| 1864 | method public float getTarget(); |
| 1865 | } |
| 1866 | </pre> |
| 1867 | |
| 1868 | Because members are exposed as numbered components for destructuring, you can |
| 1869 | only safely add members at the end of the member list. As `copy` is generated |
| 1870 | with every member name in order as well, you'll also have to manually |
| 1871 | re-implement any old `copy` variants as items are added. If these constraints |
| 1872 | are acceptable, data classes may still be useful to you. |
| 1873 | |
alanv | 1d2bee5 | 2021-09-13 08:49:27 -0700 | [diff] [blame] | 1874 | As a result, Kotlin `data` classes are *strongly discouraged* in library APIs. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 1875 | Instead, follow best-practices for Java data classes including implementing |
| 1876 | `equals`, `hashCode`, and `toString`. |
| 1877 | |
| 1878 | See Jake Wharton's article on |
| 1879 | [Public API challenges in Kotlin](https://jakewharton.com/public-api-challenges-in-kotlin/) |
| 1880 | for more details. |
| 1881 | |
AndroidX Core Team | 6fd727f | 2021-06-01 12:26:34 -0700 | [diff] [blame] | 1882 | #### Exhaustive `when` and `sealed class`/`enum class` {#exhaustive-when} |
| 1883 | |
| 1884 | A key feature of Kotlin's `sealed class` and `enum class` declarations is that |
| 1885 | they permit the use of **exhaustive `when` expressions.** For example: |
| 1886 | |
| 1887 | ```kotlin |
| 1888 | enum class CommandResult { Permitted, DeniedByUser } |
| 1889 | |
| 1890 | val message = when (commandResult) { |
| 1891 | Permitted -> "the operation was permitted" |
| 1892 | DeniedByUser -> "the user said no" |
| 1893 | } |
| 1894 | |
| 1895 | println(message) |
| 1896 | ``` |
| 1897 | |
| 1898 | This highlights challenges for library API design and compatibility. Consider |
| 1899 | the following addition to the `CommandResult` possibilities: |
| 1900 | |
| 1901 | ```kotlin {.bad} |
| 1902 | enum class CommandResult { |
| 1903 | Permitted, |
| 1904 | DeniedByUser, |
| 1905 | DeniedByAdmin // New in androidx.mylibrary:1.1.0! |
| 1906 | } |
| 1907 | ``` |
| 1908 | |
| 1909 | This change is both **source and binary breaking.** |
| 1910 | |
| 1911 | It is **source breaking** because the author of the `when` block above will see |
| 1912 | a compiler error about not handling the new result value. |
| 1913 | |
| 1914 | It is **binary breaking** because if the `when` block above was compiled as part |
| 1915 | of a library `com.example.library:1.0.0` that transitively depends on |
| 1916 | `androidx.mylibrary:1.0.0`, and an app declares the dependencies: |
| 1917 | |
| 1918 | ```kotlin |
| 1919 | implementation("com.example.library:1.0.0") |
| 1920 | implementation("androidx.mylibrary:1.1.0") // Updated! |
| 1921 | ``` |
| 1922 | |
| 1923 | `com.example.library:1.0.0` does not handle the new result value, leading to a |
| 1924 | runtime exception. |
| 1925 | |
| 1926 | **Note:** The above example is one where Kotlin's `enum class` is the correct |
| 1927 | tool and the library should **not** add a new constant! Kotlin turns this |
| 1928 | semantic API design problem into a compiler or runtime error. This type of |
| 1929 | library API change could silently cause app logic errors or data corruption |
| 1930 | without the protection provided by exhaustive `when`. See |
| 1931 | [When to use exhaustive types](#when-to-use-exhaustive-types). |
| 1932 | |
| 1933 | `sealed class` exhibits the same characteristic; adding a new subtype of an |
| 1934 | existing sealed class is a breaking change for the following code: |
| 1935 | |
| 1936 | ```kotlin |
| 1937 | val message = when (command) { |
| 1938 | is Command.Migrate -> "migrating to ${command.destination}" |
| 1939 | is Command.Quack -> "quack!" |
| 1940 | } |
| 1941 | ``` |
| 1942 | |
| 1943 | ##### Non-exhaustive alternatives to `enum class` |
| 1944 | |
| 1945 | Kotlin's `@JvmInline value class` with a `private constructor` can be used to |
| 1946 | create type-safe sets of non-exhaustive constants as of Kotlin 1.5. Compose's |
| 1947 | `BlendMode` uses the following pattern: |
| 1948 | |
| 1949 | ```kotlin {.good} |
| 1950 | @JvmInline |
| 1951 | value class BlendMode private constructor(val value: Int) { |
| 1952 | companion object { |
| 1953 | /** Drop both the source and destination images, leaving nothing. */ |
| 1954 | val Clear = BlendMode(0) |
| 1955 | /** Drop the destination image, only paint the source image. */ |
| 1956 | val Src = BlendMode(1) |
| 1957 | // ... |
| 1958 | } |
| 1959 | } |
| 1960 | ``` |
| 1961 | |
| 1962 | **Note:** This recommendation may be temporary. Kotlin may add new annotations |
| 1963 | or other language features to declare non-exhaustive enum classes in the future. |
| 1964 | |
| 1965 | Alternatively, the existing `@IntDef` mechanism used in Java-language androidx |
| 1966 | libraries may also be used, but type checking of constants will only be |
| 1967 | performed by lint, and functions overloaded with parameters of different value |
| 1968 | class types are not supported. Prefer the `@JvmInline value class` solution for |
| 1969 | new code unless it would break local consistency with other API in the same |
| 1970 | module that already uses `@IntDef`. |
| 1971 | |
| 1972 | ##### Non-exhaustive alternatives to `sealed class` |
| 1973 | |
| 1974 | Abstract classes with constructors marked as `internal` or `private` can |
| 1975 | represent the same subclassing restrictions of sealed classes as seen from |
| 1976 | outside of a library module's own codebase: |
| 1977 | |
| 1978 | ```kotlin |
| 1979 | abstract class Command private constructor() { |
| 1980 | class Migrate(val destination: String) : Command() |
| 1981 | object Quack : Command() |
| 1982 | } |
| 1983 | ``` |
| 1984 | |
| 1985 | Using an `internal` constructor will permit non-nested subclasses, but will |
| 1986 | **not** restrict subclasses to the same package within the module, as sealed |
| 1987 | classes do. |
| 1988 | |
| 1989 | ##### When to use exhaustive types |
| 1990 | |
| 1991 | Use `enum class` or `sealed class` when the values or subtypes are intended to |
| 1992 | be exhaustive by design from the API's initial release. Use non-exhaustive |
| 1993 | alternatives when the set of constants or subtypes might expand in a minor |
| 1994 | version release. |
| 1995 | |
| 1996 | Consider using an **exhaustive** (`enum class` or `sealed class`) type |
| 1997 | declaration if: |
| 1998 | |
| 1999 | * The developer is expected to **accept** values of the type |
| 2000 | * The developer is expected to **act** on **any and all** values received |
| 2001 | |
| 2002 | Consider using a **non-exhaustive** type declaration if: |
| 2003 | |
| 2004 | * The developer is expected to **provide** values of the type to APIs exposed |
| 2005 | by the same module **only** |
| 2006 | * The developer is expected to **ignore** unknown values received |
| 2007 | |
| 2008 | The `CommandResult` example above is a good example of a type that **should** |
| 2009 | use the exhaustive `enum class`; `CommandResult`s are **returned** to the |
| 2010 | developer and the developer cannot implement correct app behavior by ignoring |
| 2011 | unrecognized result values. Adding a new result value would semantically break |
| 2012 | existing code regardless of the language facility used to express the type. |
| 2013 | |
| 2014 | ```kotlin {.good} |
| 2015 | enum class CommandResult { Permitted, DeniedByUser, DeniedByAdmin } |
| 2016 | ``` |
| 2017 | |
| 2018 | Compose's `BlendMode` is a good example of a type that **should not** use the |
| 2019 | exhaustive `enum class`; blending modes are used as arguments to Compose |
| 2020 | graphics APIs and are not intended for interpretation by app code. Additionally, |
| 2021 | there is historical precedent from `android.graphics` for new blending modes to |
| 2022 | be added in the future. |
| 2023 | |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 2024 | #### Extension and top-level functions {#kotlin-extension-functions} |
| 2025 | |
AndroidX Core Team | 6fd727f | 2021-06-01 12:26:34 -0700 | [diff] [blame] | 2026 | If your Kotlin file contains any symbols outside of class-like types |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 2027 | (extension/top-level functions, properties, etc), the file must be annotated |
| 2028 | with `@JvmName`. This ensures unanticipated use-cases from Java callers don't |
| 2029 | get stuck using `BlahKt` files. |
| 2030 | |
| 2031 | Example: |
| 2032 | |
| 2033 | ```kotlin {.bad} |
| 2034 | package androidx.example |
| 2035 | |
| 2036 | fun String.foo() = // ... |
| 2037 | ``` |
| 2038 | |
| 2039 | ```kotlin {.good} |
| 2040 | @file:JvmName("StringUtils") |
| 2041 | |
| 2042 | package androidx.example |
| 2043 | |
| 2044 | fun String.foo() = // ... |
| 2045 | ``` |
| 2046 | |
| 2047 | NOTE This guideline may be ignored for libraries that only work in Kotlin (think |
| 2048 | Compose). |
| 2049 | |
| 2050 | ## Testing Guidelines |
| 2051 | |
| 2052 | ### [Do not Mock, AndroidX](do_not_mock.md) |
| 2053 | |
| 2054 | ## Android Lint Guidelines |
| 2055 | |
| 2056 | ### Suppression vs Baselines |
| 2057 | |
| 2058 | Lint sometimes flags false positives, even though it is safe to ignore these |
| 2059 | errors (for example WeakerAccess warnings when you are avoiding synthetic |
| 2060 | access). There may also be lint failures when your library is in the middle of a |
| 2061 | beta / rc / stable release, and cannot make the breaking changes needed to fix |
| 2062 | the root cause. There are two ways of ignoring lint errors: |
| 2063 | |
| 2064 | 1. Suppression - using `@SuppressLint` (for Java) or `@Suppress` annotations to |
| 2065 | ignore the warning per call site, per method, or per file. *Note |
| 2066 | `@SuppressLint` - Requires Android dependency*. |
| 2067 | 2. Baselines - allowlisting errors in a lint-baseline.xml file at the root of |
| 2068 | the project directory. |
| 2069 | |
| 2070 | Where possible, you should use a **suppression annotation at the call site**. |
| 2071 | This helps ensure that you are only suppressing the *exact* failure, and this |
| 2072 | also keeps the failure visible so it can be fixed later on. Only use a baseline |
| 2073 | if you are in a Java library without Android dependencies, or when enabling a |
| 2074 | new lint check, and it is prohibitively expensive / not possible to fix the |
| 2075 | errors generated by enabling this lint check. |
| 2076 | |
AndroidX Core Team | 21ccf65 | 2022-04-01 14:53:07 +0000 | [diff] [blame] | 2077 | To update a lint baseline (`lint-baseline.xml`) after you have fixed issues, run |
| 2078 | the `updateLintBaseline` task. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 2079 | |
| 2080 | ```shell |
AndroidX Core Team | 21ccf65 | 2022-04-01 14:53:07 +0000 | [diff] [blame] | 2081 | ./gradlew :core:core:updateLintBaseline |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 2082 | ``` |
| 2083 | |
| 2084 | ## Metalava API Lint |
| 2085 | |
| 2086 | As well as Android Lint, which runs on all source code, Metalava will also run |
| 2087 | checks on the public API surface of each library. Similar to with Android Lint, |
| 2088 | there can sometimes be false positives / intended deviations from the API |
| 2089 | guidelines that Metalava will lint your API surface against. When this happens, |
| 2090 | you can suppress Metalava API lint issues using `@SuppressLint` (for Java) or |
| 2091 | `@Suppress` annotations. In cases where it is not possible, update Metalava's |
| 2092 | baseline with the `updateApiLintBaseline` task. |
| 2093 | |
| 2094 | ```shell |
AndroidX Core Team | 4e1909a | 2021-10-20 15:04:04 +0000 | [diff] [blame] | 2095 | ./gradlew :core:core:updateApiLintBaseline |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 2096 | ``` |
| 2097 | |
| 2098 | This will create/amend the `api_lint.ignore` file that lives in a library's |
| 2099 | `api` directory. |
| 2100 | |
| 2101 | ## Build Output Guidelines |
| 2102 | |
| 2103 | In order to more easily identify the root cause of build failures, we want to |
| 2104 | keep the amount of output generated by a successful build to a minimum. |
| 2105 | Consequently, we track build output similarly to the way in which we track Lint |
| 2106 | warnings. |
| 2107 | |
| 2108 | ### Invoking build output validation |
| 2109 | |
| 2110 | You can add `-Pandroidx.validateNoUnrecognizedMessages` to any other AndroidX |
| 2111 | gradlew command to enable validation of build output. For example: |
| 2112 | |
| 2113 | ```shell |
| 2114 | /gradlew -Pandroidx.validateNoUnrecognizedMessages :help |
| 2115 | ``` |
| 2116 | |
| 2117 | ### Exempting new build output messages |
| 2118 | |
| 2119 | Please avoid exempting new build output and instead fix or suppress the warnings |
| 2120 | themselves, because that will take effect not only on the build server but also |
| 2121 | in Android Studio, and will also run more quickly. |
| 2122 | |
| 2123 | If you cannot prevent the message from being generating and must exempt the |
| 2124 | message anyway, follow the instructions in the error: |
| 2125 | |
| 2126 | ```shell |
| 2127 | $ ./gradlew -Pandroidx.validateNoUnrecognizedMessages :help |
| 2128 | |
| 2129 | Error: build_log_simplifier.py found 15 new messages found in /usr/local/google/workspace/aosp-androidx-git/out/dist/gradle.log. |
| 2130 | |
| 2131 | Please fix or suppress these new messages in the tool that generates them. |
| 2132 | If you cannot, then you can exempt them by doing: |
| 2133 | |
| 2134 | 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 |
| 2135 | 2. modify the new lines to be appropriately generalized |
| 2136 | ``` |
| 2137 | |
| 2138 | Each line in this exemptions file is a regular expressing matching one or more |
| 2139 | lines of output to be exempted. You may want to make these expressions as |
| 2140 | specific as possible to ensure that the addition of new, similar messages will |
| 2141 | also be detected (for example, discovering an existing warning in a new source |
| 2142 | file). |
| 2143 | |
| 2144 | ## Behavior changes |
| 2145 | |
| 2146 | ### Changes that affect API documentation |
| 2147 | |
| 2148 | Do not make behavior changes that require altering API documentation in a way |
| 2149 | that would break existing clients, even if such changes are technically binary |
| 2150 | compatible. For example, changing the meaning of a method's return value to |
| 2151 | return true rather than false in a given state would be considered a breaking |
| 2152 | change. Because this change is binary-compatible, it will not be caught by |
| 2153 | tooling and is effectively invisible to clients. |
| 2154 | |
| 2155 | Instead, add new methods and deprecate the existing ones if necessary, noting |
| 2156 | behavior changes in the deprecation message. |
| 2157 | |
| 2158 | ### High-risk behavior changes |
| 2159 | |
| 2160 | Behavior changes that conform to documented API contracts but are highly complex |
| 2161 | and difficult to comprehensively test are considered high-risk and should be |
| 2162 | implemented using behavior flags. These changes may be flagged on initially, but |
| 2163 | the original behaviors must be preserved until the library enters release |
| 2164 | candidate stage and the behavior changes have been appropriately verified by |
| 2165 | integration testing against public pre-release |
| 2166 | revisions. |
| 2167 | |
| 2168 | It may be necessary to soft-revert a high-risk behavior change with only 24-hour |
| 2169 | notice, which should be achievable by flipping the behavior flag to off. |
| 2170 | |
| 2171 | ```java |
AndroidX Core Team | ee1457a | 2021-02-25 16:13:10 +0000 | [diff] [blame] | 2172 | // Flag for whether to throw exceptions when the state is known to be bad. This |
| 2173 | // is expected to be a high-risk change since apps may be working fine even with |
| 2174 | // a bad state, so we may need to disable this as a hotfix. |
| 2175 | private static final boolean FLAG_EXCEPTION_ON_BAD_STATE = false; |
| 2176 | ``` |
| 2177 | |
| 2178 | ```java |
| 2179 | /** |
| 2180 | * Allows a developer to toggle throwing exceptions when the state is known to |
| 2181 | * be bad. This method is intended to give developers time to update their code. |
| 2182 | * It is temporary and will be removed in a future release. |
| 2183 | */ |
| 2184 | @TemporaryFeatureFlag |
| 2185 | public void setExceptionOnBadStateEnabled(boolean enabled); |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 2186 | ``` |
| 2187 | |
| 2188 | Avoid adding multiple high-risk changes during a feature cycle, as verifying the |
| 2189 | interaction of multiple feature flags leads to unnecessary complexity and |
| 2190 | exposes clients to high risk even when a single change is flagged off. Instead, |
| 2191 | wait until one high-risk change has landed in RC before moving on to the next. |
| 2192 | |
| 2193 | #### Testing |
| 2194 | |
| 2195 | Relevant tests should be run for the behavior change in both the on and off |
| 2196 | flagged states to prevent regressions. |
| 2197 | |
| 2198 | ## Sample code in Kotlin modules |
| 2199 | |
| 2200 | ### Background |
| 2201 | |
| 2202 | Public API can (and should!) have small corresponding code snippets that |
| 2203 | demonstrate functionality and usage of a particular API. These are often exposed |
| 2204 | inline in the documentation for the function / class - this causes consistency |
| 2205 | and correctness issues as this code is not compiled against, and the underlying |
| 2206 | implementation can easily change. |
| 2207 | |
| 2208 | KDoc (JavaDoc for Kotlin) supports a `@sample` tag, which allows referencing the |
| 2209 | body of a function from documentation. This means that code samples can be just |
| 2210 | written as a normal function, compiled and linted against, and reused from other |
| 2211 | modules such as tests! This allows for some guarantees on the correctness of a |
| 2212 | sample, and ensuring that it is always kept up to date. |
| 2213 | |
| 2214 | ### Enforcement |
| 2215 | |
| 2216 | There are still some visibility issues here - it can be hard to tell if a |
| 2217 | function is a sample, and is used from public documentation - so as a result we |
| 2218 | have lint checks to ensure sample correctness. |
| 2219 | |
| 2220 | Primarily, there are three requirements when using sample links: |
| 2221 | |
| 2222 | 1. All functions linked to from a `@sample` KDoc tag must be annotated with |
| 2223 | `@Sampled` |
| 2224 | 2. All sample functions annotated with `@Sampled` must be linked to from a |
| 2225 | `@sample` KDoc tag |
| 2226 | 3. All sample functions must live inside a separate `samples` library |
| 2227 | submodule - see the section on module configuration below for more |
| 2228 | information. |
| 2229 | |
| 2230 | This enforces visibility guarantees, and make it easier to know that a sample is |
| 2231 | a sample. This also prevents orphaned samples that aren't used, and remain |
| 2232 | unmaintained and outdated. |
| 2233 | |
| 2234 | ### Sample usage |
| 2235 | |
| 2236 | The follow demonstrates how to reference sample functions from public API. It is |
| 2237 | also recommended to reuse these samples in unit tests / integration tests / test |
AndroidX Core Team | 4cc85fa | 2021-11-23 15:58:34 +0000 | [diff] [blame] | 2238 | apps / library demos where possible to help ensure that the samples work as |
| 2239 | intended. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 2240 | |
| 2241 | **Public API:** |
| 2242 | |
| 2243 | ``` |
| 2244 | /* |
| 2245 | * Fancy prints the given [string] |
| 2246 | * |
| 2247 | * @sample androidx.printer.samples.fancySample |
| 2248 | */ |
| 2249 | fun fancyPrint(str: String) ... |
| 2250 | ``` |
| 2251 | |
| 2252 | **Sample function:** |
| 2253 | |
| 2254 | ``` |
| 2255 | package androidx.printer.samples |
| 2256 | |
| 2257 | import androidx.printer.fancyPrint |
| 2258 | |
| 2259 | @Sampled |
| 2260 | fun fancySample() { |
| 2261 | fancyPrint("Fancy!") |
| 2262 | } |
| 2263 | ``` |
| 2264 | |
AndroidX Core Team | 4cc85fa | 2021-11-23 15:58:34 +0000 | [diff] [blame] | 2265 | **Generated documentation visible on d.android.com / within Android Studio** |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 2266 | |
| 2267 | ``` |
| 2268 | fun fancyPrint(str: String) |
| 2269 | |
| 2270 | Fancy prints the given [string] |
| 2271 | |
| 2272 | <code> |
| 2273 | import androidx.printer.fancyPrint |
| 2274 | |
| 2275 | fancyPrint("Fancy!") |
| 2276 | <code> |
| 2277 | ``` |
| 2278 | |
AndroidX Core Team | 4cc85fa | 2021-11-23 15:58:34 +0000 | [diff] [blame] | 2279 | Warning: Only the body of the function is used in generated documentation, so |
| 2280 | any other references to elements defined outside the body of the function (such |
| 2281 | as variables defined within the sample file) will not be visible. To ensure that |
| 2282 | samples can be easily copy and pasted without errors, make sure that any |
| 2283 | references are defined within the body of the function. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 2284 | |
| 2285 | ### Module configuration |
| 2286 | |
AndroidX Core Team | 4cc85fa | 2021-11-23 15:58:34 +0000 | [diff] [blame] | 2287 | The following module setups should be used for sample functions: |
| 2288 | |
| 2289 | **Per-module samples** |
| 2290 | |
| 2291 | For library groups with relatively independent sub-libraries. This is the |
| 2292 | recommended project setup, and should be used in most cases. |
| 2293 | |
| 2294 | Gradle project name: `:foo-library:foo-module:foo-module-samples` |
| 2295 | |
| 2296 | ``` |
| 2297 | foo-library/ |
| 2298 | foo-module/ |
| 2299 | samples/ |
| 2300 | ``` |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 2301 | |
| 2302 | **Group-level samples** |
| 2303 | |
AndroidX Core Team | 4cc85fa | 2021-11-23 15:58:34 +0000 | [diff] [blame] | 2304 | For library groups with strongly related samples that want to share code and be |
| 2305 | reused across a library group, a singular shared samples library can be created. |
| 2306 | In most cases this is discouraged - samples should be small and show the usage |
| 2307 | of a particular API / small set of APIs, instead of more complicated usage |
| 2308 | combining multiple APIs from across libraries. For these cases a sample |
| 2309 | application is more appropriate. |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 2310 | |
AndroidX Core Team | 0db91f0 | 2021-05-06 22:45:18 +0000 | [diff] [blame] | 2311 | Gradle project name: `:foo-library:foo-library-samples` |
AndroidX Core Team | 2e416b2 | 2020-12-03 22:58:07 +0000 | [diff] [blame] | 2312 | |
| 2313 | ``` |
| 2314 | foo-library/ |
| 2315 | foo-module/ |
| 2316 | bar-module/ |
| 2317 | samples/ |
| 2318 | ``` |
| 2319 | |
AndroidX Core Team | 0db91f0 | 2021-05-06 22:45:18 +0000 | [diff] [blame] | 2320 | **Samples module configuration** |
| 2321 | |
| 2322 | Samples modules are published to GMaven so that they are available to Android |
AndroidX Core Team | 4cc85fa | 2021-11-23 15:58:34 +0000 | [diff] [blame] | 2323 | Studio, which displays referenced samples as hover-over pop-ups. |
AndroidX Core Team | 0db91f0 | 2021-05-06 22:45:18 +0000 | [diff] [blame] | 2324 | |
| 2325 | To achieve this, samples modules must declare the same MavenGroup and `publish` |
| 2326 | as the library(s) they are samples for. |