AutoValue Extensions
This is the third article in a series on AutoValue. The first article introduced AutoValue, the code generating annotation processor for value types. The second took a more in depth look at the code generated by AutoValue, and the benefits of compile time code generation.
In a previous article introducing AutoValue, I briefly mentioned AutoValue Extensions. Now it’s time to go a bit more in depth to look at what extensions are, how they work, and how they can help you get even more out of AutoValue.
What are AutoValue Extenions⌗
As mentioned in the last article, AutoValue is a compile time code generator that generates all of the boilerplate code to enable immutable value types in Java. Extensions tie into this compile time generation and allow you to easily add functionality to these value types.
While AutoValue does a great job of creating value types, those value types are rarely used in isolation. Most apps interact with multiple libraries and services using these value types. These can be web services, which might serialize value types using JSON or Protocol Buffers, databases, and Androids Parcelable interface. AutoValue has no support for these protocols built in, since it is a focused library, and there’s no way to know which services users might use.
To provide support for unknown services, or provide customization to the generated code, AutoValue 1.2 introduced Extensions. By including an AutoValue extension in your dependencies, you can add functionality to the generated AutoValue value types, usually with minimal work from you.
How AutoValue Extensions Work⌗
If you recall from my previous AutoValue Deep Dive article, AutoValue works by generating a package private implementation of your abstract
class. AutoValue extensions work in the same way, taking cues from your abstract
class to determine what to generate, then adding functionality to the implementation via subclasses.
Discovery⌗
This first important thing to understand about AutoValue extensions is how they are discovered. AutoValue uses the Java ServiceLoader API to find the available extensions on the class path. This means that extensions become available by simply including the dependency in your build.gradle
file (or your pom.xml
for Maven).
Once AutoValue finds available extensions, it determines which extensions are applicable for each class by calling the extenion’s applicable()
method. This means that extensions can be selective and only run their generation code on classes that need it.
Each extension has unique requirements to determine applicability. For the Android Parcel Extensions, for instance, simply making your @AutoValue
annotated abstract class implement the Parcelable
interface determines whether or not the extension will generate code for it. Since the annotated class is abstract
, you don’t actually have to write any of the implementation, but can simply mark the class with the interface.
Implementation⌗
Extensions use a similar mechanism to AutoValue itself in their generation, namely creating subclasses of your @AutoValue
annotated class containing the implementation. In order to allow chaining of multiple extensions along with the AutoValue implementation, each extension generates it’s own subclass, starting with the AutoValue implementation, so all implementation is inherited along the way.
Behind the scenes, after determining which extensions apply to each class, AutoValue prefixes each implemented subclass with $
characters, and creates each as abstract classes, leaving only the final AutoValue_
prefixed class as a concrete implementation.
As an AutoValue user, you never have to worry about this structure, as no matter how many extensions are applied to a particular class, you will always use the AutoValue_
prefixed implementation within your annotated class.
One important thing to keep in mind is that there are certain extensions, like the Parcel extension, which are incompatible with others. The parcel extension, in particular, can only be the final class in the hierarchy, as opposed to an intermediate one, since it requires static member variables in order to fully comply with the Android Parcelable contract.
This shouldn’t be an issue for most users as, to my knowledge, there are no other extensions with this requirement at the time of this writing.
Existing Extensions⌗
While extension support is still a very new feature for AutoValue, there are already several existing extensions available. Here is a brief, non-exclusive, listing of some.
auto-value-parcel⌗
auto-value-parcel adds support for Android’s Parcelable
interface. By simply adding implements Parcelable
to your value types, the AutoValue Parcel extension generates a fully parcelable implementation of your value type without requiring wrapping or unwrapping to use.
auto-value-cursor⌗
auto-value-cursor from Gabriel Ittner generates code to marshal and unmarshal your value types from your database via Android Cursor
objects. It makes it easy to store and retrieve your value types in a database, including support for specific column naming that doesn’t necessarily have to match your property names, and de/serialization of non-natively supported types via @CursorAdapter
.
auto-value-redacted⌗
auto-value-redacted from Square allows you to customize the generated toString
method to redact properties, like passwords and credit card or other sensetive information.
auto-value-moshi⌗
auto-value-moshi generates type safe JsonAdapter
s for use with Square’s Moshi JSON serialization library. The generated adapters are optimized to use no reflection, enforce nullability, and ensure type safety.
auto-value-gson⌗
auto-value-gson is similar to auto-value-moshi
, but generates optimized TypeAdapter
s for Google’s Gson JSON serialization library.
auto-value-with⌗
auto-value-with makes it easy to make immutable copies of your value types with slightly different properties. If you need to make a copy of an object, but change the isRead
flag from true
to false
, then auto-value-with
is a great extension to check out.
Conclusion⌗
Extensions add powerful extensibility to AutoValue, and relieve you from having to write boilerplate code. By only generating code you actually need, they help reduce the size of your app. If you don’t find an extension that you need, check out the source of one of the existing extensions and create your own!