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