Introducing Spork 4.0.0
I’ve been working hard on Spork 4.0.0 since last summer.
Spork is a high performance runtime annotation processing framework with implementations for Android and for dependency injection. It is intended as a replacement for Butter Knife and/or Dagger 2, that’s why its functionality is heavily modelled onto the design of these libraries.
Let’s take a look at the Android and dependency injection features…
Spork for Android
Here’s a code snippet of several Android bindings with the spork-android
dependency:
@BindLayout(R.layout.activity_download)
public class DownloadActivity extends Activity {
@BindView(R.id.download_button)
private Button downloadButton;
@BindFragment(R.id.details_fragment)
private DetailsFragment fragment;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Spork.bind(this); // bind() wires it all up!
}
@BindClick(R.id.other_button)
private void onClickButton(Button someButton) {
downloadManager.startDownload();
}
}
By calling Spork.bind()
in onCreate()
the View
, OnClickListener
and the Fragment
are
all automatically resolved without the need for all that boilerplate code.
This is all pretty much the same as with Spork 3.x
These are all the supported annotations:
@BindLayout
@BindView
@BindFragment
@BindResource
@BindClick
Spork Dependency Injection
The spork-inject
library creates instances of your classes and satisfies their dependencies.
Let’s first take a look at a regular constructor injection without spork-inject
:
class CoffeeMug {
private Coffee coffee;
private Mug mug;
CoffeeMug(Coffee coffee, Mug mug) {
this.coffee = coffee;
this.mug = mug;
}
}
Creating a CoffeeMug
requires you to pass along its dependencies manually.
This is not a difficult task for a simple object with simple dependencies,
but it gets a lot more tedious with scopes and lifecycle considerations. Spork takes care of all that.
Spork can inject fields directly. In this example it obtains a Coffee
and a Mug
instance for the respective fields:
class CoffeeMug {
@Inject private Coffee coffee;
@Inject private Mug mug;
...
}
Spork also supports method injection, but Field injection is generally preferred.
Declaring Dependencies
In the above sample, a Coffee
and Mug
are injected. Of course these dependencies must come from somewhere.
Dependencies should be defined in a Module
:
@Provides
public Coffee provideCoffee() {
return new BlackCoffee();
}
A @Provides
method can require dependencies on its own. These are passed on as method arguments and
they are automatically resolved by Spork:
@Provides
public Coffee provideCoffee(Water water, CoffeeBeans beans) {
return new BlackCoffee(water, beans);
}
Modules
The @Provides
-annotated methods above are placed in a Module
. Modules are POJO objects that define a set of dependencies:
public class BrewModule {
@Provides
public Mug provideMug() {
return new MugWithPrint("Input Java, output Java.");
}
@Provides
public Water provideWater() {
return new BoilingWater();
}
@Provides
public Beans provideBeans() {
return new ArabicaBeans();
}
@Provides
public Coffee provideCoffee(Water water, CoffeeBeans beans) {
return new BlackCoffee(water, beans);
}
}
Building an ObjectGraph
One or more modules are used to build an object graph. Object graphs hold state such as your singletons and named instances.
Creating an ObjectGraph
is easy:
ObjectGraph objectGraph = ObjectGraphs.builder()
.module(new BrewModule())
.build();
When putting it all together, the CoffeeMug
can now be injected with an ObjectGraph
made with the BrewModule
:
class CoffeeMug {
@Inject private Coffee coffee;
@Inject private Mug mug;
public CoffeeMug() {
ObjectGraphs.builder()
.module(new BrewModule())
.build()
.inject(this); // same as calling Spork.bind(this, objectGraph)
}
}
Scoped injection
A scoped instance is an instance that belongs to a specific ObjectGraph
created at a specific level of the application. It is tied to the lifecycle of that ObjectGraph
.
@Singleton
is a predefined scope that is always available at the root ObjectGraph
in your application. It is tied to the lifecycle of that ObjectGraph
.
@Provides
methods in a module can specify a scope. It can be used like this:
@Provides
@Singleton
public CoffeeService provideCoffeeService() {
return new CoffeeServiceImpl();
}
Scopes can also be made custom.
Check out the full spork-inject User Guide for more details.
Qualifiers
In some cases, you might want to identify an injection by some kind of identifier. This is done with a qualifier.
The @Named
annotation is a qualifier that is available by default. It can be used in a module:
class WaterModule {
@Provides
@Named("cold")
public Water provideColdWater() {
...
}
@Provides
@Named("hot")
public Water provideHotWater() {
...
}
}
WaterModule
can then be used to inject a Faucet
class with the same annotation:
class Faucet {
@Inject @Named("cold") Water coldWater;
@Inject @Named("hot") Water hotWater;
...
}
You can even create your own qualifier annotations.
Dependencies
All dependencies are hosted on jcenter, which is the default repository when developing Android projects.
To use dependency injection:
dependencies {
compile 'com.bytewelder.spork:spork-inject:4.0.0'
}
To use Android bindings:
dependencies {
compile 'com.bytewelder.spork:spork-android:4.0.0@aar'
}
To use Android bindings with AppCompat/Support:
dependencies {
compile 'com.bytewelder.spork:spork-android-support:4.0.0@aar' {
exclude group: 'com.android.support'
}
}
Closing words
Check out the Spork website or the documentation for more information.
Please file bug reports and feature requests at Spork’s GitHub repo.