Annotation Processor
A notable piece of trivia about this library is that, at first, JSignal’s standard component library was written in Kotlin. The reasons for switching to Java are outside the scope of this article, but one of the most attractive features of Kotlin, from the standpoint of component design, is it’s support for named arguments because it largley replaces the boilerplate builder pattern.
While JSignal is designed to be entierly usable without any compile time processing, writing builders for components can be quite tedious and also error prone for those unfamiliar with the workings of it’s reactive system. For those reasons, the library provides a fairly simple annotation processor for generating component builders.
Usage
A typical component signature in JSignal looks like this:
To automatically create a builder, components must be annotated with @GeneratePropComponent
and instead of extending Component
will extend a class called <component name>PropComponent
(this is the name of the class generated by the annotation processor). This new parent class will exist on the classpath in the same package as the component, so it is important to ensure that the package does not already contain a class with that name. Below is an example for our imaginary button component:
The ButtonPropComponent
will contain a static inner class called Builder
with setters for each of the component’s properties. It will also contain a static builder
method to instantiate the builder, and an empty protected onBuild
method, meant to be overridden, that is called after all the properties have been set.
Properties
When generating the builder, the annotation processor scans the component class for fields annotated with @Prop
. These fields must either be public or package protected, and in order for them to be reactive (though they aren’t required to be) they must use the Supplier
type. Let’s add some properties to our button example:
Now the button component can be instantiated like so:
By default, properties are considered optional and do not need to be set before calling build
, but required properties are also supported using @Prop(required = true)
. The processor generates fluent builder interfaces for ensuring each required property gets set, using the order they are declared in.