docs/api_guidelines/platform_compat.md
NOTE For all library APIs that wrap or provide parity with platform APIs, we
prefer to follow modern API guidelines; however, developers may choose to
prioritize parity with the platform APIs over adherence to modern guidelines.
For example, if the platform API being wrapped has incorrect Executor and
Callback ordering according to the API Guidelines, the corresponding library
API should re-order the arguments.
When to use?
minSdkVersionandroidx.core:core library<PlatformClass>Compatandroidx.<feature>.<platform.package>Object<PlatformClass>
<PlatformClass> as first
parameter (except in the case of static methods on the platform class, as
shown below)<PlatformClass> methods when availableThe following sample provides static helper methods for the platform class
android.os.Process.
package androidx.core.os;
/**
* Helper for accessing features in {@link Process}.
*/
public final class ProcessCompat {
private ProcessCompat() {
// This class is non-instantiable.
}
/**
* [Docs should match platform docs.]
*
* Compatibility behavior:
* <ul>
* <li>SDK 24 and above, this method matches platform behavior.
* <li>SDK 16 through 23, this method is a best-effort to match platform behavior, but may
* default to returning {@code true} if an accurate result is not available.
* <li>SDK 15 and below, this method always returns {@code true} as application UIDs and
* isolated processes did not exist yet.
* </ul>
*
* @param [match platform docs]
* @return [match platform docs], or a value based on platform-specific fallback behavior
*/
public static boolean isApplicationUid(int uid) {
if (Build.VERSION.SDK_INT >= 24) {
return Api24Impl.isApplicationUid(uid);
} else if (Build.VERSION.SDK_INT >= 16) {
// Fall back to using reflection on private APIs.
// ...
} else {
return true;
}
}
}
When to use?
minSdkVersionminSdkVersion is raised<PlatformClass>Compatandroidx.core.<platform.package><PlatformClass>PlatformClass constructors
PlatformClass must not be publicPlatformClassCompat toPlatformClassCompat(PlatformClass) method to wrap PlatformClass on
supported SDK levels
minSdkVersion, method must be
annotated with @RequiresApi(<sdk>) for SDK version where class was
introducedandroidx.core:core libraryPlatformClass toPlatformClass() method to
unwrap PlatformClass on supported SDK levels
minSdkVersion, method must be
annotated with @RequiresApi(<sdk>) for SDK version where class was
introducedPlatformClass methods when available (see
below note for caveats)The following sample wraps a hypothetical platform class ModemInfo that was
added to the platform SDK in API level 23:
public final class ModemInfoCompat {
// Only guaranteed to be non-null on SDK_INT >= 23. Note that referencing the
// class itself directly is fine -- only references to class members need to
// be pushed into static inner classes.
private final Object wrappedObj;
/**
* [Copy platform docs for matching constructor.]
*/
public ModemInfoCompat() {
if (SDK_INT >= 23) {
wrappedObj = Api23Impl.create();
} else {
wrappedObj = null;
}
}
@RequiresApi(23)
private ModemInfoCompat(@NonNull ModemInfo obj) {
mWrapped = obj;
}
/**
* Provides a backward-compatible wrapper for {@link ModemInfo}.
* <p>
* This method is not supported on devices running SDK < 23 since the platform
* class will not be available.
*
* @param info platform class to wrap
* @return wrapped class, or {@code null} if parameter is {@code null}
*/
@RequiresApi(23)
@NonNull
public static ModemInfoCompat toModemInfoCompat(@NonNull ModemInfo info) {
return new ModemInfoCompat(obj);
}
/**
* Provides the {@link ModemInfo} represented by this object.
* <p>
* This method is not supported on devices running SDK < 23 since the platform
* class will not be available.
*
* @return platform class object
* @see ModemInfoCompat#toModemInfoCompat(ModemInfo)
*/
@RequiresApi(23)
@NonNull
public ModemInfo toModemInfo() {
return mWrapped;
}
/**
* [Docs should match platform docs.]
*
* Compatibility behavior:
* <ul>
* <li>API level 23 and above, this method matches platform behavior.
* <li>API level 18 through 22, this method ...
* <li>API level 17 and earlier, this method always returns false.
* </ul>
*
* @return [match platform docs], or platform-specific fallback behavior
*/
public boolean isLteSupported() {
if (SDK_INT >= 23) {
return Api23Impl.isLteSupported(mWrapped);
} else if (SDK_INT >= 18) {
// Smart fallback behavior based on earlier APIs.
// ...
}
// Default behavior.
return false;
}
}
Note that libraries written in Java should express conversion to and from the
platform class differently than Kotlin classes. For Java classes, conversion
from the platform class to the wrapper should be expressed as a static method,
while conversion from the wrapper to the platform class should be a method on
the wrapper object:
@NonNull
public static ModemInfoCompat toModemInfoCompat(@NonNull ModemInfo info);
@NonNull
public ModemInfo toModemInfo();
In cases where the primary library is written in Java and has an accompanying
-ktx Kotlin extensions library, the following conversion should be provided as
an extension function:
fun ModemInfo.toModemInfoCompat() : ModemInfoCompat
Whereas in cases where the primary library is written in Kotlin, the conversion should be provided as an extension factory:
class ModemInfoCompat {
fun toModemInfo() : ModemInfo
companion object {
@JvmStatic
@JvmName("toModemInfoCompat")
fun ModemInfo.toModemInfoCompat() : ModemInfoCompat
}
}
When to use?
minSdkVersionimport
collision due to both compatibility and platform classes being referenced
within the same source fileImplementation requirements
<PlatformClass>androidx.<platform.package><PlatformClass>PlatformClass in public API
toPlatform<PlatformClass> and
static toCompat<PlatformClass> method naming convention.PlatformClass methods when availableWhen to use
Examples:
The Paging Library pages data from DataSources (such as DB content from Room or network content from Retrofit) into PagedLists, so they can be presented in a RecyclerView. Since the included Adapter receives a PagedList, and there are no other Android dependencies, Paging is split into two parts - a no-android library (paging-common) with the majority of the paging code, and an android library (paging-runtime) with just the code to present a PagedList in a RecyclerView Adapter. This way, tests of Repositories and their components can be tested in host-side tests.
Room loads SQLite data on Android, but provides an abstraction for those that want to use a different SQL implementation on device. This abstraction, and the fact that Room generates code dynamically, means that Room interfaces can be used in host-side tests (though actual DB code should be tested on device, since DB impls may be significantly different on host).