Learn what the support annotations are, and why you should use them
[tl;dr: use the support annotations and code like a bawss]
You have surely noticed that sometimes Android Studio complains about something being annotated in the signature of the method you’re overriding, but not in your code:
What does that @NonNull
parameter annotation mean, and where does it come from?
On Support Annotations
The @NonNull
annotation is included in the Android Support Annotations package. That package is among those that are installed in the local repository by the SDK Manager as part of the Support Library bundle.
What those annotations do is help you write meaningful contracts for your APIs. @NonNull
is just an example — there are a lot of other annotations in the package. There are annotations to specify that an int
parameter is supposed to take a resource ID of a given type, the thread a method is supposed to be run on, and more.
Android Studio can use those annotations to help you avoid bugs (NPEs, unresolved resources, threading issues), and from v1.3.0 of the Android Gradle plugin, so will the CLI Lint. The Android SDK has been annotated with the support annotations to help you avoid pitfalls and common issues.
Before diving into the annotations themselves, you’ll need to add the annotations to your app’s compile dependencies:
compile 'com.android.support:support-annotations:23.0.1'
Protip
Note that, starting with version 20 of the support library, the support-v4
library depends on the support-annotations
, so you don’t have to include them explicitly. If you have any dependencies that include support-v4
, such as appcompat-v7
, in your app, then you’re all set.
Show us the goods!
Now that you have a brand-spanking-new dependency in your project, you want to jump straight to using this newly found arsenal of annotations.
Let’s take your APIs from zero to hero!
Nullability
There are two nullability annotations, our dearest @NonNull
and its counterpart @Nullable
. They can be applied to fields, parameters and methods (in which case they refer to the return value).
What they do is pretty obvious: @NonNull
means that that symbol is expected to never be null
, whereas @Nullable
means that you should guard against null
values for it. Note that the @Nullable
annotation should only be used if that symbol can be null
in normal conditions, not if it can be null
if misused.
Android Studio uses them as hint for its static analysis tools and the Infer nullity analysis.
Resources
When you pass around a resource ID, it looks like a normal integer value. There’s no way of telling if you’re passing, say, a string ID to a method that expects a colour. This is, unless you use the resources annotations.
Using these annotations you can explicitly state that an int value is supposed to be any resource ID (@AnyRes
) or a specific resource ID (@*Res
), and the tools can check the contract at compile time.
There are a bunch of resources annotations, one for each type of resource: @AnimatorRes
, @AnimRes
, @AnyRes
, @ArrayRes
, @AttrRes
, @BoolRes
, @ColorRes
, @DimenRes
, @DrawableRes
, @FractionRes
, @IdRes
, @IntegerRes
, @InterpolatorRes
, @LayoutRes
, @MenuRes
, @PluralsRes
, @RawRes
, @StringRes
, @StyleableRes
, @StyleRes
, @XmlRes
.
Values
Similar to the way you can mark integers to be resource IDs, you can also specify that they are representing a colour (with the @ColorInt
annotation), or that their values must fall within a certain range.
To specify a range for an integer, you can use @IntRange
; for a float, its counterpart @FloatRange
. You can even specify the expected size of arrays using the @Size
annotation.
TypeDefs
As you probably already noticed, the Android framework prefers integer flags to enums. This is done to save memory, as enum values are object instances and take up way more memory than a simple int.
On the other hand, you lose any kind of type safety and check over which kind of values you can bundle up together.
You can use the @interface
and @IntDef
(or @StringDef
, if you want to use strings instead) annotations to define a “virtual enum” annotation that you can then use to mark int symbols as if they were an enum.
Refer to the Creating Enumerated Annotations writeup on the Android Developers website for further details on how to implement TypeDefs. Please note that by default these annotated types are assumed to only assume one of the allowed types. If you want to be able to combine more @IntDef
values together, you need to set the flag
property to true for the @IntDef
.
Proguard
In the future you’ll be able to auto-generate Proguard rules by simply annotating symbols and classes with the @Keep
annotation.
At the time of writing the feature is still being developed, because the Android Gradle plugin is not picking those annotations up yet.
Thread safety
There are annotations that you can use to mark some method (or whole class) that need to be run on a specific thread: @UiThread
, @MainThread
, @WorkerThread
, @BinderThread
.
The difference between @MainThread
and @UiThread
is that an app has only one main thread, which is also a UI thread, but can spawn multiple UI threads (one per window). Usually you want to tie lifecycle events to the main thread, and all UI logic to a UI thread.
Bits and bobs
In this section we’ll quickly look at the rest of the annotations in the package, that can’t be easily split into categories.
This set of annotations is what I call architecture annotations. They’re defining a contract that your code should respect when calling into some methods, or just providing some context on some aspects of a class’ API.
The @CallSuper
annotation is making explicit that when you override a method from the superclass, you have to call through to super
. This really just makes it obvious and shows a warning, but should be always the case.
The @CheckResult
annotation is used to remark the fact that the return value of a method should really not be ignored, and it’s neat as it allows you to specify a suggested usage for the return value that the IDE will show in the warning should this contract be violated. This is how the checkSelfPermission()
method is annotated, for example.
Lastly, we have the @VisibleForTesting
annotation. This isn’t really used by the IDE in any way, as far as I could tell when looking into it. What this does is explicit the reason why a method, or a class, has a broader scope than strictly needed: you’re using it for testing. While you should always minimise the need for this, it’s good to let other devs know that you didn’t mistakenly leave that method as public instead of package private.
Want to know more?
You can watch my Tools of the Trade talk recording where I go through the XML tools namespace, the Support Annotations, and more. You can find it here: