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