1## Platform compatibility API patterns {#platform-compatibility-apis}
2
3NOTE For all library APIs that wrap or provide parity with platform APIs, we
4prefer to follow modern API guidelines; however, developers *may* choose to
5prioritize parity with the platform APIs over adherence to modern guidelines.
6For example, if the platform API being wrapped has incorrect `Executor` and
7`Callback` ordering according to the API Guidelines, the corresponding library
8API *should* re-order the arguments.
9
10### Static shims (ex. [ViewCompat](https://developer.android.com/reference/androidx/core/view/ViewCompat)) {#static-shim}
11
12When to use?
13
14*   Platform class exists at module's `minSdkVersion`
15*   Compatibility implementation does not need to store additional metadata
16
17#### API guidelines {#static-shim-api-guidelines}
18
19##### Naming {#static-shim-naming}
20
21*   Class *should* be added to the `androidx.core:core` library
22*   Class name **must** be `<PlatformClass>Compat`
23*   Package name **must** be `androidx.<feature>.<platform.package>`
24*   Superclass **must** be `Object`
25
26##### Construction {#static-shim-construction}
27
28*   Class **must** be non-instantiable, i.e. constructor is private no-op
29
30#### Implementation {#static-shim-implementation}
31
32*   Static fields and static methods **must** match match signatures with
33    `<PlatformClass>`
34    *   Static fields that can be inlined, ex. integer constants, **must not**
35        be shimmed
36*   Public method names **must** match platform method names
37*   Public methods **must** be static and take `<PlatformClass>` as first
38    parameter (except in the case of static methods on the platform class, as
39    shown below)
40*   Implementation *may* delegate to `<PlatformClass>` methods when available
41
42#### Sample {#static-shim-sample}
43
44The following sample provides static helper methods for the platform class
45`android.os.Process`.
46
47```java
48package androidx.core.os;
49
50/**
51 * Helper for accessing features in {@link Process}.
52 */
53public final class ProcessCompat {
54    private ProcessCompat() {
55        // This class is non-instantiable.
56    }
57
58    /**
59     * [Docs should match platform docs.]
60     *
61     * Compatibility behavior:
62     * <ul>
63     * <li>SDK 24 and above, this method matches platform behavior.
64     * <li>SDK 16 through 23, this method is a best-effort to match platform behavior, but may
65     * default to returning {@code true} if an accurate result is not available.
66     * <li>SDK 15 and below, this method always returns {@code true} as application UIDs and
67     * isolated processes did not exist yet.
68     * </ul>
69     *
70     * @param [match platform docs]
71     * @return [match platform docs], or a value based on platform-specific fallback behavior
72     */
73    public static boolean isApplicationUid(int uid) {
74        if (Build.VERSION.SDK_INT >= 24) {
75            return Api24Impl.isApplicationUid(uid);
76        } else if (Build.VERSION.SDK_INT >= 16) {
77            // Fall back to using reflection on private APIs.
78            // ...
79        } else {
80            return true;
81        }
82    }
83}
84```
85
86### Wrapper (ex. [AccessibilityNodeInfoCompat](https://developer.android.com/reference/androidx/core/view/accessibility/AccessibilityNodeInfoCompat)) {#wrapper}
87
88When to use?
89
90*   Platform class may not exist at module's `minSdkVersion`
91*   Compatibility implementation may need to store additional metadata
92*   Needs to integrate with platform APIs as return value or method argument
93*   **Note:** Should be avoided when possible, as using wrapper classes makes it
94    very difficult to deprecate classes and migrate source code when the
95    `minSdkVersion` is raised
96
97#### API guidelines {#wrapper-api-guidelines}
98
99##### Naming {#wrapper-naming}
100
101*   Class name **must** be `<PlatformClass>Compat`
102*   Package name **must** be `androidx.core.<platform.package>`
103*   Superclass **must not** be `<PlatformClass>`
104
105##### Construction {#wrapper-construction}
106
107*   Class *may* have public constructor(s) to provide parity with public
108    `PlatformClass` constructors
109    *   Constructor used to wrap `PlatformClass` **must not** be public
110*   Class **must** implement a static `PlatformClassCompat
111    toPlatformClassCompat(PlatformClass)` method to wrap `PlatformClass` on
112    supported SDK levels
113    *   If class does not exist at module's `minSdkVersion`, method must be
114        annotated with `@RequiresApi(<sdk>)` for SDK version where class was
115        introduced
116
117#### Implementation {#wrapper-implementation}
118
119*   Class *should* be added to the `androidx.core:core` library
120*   Class **must** implement a `PlatformClass toPlatformClass()` method to
121    unwrap `PlatformClass` on supported SDK levels
122    *   If class does not exist at module's `minSdkVersion`, method must be
123        annotated with `@RequiresApi(<sdk>)` for SDK version where class was
124        introduced
125*   Implementation *may* delegate to `PlatformClass` methods when available (see
126    below note for caveats)
127
128#### Sample {#wrapper-sample}
129
130The following sample wraps a hypothetical platform class `ModemInfo` that was
131added to the platform SDK in API level 23:
132
133```java
134public final class ModemInfoCompat {
135  // Only guaranteed to be non-null on SDK_INT >= 23. Note that referencing the
136  // class itself directly is fine -- only references to class members need to
137  // be pushed into static inner classes.
138  private final Object wrappedObj;
139
140  /**
141   * [Copy platform docs for matching constructor.]
142   */
143  public ModemInfoCompat() {
144    if (SDK_INT >= 23) {
145      wrappedObj = Api23Impl.create();
146    } else {
147      wrappedObj = null;
148    }
149  }
150
151  @RequiresApi(23)
152  private ModemInfoCompat(@NonNull ModemInfo obj) {
153    mWrapped = obj;
154  }
155
156  /**
157   * Provides a backward-compatible wrapper for {@link ModemInfo}.
158   * <p>
159   * This method is not supported on devices running SDK < 23 since the platform
160   * class will not be available.
161   *
162   * @param info platform class to wrap
163   * @return wrapped class, or {@code null} if parameter is {@code null}
164   */
165  @RequiresApi(23)
166  @NonNull
167  public static ModemInfoCompat toModemInfoCompat(@NonNull ModemInfo info) {
168    return new ModemInfoCompat(obj);
169  }
170
171  /**
172   * Provides the {@link ModemInfo} represented by this object.
173   * <p>
174   * This method is not supported on devices running SDK < 23 since the platform
175   * class will not be available.
176   *
177   * @return platform class object
178   * @see ModemInfoCompat#toModemInfoCompat(ModemInfo)
179   */
180  @RequiresApi(23)
181  @NonNull
182  public ModemInfo toModemInfo() {
183    return mWrapped;
184  }
185
186  /**
187   * [Docs should match platform docs.]
188   *
189   * Compatibility behavior:
190   * <ul>
191   * <li>API level 23 and above, this method matches platform behavior.
192   * <li>API level 18 through 22, this method ...
193   * <li>API level 17 and earlier, this method always returns false.
194   * </ul>
195   *
196   * @return [match platform docs], or platform-specific fallback behavior
197   */
198  public boolean isLteSupported() {
199    if (SDK_INT >= 23) {
200      return Api23Impl.isLteSupported(mWrapped);
201    } else if (SDK_INT >= 18) {
202      // Smart fallback behavior based on earlier APIs.
203      // ...
204    }
205    // Default behavior.
206    return false;
207  }
208}
209```
210
211Note that libraries written in Java should express conversion to and from the
212platform class differently than Kotlin classes. For Java classes, conversion
213from the platform class to the wrapper should be expressed as a `static` method,
214while conversion from the wrapper to the platform class should be a method on
215the wrapper object:
216
217```java
218@NonNull
219public static ModemInfoCompat toModemInfoCompat(@NonNull ModemInfo info);
220
221@NonNull
222public ModemInfo toModemInfo();
223```
224
225In cases where the primary library is written in Java and has an accompanying
226`-ktx` Kotlin extensions library, the following conversion should be provided as
227an extension function:
228
229```kotlin
230fun ModemInfo.toModemInfoCompat() : ModemInfoCompat
231```
232
233Whereas in cases where the primary library is written in Kotlin, the conversion
234should be provided as an extension factory:
235
236```kotlin
237class ModemInfoCompat {
238  fun toModemInfo() : ModemInfo
239
240  companion object {
241    @JvmStatic
242    @JvmName("toModemInfoCompat")
243    fun ModemInfo.toModemInfoCompat() : ModemInfoCompat
244  }
245}
246```
247
248### Standalone (ex. [ArraySet](https://developer.android.com/reference/androidx/collection/ArraySet), [Fragment](https://developer.android.com/jetpack/androidx/releases/fragment)) {#standalone}
249
250When to use?
251
252*   Platform class may exist at module's `minSdkVersion`
253*   Does not need to integrate with platform APIs
254*   Does not need to coexist with platform class, ex. no potential `import`
255    collision due to both compatibility and platform classes being referenced
256    within the same source file
257
258Implementation requirements
259
260*   Class name **must** be `<PlatformClass>`
261*   Package name **must** be `androidx.<platform.package>`
262*   Superclass **must not** be `<PlatformClass>`
263*   Class **must not** expose `PlatformClass` in public API
264    *   In exceptional cases, a *released* standalone class may add conversion
265        between itself and the equivalent platform class; however, *new* classes
266        that support conversion should follow the [Wrapper](#wrapper)
267        guidelines. In these cases, use a `toPlatform<PlatformClass>` and
268        `static toCompat<PlatformClass>` method naming convention.
269*   Implementation *may* delegate to `PlatformClass` methods when available
270
271### Standalone JAR library (no Android dependencies) {#standalone-jvm}
272
273When to use
274
275*   General purpose library with minimal interaction with Android types
276    *   or when abstraction around types can be used (e.g. Room's SQLite
277        wrapper)
278*   Lib used in parts of app with minimal Android dependencies
279    *   ex. Repository, ViewModel
280*   When Android dependency can sit on top of common library
281*   Clear separation between android dependent and independent parts of your
282    library
283*   Clear that future integration with android dependencies can be layered
284    separately
285
286**Examples:**
287
288The **Paging Library** pages data from DataSources (such as DB content from Room
289or network content from Retrofit) into PagedLists, so they can be presented in a
290RecyclerView. Since the included Adapter receives a PagedList, and there are no
291other Android dependencies, Paging is split into two parts - a no-android
292library (paging-common) with the majority of the paging code, and an android
293library (paging-runtime) with just the code to present a PagedList in a
294RecyclerView Adapter. This way, tests of Repositories and their components can
295be tested in host-side tests.
296
297**Room** loads SQLite data on Android, but provides an abstraction for those
298that want to use a different SQL implementation on device. This abstraction, and
299the fact that Room generates code dynamically, means that Room interfaces can be
300used in host-side tests (though actual DB code should be tested on device, since
301DB impls may be significantly different on host).
302