These are rules about classes, interfaces, and inheritance.
Inheritance exposes API in your subclass which may not be appropriate. For example, a new public subclass of FrameLayout
will look like a FrameLayout
(plus the new functionality/API). If that inherited API is not appropriate for your use case, inherit from something further up the tree (for example, ViewGroup
or even View
, instead of FrameLayout
).
If you are tempted to override methods from the base class to throw UnsupportedOperationException
, reconsider which base class you are using.
Whether taking a collection as an argument or returning it as a value, always prefer the base class over the specific implementation (e.g. return List<Foo>
rather than ArrayList<Foo>
).
Use a base class that expresses appropriate constraints for the API. For example, an API whose collection must be ordered should use List
and an API whose collection must consist of unique elements should use Set
.
In Kotlin, prefer immutable collections. See Collection mutability for more details.
Java 8 adds support for default interface methods, which allows API designers to add methods to interfaces while maintaining binary compatibility. Platform code and all Jetpack libraries should target Java 8 or later.
In cases where the default implementation is stateless, API designers should prefer interfaces over abstract classes -- that is, default interface methods can be implemented as calls to other interface methods.
In cases where a constructor or internal state is required by the default implementation, abstract classes must be used.
In both cases, API designers may choose to leave a single method abstract to simplify usage as a lambda.
public interface AnimationEndCallback { // Always called, must be implemented. public void onFinished(Animation anim); // Optional callbacks. public default void onStopped(Animation anim) { } public default void onCanceled(Animation anim) { } }
For example, classes which extend Service
should be named FooService
for clarity.
public class IntentHelper extends Service {}
public class IntentService extends Service {}
Helper
, Util
, etc.)Avoid using generic class name suffixes like Helper
and Util
for collections of utility methods. Instead, put the methods directly in the associated classes or into Kotlin extension functions.
In cases where methods are bridging multiple classes, give the containing class a meaningful name that explains what it does.
In very limited cases, using the Helper
suffix may be appropriate:
View
For example, if backporting tooltips requires persisting state associated with a View
and calling several methods on the View
to install the backport, TooltipHelper
would be an acceptable class name.
Keep IDL-generated code as implementation details. This includes protobuf, sockets, FlatBuffers, or any other non-Java, non-NDK API surface. However, most IDL in Android is in AIDL, so we focus on AIDL here.
Generated AIDL classes do not meet the API style guide requirements (for example, they cannot use overloading) and are not guaranteed to maintain language API compatibility, so we can't embed them in a public API.
Instead, add a public API layer on top of the AIDL interface, even if it initially is a shallow wrapper.
Binder
interfacesIf the Binder
interface is an implementation detail, it can be changed freely in the future, with the public layer allowing for the required backward compatibility to be maintained. For example, you may find you need to add new arguments to the internal calls, or optimize IPC traffic via batching/streaming, using shared memory, or similar. None of these can currently be done if your AIDL interface is also the public API.
For example, instead of exposing FooService
as a public API directly:
// BAD: Public API generated from IFooService.aidl public class IFooService { public void doFoo(String foo); }
instead wrap the Binder
interface inside a manager or other class:
/** * @hide */ public class IFooService { public void doFoo(String foo); } public IFooManager { public void doFoo(String foo) { mFooService.doFoo(foo); } }
and if later a new argument is needed for this call, the internal interface can be kept simple and convenient overloads added to the public API. And the wrapping layer can be used to handle other backwards-compatibility concerns as the implementation evolves, as well:
/** * @hide */ public class IFooService { public void doFoo(String foo, int flags); } public IFooManager { public void doFoo(String foo) { if (mAppTargetSdkLevel < 26) { useOldFooLogic(); // Apps targeting API before 26 are broken otherwise mFooService.doFoo(foo, FLAG_THAT_ONE_WEIRD_HACK); } else { mFooService.doFoo(foo, 0); } } public void doFoo(String foo, int flags) { mFooService.doFoo(foo, flags); } }
For Binder
interfaces that are not part of the Android platform (for example, a service interface exported by Google Play Services for applications to use), the requirement for a stable, published, and versioned IPC interface means that it is much harder to evolve the interface itself. However, it is still worthwhile to have a wrapper layer around it, to match other API guidelines and to make it easier to use the same public API for a new version of the IPC interface, if that ever becomes necessary.
Binder
objects in public APIA Binder
object does not have any meaning on its own and thus should not be used in public API. One common use-case is to use a Binder
or IBinder
as a token because it has identity semantics. Instead of using a raw Binder
object use a wrapper token class instead.
public final class IdentifiableObject { public Binder getToken() {...} }
public final class IdentifiableObjectToken { /** * @hide */ public Binder getRawValue() {...} /** * @hide */ public static IdentifiableObjectToken wrapToken(Binder rawValue) {...} } public final class IdentifiableObject { public IdentifiableObjectToken getToken() {...} }
Manager
classes must be final
.Manager classes should be declared as final
. Manager classes talk to system services and are the single point of interaction. There is no need for customization so declare it as final
.
CompletableFuture
or Future
java.util.concurrent.CompletableFuture
has a large API surface that permits arbitrary mutation of the future's value and has error-prone defaults .
Conversely, java.util.concurrent.Future
is missing non-blocking listening, making it hard to use with asynchronous code.
In platform code and low-level library APIs consumed by both Kotlin and Java, prefer a combination of a completion callback, Executor
, and if the API supports cancellation CancellationSignal
.
public void asyncLoadFoo(android.os.CancellationSignal cancellationSignal, Executor callbackExecutor, android.os.OutcomeReceiver<FooResult, Throwable> callback);
If you are targeting Kotlin, prefer suspend
functions.
suspend fun asyncLoadFoo(): Foo
In Java-specific integration libraries, Guava's ListenableFuture
may be used.
public com.google.common.util.concurrent.ListenableFuture<Foo> asyncLoadFoo();
Optional
While Optional
can have advantages in some API surfaces, it is inconsistent with the existing Android API surface area. @Nullable
and @NonNull
provide tooling assistance for null
safety and Kotlin enforces nullability contracts at the compiler level, making Optional
unnecessary.
For optional primitives, use paired has
and get
methods. If the value isn't set (i.e., has
returns false
), the get
method should throw an IllegalStateException
.
public boolean hasAzimuth() { ... } public int getAzimuth() { if (!hasAzimuth()) { throw new IllegalStateException("azimuth is not set"); } return azimuth; }
Classes that can only be created by Builder
s, classes containing only constants or static methods, or otherwise non-instantiable classes should include at least one private constructor to prevent instantiation via the default no-arg constructor.
public final class Log { // Not instantiable. private Log() {} }
Singleton are discouraged because they have the following testing-related drawbacks:
Prefer the single instance pattern, which relies on an abstract base class to address these issues.
Single instance classes use an abstract base class with a private
or internal
constructor and provide a static getInstance()
method to obtain an instance. The getInstance()
method must return the same object on subsequent calls.
The object returned by getInstance()
should be a private implementation of the abstract base class.
class Singleton private constructor(...) { companion object { private val _instance: Singleton by lazy { Singleton(...) } fun getInstance(): Singleton { return _instance } } }
abstract class SingleInstance private constructor(...) { companion object { private val _instance: SingleInstance by lazy { SingleInstanceImp(...) } fun getInstance(): SingleInstance { return _instance } } }
Single instance differs from singleton in that developers can create a fake version of SingleInstance
and use their own Dependency Injection framework to manage the implementation without having to create a wrapper, or the library can provide its own fake in a -testing
artifact.
AutoCloseable
Classes that release resources through close
, release
, destroy
or similar methods should implement java.lang.AutoCloseable
to allow developers to automatically clean up these resources when using a try-with-resources
block.
Do not introduce new classes that inherit directly or indirectly from android.view.View
in the platform public API (i.e. in android.*
).
Android's UI toolkit is now Compose-first. New UI functionality exposed by the platform should be exposed as lower-level APIs that can be used to implement Jetpack Compose and optionally View-based UI components for developers in androidx libraries. Offering these components in androidx libraries affords opportunities for backported implementations when platform functionality is not available.
These rules are about public fields on classes.
Java classes should not expose fields directly. Fields should be private and accessible only via public getters and setters regardless of whether these fields are final or non-final.
Rare exceptions include simple data structures where there will never be a need to enhance the functionality of specifying or retrieving a field. In such cases, the fields should be named using standard variable naming conventions, ex. Point.x
and Point.y
.
Kotlin classes may expose properties.
Raw fields are strongly discouraged (@see Do not expose raw fields). But in the rare situation where a field is exposed as a public field, mark that field final
.
Do not reference internal field names in public API.
public int mFlags;
public
instead of protected
@see Use public instead of protected
These are rules about public constants.
int
or long
values“Flags” implies bits that can be combined into some union value. If this is not the case, do not call the variable/constant flag
.
public static final int FLAG_SOMETHING = 2; public static final int FLAG_SOMETHING = 3;
public static final int FLAG_PRIVATE = 1 << 2; public static final int FLAG_PRESENTATION = 1 << 3;
See @IntDef
for bitmask flags for more information on defining public flag constants.
static final
constants should use all-cap, underscore-separated naming conventionAll words in the constant should be capitalized and multiple words should be separated by _
. For example:
public static final int fooThing = 5
public static final int FOO_THING = 5
Many of the constants used in Android are for standard things, such as flags, keys, and actions. These constants should have standard prefixes to make them more identifiable as these things.
For example, intent extras should start with EXTRA_
. Intent actions should start with ACTION_
. Constants used with Context.bindService()
should start with BIND_
.
String constant values should be consistent with the constant name itself, and should generally be scoped to the package or domain. For example:
public static final String FOO_THING = “foo”
is neither named consistently nor appropriately scoped. Instead, consider:
public static final String FOO_THING = “android.fooservice.FOO_THING”
Prefixes of android
in scoped string constants are reserved for the Android Open Source Project.
Intent actions and extras, as well as Bundle entries, should be namespaced using the package name they are defined within.
package android.foo.bar { public static final String ACTION_BAZ = “android.foo.bar.action.BAZ” public static final String EXTRA_BAZ = “android.foo.bar.extra.BAZ” }
public
instead of protected
@see Use public instead of protected
Related constants should all start with the same prefix. For example, for a set of constants to use with flag values:
public static final int SOME_VALUE = 0x01; public static final int SOME_OTHER_VALUE = 0x10; public static final int SOME_THIRD_VALUE = 0x100;
public static final int FLAG_SOME_VALUE = 0x01; public static final int FLAG_SOME_OTHER_VALUE = 0x10; public static final int FLAG_SOME_THIRD_VALUE = 0x100;
@see Use standard prefixes for constants
Public identifiers, attributes, and values must be named using the camelCase naming convention, e.g. @id/accessibilityActionPageUp
or @attr/textAppearance
, similar to public fields in Java.
In some cases, a public identifier or attribute may include a common prefix separated by an underscore:
@string/config_recentsComponentName
in config.xml@attr/layout_marginStart
in attrs.xmlPublic themes and styles must follow the hierarchical PascalCase naming convention, e.g. @style/Theme.Material.Light.DarkActionBar
or @style/Widget.Material.SearchView.ActionBar
, similar to nested classes in Java.
Layout and drawable resources should not be exposed as public APIs. If they must be exposed, however, then public layouts and drawables must be named using the under_score naming convention, e.g. layout/simple_list_item_1.xml
or drawable/title_bar_tall.xml
.
Constant values may be inlined at compile time, so keeping values the same is considered part of the API contract. If the value of a MIN_FOO
or MAX_FOO
constant could change in the future, consider making them dynamic methods instead.
CameraManager.MAX_CAMERAS
CameraManager.getMaxCameras()
Constants defined in future API versions are not known to apps that target older APIs. For this reason, constants delivered to apps should take into consideration that app’s target API version and map newer constants to a consistent value. Consider the following scenario:
Hypothetical SDK source:
// Added in API level 22 public static final int STATUS_SUCCESS = 1; public static final int STATUS_FAILURE = 2; // Added in API level 23 public static final int STATUS_FAILURE_RETRY = 3; // Added in API level 26 public static final int STATUS_FAILURE_ABORT = 4;
Hypothetical app with targetSdkVersion="22"
:
if (result == STATUS_FAILURE) { // Oh no! } else { // Success! }
In this case, the app was designed within the constraints of API level 22 and made a (somewhat) reasonable assumption that there were only two possible states. If the app receives the newly-added STATUS_FAILURE_RETRY
, however, it will interpret this as success.
Methods that return constants can safely handle cases like this by constraining their output to match the API level targeted by the app:
private int mapResultForTargetSdk(Context context, int result) { int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; if (targetSdkVersion < 26) { if (result == STATUS_FAILURE_ABORT) { return STATUS_FAILURE; } if (targetSdkVersion < 23) { if (result == STATUS_FAILURE_RETRY) { return STATUS_FAILURE; } } } return result; }
It’s unreasonable to expect developers and their published applications to be clairvoyant. If you define an API with an UNKNOWN
or UNSPECIFIED
constant that looks like a catch-all, developers will assume that the published constants when they wrote their app are exhaustive. If you’re unwilling to set this expectation, reconsider whether a catch-all constant is a good idea for your API.
Consider also that libraries cannot specify their own targetSdkVersion
separate from the app, and that handling targetSdkVersion
behavior changes from library code is exceedingly complicated and error-prone.
String
constant?Use integer constants and @IntDef
if the namespace for values is not extensible outside of your package. Use string constants if the namespace is shared or can be extended by code outside of your package.
Data classes represent a set of immutable properties and provide a small and well-defined set of utility functions for interacting with that data.
Do not use data class
in public Kotlin APIs, as the Kotlin compiler does not guarantee language API or binary compatibility for generated code. Instead, manually implement the required functions.
In Java, data classes should provide a constructor when there are few properties or use the Builder
pattern when there are many properties.
In Kotlin, data classes should provide a constructor with default arguments regardless of the number of properties. Data classes defined in Kotlin may also benefit from providing a builder when targeting Java clients.
In cases where data needs to be modified, provide either a Builder
with a copy constructor (Java) or a copy()
member function (Kotlin) that returns a new object.
When providing a copy()
function in Kotlin, arguments must match the class‘s constructor and defaults must be populated using the object’s current values.
class Typography( val labelMedium: TextStyle = TypographyTokens.LabelMedium, val labelSmall: TextStyle = TypographyTokens.LabelSmall ) { fun copy( labelMedium: TextStyle = this.labelMedium, labelSmall: TextStyle = this.labelSmall ): Typography = Typography( labelMedium = labelMedium, labelSmall = labelSmall ) }
Data classes should implement both equals()
and hashCode()
, and every property must be accounted for in the implementations of these methods.
Data classes may implement toString()
with a recommended format matching Kotlin's data class implementation, e.g. User(var1=Alex, var2=42)
.