Anvil
Minimal UI library for Android inspired by React
Install / Use
/learn @anvil-ui/AnvilREADME
Anvil - reactive views for Android
<br/> <div> <img align="left" src="https://raw.githubusercontent.com/zserge/anvil/master/logo/ic_launcher.png" alt="logo" width="96px" height="96px" /> <p> Anvil is a small Java library for creating reactive user interfaces. Originally inspired by <a href="https://facebook.github.io/react/">React</a>, it suits well as a view layer for <a href="https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93viewmodel">MVVM</a> or <a href="http://redux.js.org/">Redux</a> design patterns. </p> </div> <br/>Features
- Super small (4 hand-written classes + 1 generated class)
- Easy to learn (top-level API is only 5 functions)
- Fast (uses no reflection¹)
- Efficient (views are updated lazily, if the data change didn't affect the view - it remains untouched)
- Easy to read declarative syntax
- Java 8 and Kotlin-friendly, but supports Java 6 as well
- XML layouts are supported, too
¹Reflection is still used to inflate views once (standard XML inflater does the same thing, so no performance loss here).
Installation
// build.gradle
repositories {
jcenter()
}
dependencies {
compile 'co.trikita:anvil-sdk15:0.5.0'
}
Anvil comes in multiple builds for different minimal SDK versions:
- anvil-sdk15 (ICS, 99.7% of devices)
- anvil-sdk19 (Kitkat, 94.3% of devices)
- anvil-sdk21 (Lollipop, 82.3% of devices)
API levels 16, 17, 18, 22 or 23 are not added because they had very few UI-related methods added.
Examples
Normally you would write your layouts in XMLs, then get your views by their IDs and set their listeners, finally you would observe your data and modify view properties as your data changes.
Anvil simplifies most of this boring routine.
First, add a static import that makes it much easier to write your view:
import static trikita.anvil.DSL.*;
Next, declare your layout, assign event listeners and bind data:
public int ticktock = 0;
public void onCreate(Bundle b) {
super.onCreate(b);
setContentView(new RenderableView(this) {
@Override
public void view() {
linearLayout(() -> {
size(MATCH, MATCH);
padding(dip(8));
orientation(LinearLayout.VERTICAL);
textView(() -> {
size(MATCH, WRAP);
text("Tick-tock: " + ticktock);
});
button(() -> {
size(MATCH, WRAP);
text("Close");
// Finish current activity when the button is clicked
onClick(v -> finish());
});
});
}
});
}
Here we've created a very simple reactive view and added it to our Activity. We've declared our layout (a LinearLayout with a TextView inside). We've defined styles (width, height, orientation and padding). We've set OnClickListener to the button. We've also bound a variable "ticktock" to the text property inside a TextView.
Next, let's update your views as your data changes:
ticktock++;
Anvil.render();
At this point your layout will be updated and the TextView will contain text "Tick-Tock: 1" instead of "Tick-Tock: 0". However the only actual view method being called would be setText().
You should know that there is no need to call Anvil.render() inside your event listeners, it's already triggered after each UI listener call:
public void view() {
linearLayout(() -> {
textView(() -> {
text("Clicks: " + numClicks);
});
button(() -> {
text("Click me");
onClick(v -> {
numClicks++; // text view will be updated automatically
});
});
});
}
Made with Anvil
You may find more Anvil examples for Java 6, Java 8 and Kotlin at
How it works
No magic. When a renderable object is being constructed there are 3 types of operations: push view, modify some attribute of the current view, and pop view. If you're familiar with incremental DOM - Anvil follows the same approach.
Pushing a view adds it as a child to the parent view from the top of the stack. Attribute modification simply sets the given property to the current view on top of the stack. Pop unwinds the stack.
When you mount this layout (assuming the name is "John"):
linearLayout(() -> {
textView(() -> {
text("Hello " + name);
});
});
It does the following sequence of actions:
Push LinearLayout (adding it to the root view)
Push TextView (adding it to the linear layout)
Attr text "Hello John" (calling setText of the TextView)
Pop
Pop
The only trick is that these actions are cached into a so called "virtual layout" - a tree-like structure matching the actual layout of views and their properties.
So when you call Anvil.render() next time it compares the sequence of
actions with the cache and skips property values if they haven't change. Which means on the next
Anvil.render() call the views will remain untouched. This caching technique
makes render a very quick operation (having a layout of 100 views, 10
attributes each you can do about 4000 render cycles per second!).
Now, if you modify the name from "John" to "Jane" and call Anvil.render() it will do the following:
Push LinearLayout (noop)
Push TextView (noop)
Attr text "Hello Jane" (comparing with "Hello John" from the pervious render,
noticing the difference and calling setText("Hello Jane") to the TextView)
Pop
Pop
So if you modify one of the variables "bound" to some of the attributes - the cache will be missed and attribute will be updated.
For all event listeners a "proxy" is generated, which delegates its
method calls to your actual event listener, but calls Anvil.render() after each
method. This is useful, because most of your data models are modified when
the user interacts with the UI, so you write less code without calling
Anvil.render() from every listener. Remember, no-op renders are very fast.
Supported languages and API levels
Anvil is written in Java 7, but its API is designed to use lambdas as well, so in modern times it's recommended to use Anvil with Java8/Retrolambda or Kotlin.
Syntax is a bit different for each language, but it's very intuitive anyway.
Java 6 without lambdas:
public void view() {
o (linearLayout(),
orientation(LinearLayout.VERTICAL),
o (textView(),
text("...")),
o (button(),
text("..."),
onClick(myListener)));
}
Java 8 + RetroLambda:
public void view() {
linearLayout(() -> {
orientation(LinearLayout.VERTICAL);
textView(() -> {
text("....");
});
button(() -> {
text(".....");
onClick(v -> {
....
});
});
});
}
Kotlin:
public override fun view() {
linearLayout {
orientation(LinearLayout.VERTICAL)
textView {
text("....")
}
button {
text("....")
onClick { v ->
...
}
}
}
}
Anvil library contains only a few classes to work with the virtual layout, but most
of the DSL (domain-specific language describing how to create views/layouts and set
their attributes) is generated from android.jar.
API
Here's a list of classes and methods you need to know to work with Anvil like a pro:
-
Anvil.Renderable- functional interface that one should implement to describe layout structure, style and data bindings. -
Anvil.mount(View, Anvil.Renderable)- mounts renderable layout into a View or a ViewGroup -
Anvil.unmount(View)- unmounts renderable layout from the View removing all its child views if it's a ViewGroup -
Anvil.render()- starts a new render cycle updating all mounted views -
Anvil.currentView(): View- returns the view which is currently being rendered. Useful in some very rare cases and only inside the Renderable's methodview()to get access to the real view and modifying it manually. -
RenderableView- a most typical implementation of Anvil.Renderable. Extending this class and overriding its methodview()allows to create self-contained reactive components that are mounted and unmounted automatically. -
RenderableAdapter- extending this class and overriding itsgetCount(),getItem(int)andview(int)allows to create lists where each item is a standalone reactive renderable object. -
RenderableAdapter.withItems(list, cb(index, item))- a shortcut to create simple adapters for the given list of items.cbis a lambda that describes the layout and bindings of a certain l




