These are rules about various specifics in methods, around parameters, method names, return types, and access specifiers.
java.time.*
types where possiblejava.time.Duration
, java.time.Instant
and many other java.time.*
types are available on all platform versions through desugaring and should be preferred when expressing time in API parameters or return values.
Libraries targeting SDK < 26 must not use java.time.*
until the AAR format supports advertising core library desugaring requirements (see b/203113147).
Prefer exposing only variants of an API that accept or return java.time.Duration
or java.time.Instant
and omit primitive variants with the same functionality unless the API domain is one where object allocation in intended usage patterns would have a prohibitive performance impact.
If a time value expresses the duration of time involved, name the parameter “duration”, not “time”.
ValueAnimator.setTime(java.time.Duration);
ValueAnimator.setDuration(java.time.Duration);
Exceptions:
“timeout” is appropriate when the duration specifically applies to a timeout value.
“time” with a type of java.time.Instant
is appropriate when referring to a specific point in time, not a duration.
long
Methods accepting or returning durations as a primitive should suffix the method name with the associated time units (e.g. Millis
, Nanos
, Seconds
) to reserve the undecorated name for use with java.time.Duration
. See Time.
Methods should also be annotated apporiately with their unit and time base:
@CurrentTimeMillisLong
: Value is a non-negative timestamp measured as the number of milliseconds since 1970-01-01T00:00:00Z.@CurrentTimeSecondsLong
: Value is a non-negative timestamp measured as the number of seconds since 1970-01-01T00:00:00Z.@DurationMillisLong
: Value is a non-negative duration in milliseconds.@ElapsedRealtimeLong
: Value is a non-negative timestamp in the SystemClock.elapsedRealtime()
time base.@UptimeMillisLong
: Value is a non-negative timestamp in theSystemClock.uptimeMillis()
time base.Primitive time parameters or return values should use long
, not int
.
ValueAnimator.setDuration(@DurationMillisLong long);
ValueAnimator.setDurationNanos(long);
public void setIntervalNs(long intervalNs); public void setTimeoutUs(long timeoutUs);
public void setIntervalNanos(long intervalNanos); public void setTimeoutMicros(long timeoutMicros);
long
time argumentsThe platform includes several annotations to provide stronger typing for long
-type time units:
@CurrentTimeMillisLong
: Value is a non-negative timestamp measured as the number of milliseconds since 1970-01-01T00:00:00Z
, e.g. in the System.currentTimeMillis()
time base.@CurrentTimeSecondsLong
: Value is a non-negative timestamp measured as the number of seconds since 1970-01-01T00:00:00Z
.@DurationMillisLong
: Value is a non-negative duration in milliseconds.@ElapsedRealtimeLong
: Value is a non-negative timestamp in the SystemClock#elapsedRealtime()
time base.@UptimeMillisLong
: Value is a non-negative timestamp in the SystemClock#uptimeMillis()
time base.For all methods expressing a unit of measurement other than time, prefer CamelCased SI unit prefixes.
public long[] getFrequenciesKhz(); public float getStreamVolumeDb();
If you have overloads of a method with optional parameters, keep those parameters at the end and keep consistent ordering with the other parameters:
public int doFoo(boolean flag); public int doFoo(int id, boolean flag);
public int doFoo(boolean flag); public int doFoo(boolean flag, int id);
When adding overloads for optional arguments, the behavior of the simpler methods should behave in exactly the same way as if default arguments had been provided to the more elaborate methods.
Corollary: Don’t overload methods other than to add optional arguments or to accept different types of arguments if the method is polymorphic. If the overloaded method does something fundamentally different, then give it a new name.
Note: The guideline on placement of single abstract method parameters (ex. Runnable
, listeners) overrides this guideline. In cases where a developer could reasonably expected to write the body of a SAM class as a lambda, the SAM class parameter should be placed last.
Note: The guideline on use of Executors overrides this guideline, as it allows for an overload that omits an Executor
, even though it is not the final argument in the parameter list.
@JvmOverloads
(Kotlin only)Methods and constructors with default parameters must be annotated with @JvmOverloads
to ensure they maintain binary compatibility.
See Function overloads for defaults in the official Kotlin-Java interop guide for more details.
class Greeting @JvmOverloads constructor( loudness: Int = 5 ) { @JvmOverloads fun sayHello(prefix: String = "Dr.", name: String) = // ... }
If a method has shipped with a parameter with a default value, removal of the default value is a source-breaking change.
If you have a method with multiple parameters, put the most relevant ones first. Parameters that specify flags and other options are less important than those that describe the object that is being acted upon. If there is a completion callback, put it last.
public void openFile(int flags, String name); public void openFileAsync(OnFileOpenedListener listener, String name, int flags); public void setFlags(int mask, int flags);
public void openFile(String name, int flags); public void openFileAsync(String name, int flags, OnFileOpenedListener listener); public void setFlags(int flags, int mask);
See also: Put optional parameters at end in overloads
The Builder pattern is recommended for creating complex Java objects, and is commonly used in Android for cases where:
Consider whether you need a builder. Builders are useful in an API surface if they are used to:
If you have a type with three or fewer required parameters and no optional parameters you can almost always skip a builder and use a plain constructor.
Kotlin-sourced classes should prefer @JvmOverloads
-annotated constructors with default arguments over Builders, but may choose to improve usabilty for Java clients by also providing Builders in the cases outlined above.
class Tone @JvmOverloads constructor( val duration: Long = 1000, val frequency: Int = 2600, val dtmfConfigs: List<DtmfConfig> = emptyList() ) { class Builder { // ... } }
Builder classes must enable method chaining by returning the Builder object (e.g. this
) from every method except build()
. Additional built objects should be passed as arguments -- do not return a different object’s builder. For example:
public static class Builder { public void setDuration(long); public void setFrequency(int); public DtmfConfigBuilder addDtmfConfig(); public Tone build(); }
public class Tone { public static class Builder { public Builder setDuration(long); public Builder setFrequency(int); public Builder addDtmfConfig(DtmfConfig); public Tone build(); } }
In rare cases where a base builder class must support extension, use a generic return type:
public abstract class Builder<T extends Builder<T>> { abstract T setValue(int); } public class TypeBuilder<T extends TypeBuilder<T>> extends Builder<T> { T setValue(int); T setTypeSpecificValue(long); }
To ensure consistent builder creation through Android API surface, all the builders must be created through a constructor and not a static creator method. For Kotlin-based APIs, the Builder
must be public even if Kotlin users are expected to implicitly leverage the builder through a factory method/DSL style creation mechanism. Libraries must not use @PublishedApi internal
to selectively hide the Builder
class constructor from Kotlin clients.
public class Tone { public static Builder builder(); public static class Builder { } }
public class Tone { public static class Builder { public Builder(); } }
@NonNull
)Optional, e.g. @Nullable
, arguments should be moved to setter methods. The builder constructor should throw an NullPointerException
(consider using Objects.requireNonNull
) if any required arguments are not specified.
For the sake of logical organization within a package, builder classes should typically be exposed as final inner classes of their built types, ex. Tone.Builder
rather than ToneBuilder
.
Builders may include a copy constructor to create a new builder instance from an existing builder or built object. They should not provide alternative methods for creating builder instances from existing builders or build objects.
public class Tone { public static class Builder { public Builder clone(); } public Builder toBuilder(); }
public class Tone { public static class Builder { public Builder(Builder original); public Builder(Tone original); } }
@Nullable
arguments if the builder has copy constructorResetting is essential if a new instance of a builder may be created from an existing instance. If no copy constructor is available, then the builder may have either @Nullable
or @NonNullable
arguments.
public static class Builder { public Builder(Builder original); public Builder setObjectValue(@Nullable Object value); }
@Nullable
arguments for optional propertiesIt's often simpler to use a nullable value for second-degree input, especially in Kotlin, which utilizes default arguments instead of builders and overloads.
Additionally, @Nullable
setters will match them with their getters, which must be @Nullable
for optional properties.
Value createValue(@Nullable OptionalValue optionalValue) { Value.Builder builder = new Value.Builder(); if (optionalValue != null) { builder.setOptionalValue(optionalValue); } return builder.build(); }
Value createValue(@Nullable OptionalValue optionalValue) { return new Value.Builder() .setOptionalValue(optionalValue); .build(); } // Or in other cases: Value createValue() { return new Value.Builder() .setOptionalValue(condition ? new OptionalValue() : null); .build(); }
Common usage in Kotlin:
fun createValue(optionalValue: OptionalValue? = null) = Value.Builder() .apply { optionalValue?.let { setOptionalValue(it) } } .build()
fun createValue(optionalValue: OptionalValue? = null) = Value.Builder() .setOptionalValue(optionalValue) .build()
The default value (if the setter is not called), and the meaning of null
, must be properlty documented in both the setter and the getter.
/** * ... * * <p>Defaults to {@code null}, which means the optional value will not be used. */
If your class has mutable properties and needs a Builder
class, first ask yourself whether your class should actually have mutable properties.
Next, if you're certain that you need mutable properties, decide which of the following scenarios works better for your expected use case:
The built object should be immediately usable, thus setters should be provided for all relevant properties whether mutable or immutable.
map.put(key, new Value.Builder(requiredValue) .setImmutableProperty(immutableValue) .setUsefulMutableProperty(usefulValue) .build());
Some additional calls may need to be made before the built object can be useful, thus setters should not be provided for mutable properties.
Value v = new Value.Builder(requiredValue) .setImmutableProperty(immutableValue) .build(); v.setUsefulMutableProperty(usefulValue) Result r = v.performSomeAction(); Key k = callSomeMethod(r); map.put(k, v);
Don't mix the two scenarios.
Value v = new Value.Builder(requiredValue) .setImmutableProperty(immutableValue) .setUsefulMutableProperty(usefulValue) .build(); Result r = v.performSomeAction(); Key k = callSomeMethod(r); map.put(k, v);
Getter should be on the built object, not the builder.
public class Tone { public static class Builder { public Builder setDuration(long); public Builder setFrequency(int); public Builder addDtmfConfig(DtmfConfig); public Tone build(); } }
public class Tone { public static class Builder { public Builder setDuration(long); public Builder setFrequency(int); public Builder addDtmfConfig(DtmfConfig); public Tone build(); } public long getDuration(); public int getFrequency(); public @NonNull List<DtmfConfig> getDtmfConfigs(); }
Builder methods names should use setFoo()
/ addFoo()
/ clearFoo()
style.
build()
methods must return @NonNull
objectsA builder's build()
method is expected to return a non-null instance of the constructed object. In the event that the object cannot be created due to invalid parameters, validation can be deferred to the build method and an IllegalStateException
should be thrown.
Methods in the public API should not use the synchronized
keyword. This keyword causes your object/class to be used as the lock, and since it’s exposed to others, you may encounter unexpected side effects if other code outside your class starts using it for locking purposes.
Instead, perform any required locking against an internal, private object.
public synchronized void doThing() { ... }
private final Object mThingLock = new Object(); public void doThing() { synchronized (mThingLock) { ... } }
is
prefix for boolean accessor methodsThis is the standard naming convention for boolean methods and fields in Java. Generally, boolean method and variable names should be written as questions that are answered by the return value.
Java boolean accessor methods should follow a set
/is
naming scheme and fields should prefer is
, as in:
// Visibility is a direct property. The object "is" visible: void setVisible(boolean visible); boolean isVisible(); // Factory reset protection is an indirect property. void setFactoryResetProtectionEnabled(boolean enabled); boolean isFactoryResetProtectionEnabled(); final boolean isAvailable;
Using set
/is
for Java accessor methods or is
for Java fields will allow them to be used as properties from Kotlin:
obj.isVisible = true obj.isFactoryResetProtectionEnabled = false if (!obj.isAvailable) return
Properties and accessor methods should generally use positive naming, e.g. Enabled
rather than Disabled
. Using negative terminology inverts the meaning of true
and false
and makes it more difficult to reason about behavior.
// Passing false here is a double-negative. void setFactoryResetProtectionDisabled(boolean disabled);
In cases where the boolean describes inclusion or ownership of a property, you may use has rather than is; however, this will not work with Kotlin property syntax:
// Transient state is an indirect property used to track state // related to the object. The object is not transient; rather, // the object "has" transient state associated with it: void setHasTransientState(boolean hasTransientState); boolean hasTransientState();
Some alternative prefixes that may be more suitable include can and should:
// "Can" describes a behavior that the object may provide, // and here is more concise than setRecordingEnabled or // setRecordingAllowed. The object "can" record: void setCanRecord(boolean canRecord); boolean canRecord(); // "Should" describes a hint or property that is not strictly // enforced, and here is more explicit than setFitWidthEnabled. // The object "should" fit width: void setShouldFitWidth(boolean shouldFitWidth); boolean shouldFitWidth();
Methods that toggle behaviors or features may use the is prefix and Enabled suffix:
// "Enabled" describes the availability of a property, and is // more appropriate here than "can use" or "should use" the // property: void setWiFiRoamingSettingEnabled(boolean enabled) boolean isWiFiRoamingSettingEnabled()
Similarly, methods that indicate the dependency on other behaviors or features may use is prefix and Supported / Required suffix:
// "Supported" describes whether this API would work on devices that support // multiple users. The API "supports" multi-user: void setMultiUserSupported(boolean supported) boolean isMultiUserSupported()
// "Required" describes whether this API depends on devices that support // multiple users. The API "requires" multi-user: void setMultiUserRequired(boolean required) boolean isMultiUserRequired()
Generally, method names should be written as questions that are answered by the return value.
For a class property var foo: Foo
Kotlin will autogenerate get
/set
methods using a simple rule: prepend get
and uppercase the first character for the getter, and prepend set
and uppercase the first character for the setter. The above declaration will produce methods named public Foo getFoo()
and public void setFoo(Foo foo)
, respectively.
If the property is of type Boolean
an additional rule applies in name generation: if the property name begins with is
, then get
is not prepended for the getter method name, the property name itself is used as the getter. Therefore, prefer naming Boolean
properties with an is
prefix in order to follow the naming guideline above:
var isVisible: Boolean
If your property is one of the aforementioned exceptions and begins with an appropriate prefix, use the @get:JvmName
annotation on the property to manually specify the appropriate name:
@get:JvmName("hasTransientState") var hasTransientState: Boolean @get:JvmName("canRecord") var canRecord: Boolean @get:JvmName("shouldFitWidth") var shouldFitWidth: Boolean
See Use @IntDef
for bitmask flags for API guidelines regarding defining bitmask flags.
Two setter methods should be provided: one that takes a full bitstring and overwrites all existing flags and another that takes a custom bitmask to allow more flexibility.
/** * Sets the state of all scroll indicators. * <p> * See {@link #setScrollIndicators(int, int)} for usage information. * * @param indicators a bitmask of indicators that should be enabled, or * {@code 0} to disable all indicators * @see #setScrollIndicators(int, int) * @see #getScrollIndicators() */ public void setScrollIndicators(@ScrollIndicators int indicators); /** * Sets the state of the scroll indicators specified by the mask. To change * all scroll indicators at once, see {@link #setScrollIndicators(int)}. * <p> * When a scroll indicator is enabled, it will be displayed if the view * can scroll in the direction of the indicator. * <p> * Multiple indicator types may be enabled or disabled by passing the * logical OR of the desired types. If multiple types are specified, they * will all be set to the same enabled state. * <p> * For example, to enable the top scroll indicator: * {@code setScrollIndicators(SCROLL_INDICATOR_TOP, SCROLL_INDICATOR_TOP)} * <p> * To disable the top scroll indicator: * {@code setScrollIndicators(0, SCROLL_INDICATOR_TOP)} * * @param indicators a bitmask of values to set; may be a single flag, * the logical OR of multiple flags, or 0 to clear * @param mask a bitmask indicating which indicator flags to modify * @see #setScrollIndicators(int) * @see #getScrollIndicators() */ public void setScrollIndicators(@ScrollIndicators int indicators, @ScrollIndicators int mask);
One getter should be provided to obtain the full bitmask.
/** * Returns a bitmask representing the enabled scroll indicators. * <p> * For example, if the top and left scroll indicators are enabled and all * other indicators are disabled, the return value will be * {@code View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_LEFT}. * <p> * To check whether the bottom scroll indicator is enabled, use the value * of {@code (getScrollIndicators() & View.SCROLL_INDICATOR_BOTTOM) != 0}. * * @return a bitmask representing the enabled scroll indicators */ @ScrollIndicators public int getScrollIndicators();
public
instead of protected
Always prefer public
to protected
in public API. Protected access ends up being painful in the long run, because implementers have to override to implement the functionality in cases where external access would have been just as good.
Remember that protected
visibility does not prevent developers from calling an API -- it only makes it slightly more obnoxious.
equals()
and hashCode()
If you override one, you must override the other.
toString()
for data classesData classes are encouraged to override toString()
, to help developers debug their code.
Decide whether you want program behavior to rely on your implementation or not. For example, UUID.toString() and File.toString() document their specific format for programs to use. If you are exposing information for debugging only, like Intent, simply inherit docs from the superclass.
All the information available from toString()
should also be available through the public API of the object. Otherwise, you are encouraging developers to parse and rely on your toString()
output, which will prevent future changes. A good practice is to implement toString()
using only the object's public API.
While it's impossible to prevent developers from depending on debug output, including the System.identityHashCode
of your object in its toString()
output will make it very unlikely that two different objects will have equal toString()
output.
@Override public String toString() { return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) + " {mFoo=" + mFoo + "}"; }
This can effectively discourage developers from writing test assertions like assertThat(a.toString()).isEqualTo(b.toString())
on your objects.
createFoo
when returning newly created objectsUse the prefix create
, not get
or new
, for methods that will create return values, e.g. by constructing new objects.
When the method will create an object to return, make that clear in the method name.
public FooThing getFooThing() { return new FooThing(); }
public FooThing createFooThing() { return new FooThing(); }
File
objects should also accept streamsData storage locations on Android are not always files on disk. For example, content passed across user boundaries is represented as content://
Uri
s. To enable processing of various data sources, APIs which accept File
objects should also accept InputStream
and/or OutputStream
.
public void setDataSource(File file) public void setDataSource(InputStream stream)
If you need to communicate missing or null values, consider using -1
, Integer.MAX_VALUE
, or Integer.MIN_VALUE
.
public java.lang.Integer getLength() public void setLength(java.lang.Integer)
public int getLength() public void setLength(int value)
Avoiding class equivalents of primitive types avoids the memory overhead of these classes, method access to values, and, more importantly, autoboxing that comes from casting between primitive and object types. Avoiding these behaviors saves on memory and on temporary allocations that can lead to expensive and more frequent garbage collections.
Developer annotations were added to help clarify allowable values in various situations. This makes it easier for tools to help developers when they supply incorrect values (for example, passing an arbitrary int
when the framework requires one of a specific set of constant values). Use any and all of the following annotations when appropriate:
Important: The invariants specified by the annotations are not automatically asserted at runtime and must be manually checked. The annotations are only an indication to developers and used by the documentation generator and Android Lint.
Explicit nullabilty annotations are required for Java APIs, but the concept of nullability is part of the Kotlin language and nullability annotations should never be used in Kotlin APIs.
@Nullable
: Indicates that a given return value, parameter, or field can be null:
@Nullable public String getName() public void setName(@Nullable String name)
@NonNull
: Indicates that a given return value, parameter, or field cannot be null. Marking things as @Nullable
is relatively new to Android, so most of Android's API methods are not consistently documented. Therefore we have a tri-state of “unknown, @Nullable
, @NonNull
” which is why @NonNull
is part of the API guidelines.:
@NonNull public String getName() public void setName(@NonNull String name)
For Android platform docs, annotating your method parameters will automatically generate documentation in the form “This value may be null.” unless “null” is explicitly used elsewhere in the parameter doc.
Existing “not really nullable” methods: Existing methods in the API without a declared @Nullable
annotation may be annotated @Nullable
if the method can return null
under specific, obvious circumstances (e.g. findViewById()
). Companion @NotNull requireFoo()
methods that throw IllegalArgumentException
should be added for developers who do not want to null check.
Interface methods: new APIs should add the proper annotation when implementing interface methods, like Parcelable.writeToParcel()
(i.e, that method in the implementing class should be writeToParcel(@NonNull Parcel, int)
, not writeToParcel(Parcel, int)
); existing APIs that are lacking the annotations don't need to be “fixed”, though.
In Java, methods are recommended to perform input validation for @NonNull
parameters via Objects.requireNonNull()
and throw a NullPointerException
when the parameters are null. This is automatically performed in Kotlin.
Resource identifiers: Integer parameters that denote ids for specific resources should be annotated with the appropriate resource-type definition. There is an annotation for every type of resource, such as @StringRes
, @ColorRes
, and @AnimRes
, in addition to the catch-all @AnyRes
. For example:
public void setTitle(@StringRes int resId)
@IntDef
for constant setsMagic constants: String
and int
parameters that are meant to receive one of a finite set of possible values denoted by public constants should be annotated appropriately with @StringDef
or @IntDef
. These annotations allow you to create a new annotation that you can use that works like a typedef for allowable parameters. For example:
/** @hide */ @IntDef(prefix = {“NAVIGATION_MODE_”}, value = { NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS }) @Retention(RetentionPolicy.SOURCE) public @interface NavigationMode {} public static final int NAVIGATION_MODE_STANDARD = 0; public static final int NAVIGATION_MODE_LIST = 1; public static final int NAVIGATION_MODE_TABS = 2; @NavigationMode public int getNavigationMode(); public void setNavigationMode(@NavigationMode int mode);
Notice that the constants must be defined in the class that will use them, not in a subclass or interface.
Methods are recommended to check the validity of the annotated parameters and throw an IllegalArgumentException
if the parameter is not part of the @IntDef
@IntDef
for bitmask flagsThe annotation can also specify that the constants are flags, and can be combined with & and I:
/** @hide */ @IntDef(flag = true, prefix = { “FLAG_” }, value = { FLAG_USE_LOGO, FLAG_SHOW_HOME, FLAG_HOME_AS_UP, }); @Retention(RetentionPolicy.SOURCE) public @interface DisplayOptions {}
@StringDef
for string constant setsThere is also the @StringDef
annotation, which is exactly like @IntDef
above, but for String
constants. You can include multiple “prefix” values which are used to automatically emit documentation for all values.
@SdkConstant
for SDK constants@SdkConstant Annotate public fields when they are one of these SdkConstant
values: ACTIVITY_INTENT_ACTION
, BROADCAST_INTENT_ACTION
, SERVICE_ACTION
, INTENT_CATEGORY
, FEATURE
.
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_CALL = "android.intent.action.CALL";
To ensure API compatibility, the nullability of overrides should be compatible with the current nullability of the parent. The table below represents the compatibility expectations. Plainly, overrides should only be as restrictive or more restrictive than the element they override.
type | parent | child |
---|---|---|
return type | unannotated | unannotated | non-null |
return type | nullable | nullable | non-null |
return type | non-null | non-null |
fun argument | unannotated | unannotated | nullable |
fun argument | nullable | nullable |
fun argument | non-null | nullable | non-null |
@NonNull
) arguments where possibleWhen methods are overloaded, prefer that all arguments are non-null
.
public void startActivity(@NonNull Component component) { ... } public void startActivity(@NonNull Component component, @NonNull Bundle options) { ... }
This rule applies to overloaded property setters as well. The primary argument should be non-null
and clearing the property should be implemented as a separate method. This prevents “nonsense” calls where the developer must set trailing parameters even though they are not required.
public void setTitleItem(@Nullable IconCompat icon, @ImageMode mode) public void setTitleItem(@Nullable IconCompat icon, @ImageMode mode, boolean isLoading) // Nonsense call to clear property setTitleItem(null, MODE_RAW, false);
public void setTitleItem(@NonNull IconCompat icon, @ImageMode mode) public void setTitleItem(@NonNull IconCompat icon, @ImageMode mode, boolean isLoading) public void clearTitleItem()
Nullable
(e.g. @NonNull
) return types for containersFor container types -- Bundle
s, Collection
s, etc. -- return an empty (and immutable, where applicable) container. In cases where null
would be used to distinguish availability of a container, consider providing a separate boolean
method.
@NonNull public Bundle getExtras() { ... }
Note: Intent.getExtras()
returns a @Nullable
Bundle and specifies a case where it returns null
, but this was a mistake that should be avoided in future APIs.
get
/set
pairs must agreeGet/set method pairs for a single logical property should always agree in their nullability annotations. Failing to follow this guideline will defeat Kotlin's property syntax, and adding disagreeing nullability annotations to existing property methods is therefore a source-breaking change for Kotlin users.
@NonNull public Bundle getExtras() { ... } public void setExtras(@NonNull Bundle bundle) { ... }
All APIs should permit applications to react to errors. Returning false
, -1
, null
, or other catch-all values of “something went wrong” do not tell a developer enough about the failure to set user expectations or accurately track reliability of their app in the field. When designing an API, imagine that you are building an application. If you encounter an error, does the API give you enough information to surface it to the user or react appropriately?
@IntDef
, that means including an OTHER
or UNKNOWN
value - when returning a new code, you can check the caller‘s targetSdkVersion
to avoid returning an error code the application doesn’t know about. For exceptions, have a common superclass that your exceptions implement, so that any code that handles that type will also catch and handle subtypes.@CheckResult
.Prefer throwing a ? extends RuntimeException
when a failure or error condition is reached due to something that the developer did wrong, for example ignoring constraints on input parameters or failing to check observable state.
Setter or action (ex. perform
) methods may return an integer status code if the action may fail as a result of asynchronously-updated state or conditions outside the developer’s control.
Status codes should be defined on the containing class as public static final
fields, prefixed with ERROR_
, and enumerated in an @hide
@IntDef
annotation.
The name of the method should always begin with the verb (e.g. get
, create
, reload
, etc.), not the object you’re acting on.
public void tableReload() { mTable.reload(); }
public void reloadTable() { mTable.reload(); }
Collection<T>
types over arrays as return or parameter typeGenerically-typed collection interfaces provide several advantages over arrays, including stronger API guarantees around uniqueness and ordering, support for generics, and a number of developer-friendly convenience methods.
If the elements are primitives, do prefer arrays instead, in order to avoid the cost of auto-boxing. See Take and return raw primitives instead of boxed versions
In certain scenarios, where the API is used in performance-sensitive code (like graphics or other measure/layout/draw APIs), it is ok to use arrays instead of collections in order to reduce allocations and memory churn.
Kotlin arrays are invariant and the Kotlin language provides ample utility APIs around arrays, so arrays are on-par with List
and Collection
for Kotlin APIs intended to be accessed from Kotlin.
@NonNull
collectionsAlways prefer @NonNull
for collection objects. When returning an empty collection, use the appropriate Collections.empty
method to return a low-cost, correctly-typed, and immutable collection object.
Where type annotations are supported, always prefer @NonNull
for collection elements.
You should also prefer @NonNull
when using arrays instead of collections (see previous item). If object allocation is a concern, create a constant and pass it along - after all, an empty array is immutable. Example:
private static final int[] EMPTY_USER_IDS = new int[0]; @NonNull public int[] getUserIds() { int [] userIds = mService.getUserIds(); return userIds != null ? userIds : EMPTY_USER_IDS; }
Kotlin APIs should prefer read-only (e.g. not Mutable
) return types for collections by default unless the API contract specifically requires a mutable return type.
Java APIs, however, should prefer mutable return types by default since the Android platform‘s implementation of Java APIs does not yet provide a convenient implementation of immutable collections. The exception to this rule is Collections.empty
return types, which are immutable. In cases where mutability could be exploited by clients -- on purpose or by mistake -- to break the API’s intended usage pattern, Java APIs should strongly consider returning a shallow copy of the collection.
@Nullable public PermissionInfo[] getGrantedPermissions() { return mPermissions; }
@NonNull public Set<PermissionInfo> getGrantedPermissions() { if (mPermissions == null) { return Collections.emptySet(); } return new ArraySet<>(mPermissions); }
APIs that return collections should ideally not modify the returned collection object after returning. If the returned collection must change or be reused in some way -- for example, an adapted view of a mutable data set -- the precise behavior of when the contents can change must be explicitly documented and/or follow established API naming conventions.
/** * Returns a view of this object as a list of [Item]s. */ fun MyObject.asList(): List<Item> = MyObjectListWrapper(this)
The Kotlin .asFoo()
convention is described below and permits the collection returned by .asList()
to change if the original collection changes.
Similar to APIs that return collections, APIs that return data-type objects should ideally not modify the properties of the returned object after returning.
val tempResult = DataContainer() fun add(other: DataContainer): DataContainer { tempResult.innerValue = innerValue + other.innerValue return tempResult }
fun add(other: DataContainer): DataContainer { return DataContainer(innerValue + other.innerValue) }
In extremely limited cases, some performance-sensitive code may benefit from object pooling or reuse. Do not write your own object pool data structure and do not expose reused objects in public APIs. In either case, be extremely careful about managing concurrent access.
vararg
parameter typeBoth Kotlin and Java APIs are encouraged to use vararg
in cases where the developer would be likely to create an array at the call site for the sole purpose of passing multiple, related parameters of the same type.
public void setFeatures(Feature[] features) { ... } // Developer code setFeatures(new Feature[]{Features.A, Features.B, Features.C});
public void setFeatures(Feature... features) { ... } // Developer code setFeatures(Features.A, Features.B, Features.C);
Both Java and Kotlin implementations of vararg
parameters compile to the same array-backed bytecode and as a result may be called from Java code with a mutable array. API designers are strongly encouraged to create a defensive shallow copy of the array parameter in cases where it will be persisted to a field or anonymous inner class.
public void setValues(SomeObject... values) { this.values = Arrays.copyOf(values, values.length); }
Note that creating a defensive copy does not provide any protection against concurrent modification between the initial method call and the creation of the copy, nor does it protect against mutation of the objects contained in the array.
List<Foo>
is default option, but consider other types to provide additional meaning:
Use Set<Foo>
, if your API is indifferent to the order of elements and it doesn’t allow duplicates or duplicates are meaningless.
Collection<Foo>,
if your API is indifferent to the order and allows duplicates.
Note: Remember that Java Collections are mutable by default, so consider defensive copying for your return and parameter types. Another option for the return type is Collection.unmodifiable*
.
Kotlin frequently uses .toFoo()
and .asFoo()
to obtain an object of a different type from an existing object where Foo
is the name of the conversion's return type. This is consistent with the familiar JDK Object.toString()
. Kotlin takes this further by using it for primitive conversions such as 25.toFloat()
.
The distinction between conversions named .toFoo()
and .asFoo()
is significant:
.toFoo()
when creating a new, independent object {.numbered}Like .toString()
, a “to” conversion returns a new, independent object. If the original object is modified later, the new object will not reflect those changes. Similarly, if the new object is modified later, the old object will not reflect those changes.
fun Foo.toBundle(): Bundle = Bundle().apply { putInt(FOO_VALUE_KEY, value) }
.asFoo()
when creating a dependent wrapper, decorated object, or cast {.numbered}Casting in Kotlin is performed using the as
keyword. It reflects a change in interface but not a change in identity. When used as a prefix in an extension function, .asFoo()
decorates the receiver. A mutation in the original receiver object will be reflected in the object returned by asFoo()
. A mutation in the new Foo
object may be reflected in the original object.
fun <T> Flow<T>.asLiveData(): LiveData<T> = liveData { collect { emit(it) } }
Writing conversion functions outside of both the receiver and the result class definitions reduces coupling between types. An ideal conversion needs only public API access to the original object. This proves by example that a developer can write analogous conversions to their own preferred types as well.
Methods must not throw generic exceptions such as java.lang.Exception
or java.lang.Throwable
, instead an appropriate specific exception has to be used like java.lang.NullPointerException
to allow developers to handle exceptions without being overly broad.
Errors that are unrelated to the arguments provided directly to the publicly invoked method should throw java.lang.IllegalStateException
instead of java.lang.IllegalArgumentException
or java.lang.NullPointerException
.
These are the rules around the classes and methods used for listener/callback mechanisms.
Use MyObjectCallback
instead of MyObjectCallbacks
.
on<Something>
onFooEvent
signifies that FooEvent
is happening and that the callback should act in response.
Callback methods regarding events should be named to indicate whether the event has already happened or is in the process of happening.
For example, if the method is called after a click action has been performed:
public void onClicked()
However, if the method is responsible for performing the click action:
public boolean onClick()
When a listener or callback can be added or removed from an object, the associated methods should be named add/remove OR register/unregister. Be consistent with the existing convention used by the class or by other classes in the same package. When no such precedent exists, prefer add/remove.
Methods involving registering or unregistering callbacks should specify the whole name of the callback type.
public void addFooCallback(@NonNull FooCallback callback); public void removeFooCallback(@NonNull FooCallback callback);
public void registerFooCallback(@NonNull FooCallback callback); public void unregisterFooCallback(@NonNull FooCallback callback);
Do not add getFooCallback()
methods. This is a tempting escape hatch for cases where developers may want to chain an existing callback together with their own replacement, but it is brittle and makes the current state difficult to reason about for component developers. For example,
setFooCallback(a)
setFooCallback(new B(getFooCallback()))
a
and has no way to do so without knowledge of B
’s type, and B
having been built to allow such modifications of its wrapped callback.When registering callbacks that have no explicit threading expectations (pretty much anywhere outside the UI toolkit), it is strongly encouraged to include an Executor
parameter as part of registration to allow the developer to specify the thread upon which the callbacks will be invoked.
public void registerFooCallback( @NonNull @CallbackExecutor Executor executor, @NonNull FooCallback callback)
Note: Developers must provide a valid Executor
. The new @CallbackExecutor
annotation will add automatic documentation to tell developers about common default options. Also note that the callback argument is required to be last to enable idiomatic usage from Kotlin.
As an exception to our usual guidelines about optional parameters, it is ok to provide an overload omitting the Executor
even though it is not the final argument in the parameter list. If the Executor
is not provided, the callback should be invoked on the main thread using Looper.getMainLooper()
and this should be documented on the associated overloaded method.
/** * ... * Note that the callback will be executed on the main thread using * {@link Looper.getMainLooper()}. To specify the execution thread, use * {@link registerFooCallback(Executor, FooCallback)}. * ... */ public void registerFooCallback( @NonNull FooCallback callback) public void registerFooCallback( @NonNull @CallbackExecutor Executor executor, @NonNull FooCallback callback)
Executor
implementation gotchas: Note that the following is a valid executor!
public class SynchronousExecutor implements Executor { @Override public void execute(Runnable r) { r.run(); } }
This means that when implementing APIs that take this form, your incoming binder object implementation on the app process side must call Binder.clearCallingIdentity()
before invoking the app’s callback on the app-supplied Executor. This way any application code that uses Binder identity (e.g. Binder.getCallingUid()
) for permission checks correctly attributes the code running to the application and not to the system process calling into the app. If users of your API want the UID / PID information of the caller then this should be an explicit part of your API surface, rather than implicit based on where the Executor they supplied ran.
The above should be supported by your API. In performance-critical cases apps may need to run code either immediately or synchronously with feedback from your API. Accepting an Executor permits this. Defensively creating an additional HandlerThread or similar to trampoline from defeats this desirable use case.
If an app is going to run expensive code somewhere in their own process, let them. The workarounds that app developers will find to overcome your restrictions will be much harder to support in the long term.
Exception for single callback: when the nature of the events being reported calls for only supporting a single callback instance, use the following style:
public void setFooCallback( @NonNull @CallbackExecutor Executor executor, @NonNull FooCallback callback) public void clearFooCallback()
Handler
instead of Executor
?Android's Handler
was used as a standard for redirecting callback execution to a specific Looper
thread in the past. This standard was changed to prefer Executor
as most app developers manage their own thread pools, making the main or UI thread the only Looper
thread available to the app. Use Executor
to give developers the control they need to reuse their existing/preferred execution contexts.
Modern concurrency libraries like kotlinx.coroutines or RxJava provide their own scheduling mechanisms that perform their own dispatch when needed, which makes it important to provide the ability to use a direct executor (e.g. Runnable::run
) to avoid latency from double thread hops. (e.g. one hop to post to a Looper
thread via a Handler
followed by another hop from the app's concurrency framework.)
Exceptions to this guideline are rare. Common appeals for an exception include:
I have to use a Looper
because I need a Looper
to epoll
for the event. This exception request is granted as the benefits of Executor
described above cannot be realized in this situation.
I do not want app code to block my thread publishing the event. This exception request is typically not granted for code that runs in an app process. Apps that get this wrong are only hurting themselves, not impacting overall system health. Apps that get it right or use a common concurrency framework should not pay additional latency penalties.
Handler
is locally consistent with other similar APIs in the same class. This exception request is granted situationally. Preference is for Executor
-based overloads to be added, migrating Handler
implementations to use the new Executor
implementation. (myHandler::post
is a valid Executor
!) Depending on the size of the class, number of existing Handler
methods, and likelihood that developers would need to use existing Handler
based methods alongside the new method, an exception may be granted to add a new Handler
-based method.
If there is a way to add or register something, there should also be a way to remove/unregister it. The method
registerThing(Thing)
should have a matching
unregisterThing(Thing)
If it is reasonable for a developer to reuse a callback, provide an identifier object to tie the callback to the request.
class RequestParameters { public int getId() { ... } } class RequestExecutor { public void executeRequest( RequestParameters parameters, Consumer<RequestParameters> onRequestCompletedListener) { ... } }
NOTE: The identifying object may come in different forms. In View.onClick
the View that was clicked is returned through the callback.
Multiple-method callbacks should prefer interface
and use default
methods when adding to previously-released interfaces. Previously, this guideline recommended abstract class
due to the lack of default
methods in Java 7.
public interface MostlyOptionalCallback { void onImportantAction(); default void onOptionalInformation() { // Empty stub, this method is optional. } }
NOTE: The Eclipse guide to Evolving Java-based APIs cautions against using default
methods in cases where multiple inheritance is likely; however, this is rare for callbacks and in practice we have only seen method naming collisions on commonly-extended classes like Activity
.
android.os.OutcomeReceiver
when modeling a non-blocking function callOutcomeReceiver<R,E>
reports a result value R
when successful or E : Throwable
otherwise - the same things a plain method call can do. Use OutcomeReceiver
as the callback type when converting a blocking method that returns a result or throws an exception to a non-blocking async method:
interface FooType { // Before: public FooResult requestFoo(FooRequest request); // After: public void requestFooAsync(FooRequest request, Executor executor, OutcomeReceiver<FooResult, Throwable> callback); }
Async methods converted in this way always return void
. Any result that requestFoo
would return is instead reported to requestFooAsync
‘s callback
parameter’s OutcomeReceiver.onResult
by calling it on the provided executor
. Any exception that requestFoo
would throw is instead reported to the OutcomeReceiver.onError
method in the same way.
Using OutcomeReceiver
for reporting async method results also affords a simple Kotlin suspend fun
wrapper for async methods using the Continuation.asOutcomeReceiver
extension from androidx.core:core-ktx
:
suspend fun FooType.requestFoo(request: FooRequest): FooResult = suspendCancellableCoroutine { continuation -> requestFooAsync(request, Runnable::run, continuation.asOutcomeReceiver()) }
Extensions like the above enable Kotlin clients to call non-blocking async methods with the convenience of a plain function call without blocking the calling thread. These 1-1 extensions for platform APIs may be offered as part of the androidx.core:core-ktx
artifact in Jetpack when combined with standard version compatibility checks and considerations. See the documentation for asOutcomeReceiver for more information, cancellation considerations and samples.
Async methods that do not match the semantics of a method returning a result or throwing an exception when its work is complete should not use OutcomeReceiver
as a callback type. Instead consider one of the other options listed below.
API level 24 added the java.util.function.*
(reference docs) types, which offer generic SAM interfaces such as Consumer<T>
that are suitable for use as callback lambdas. In many cases, creating new SAM interfaces provides little value in terms of type safety or communicating intent while unnecessarily expanding the Android API surface area.
Consider using these generic interfaces, rather than creating new ones:
Runnable
: () -> Unit
Supplier<R>
: () -> R
Consumer<T>
: (T) -> Unit
Function<T,R>
: (T) -> R
Predicate<T>
: (T) -> Boolean
SAM parameters should be placed last to enable idiomatic usage from Kotlin, even if the method is being overloaded with additional parameters.
public void schedule(Runnable runnable) public void schedule(int delay, Runnable runnable)