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