Anvil

A micro library for building fast reactive views on Android

  • Tiny (4 classes, ~1k LOC)
  • Easy to learn (core API is only 5 functions)
  • Fast (not using Java reflection)
  • Declarative layouts with natural data binding
  • Java 8 and Kotlin-friendly, but also supports JDK6
  • For Android 2.3 or newer
  • Supports XML layouts, but is not limited by using them
  • Inspired by React, Mithril and Incremental DOM
  • Open source

Anvil renders views incrementally, so if you change your model and update your views - only the fields affected by the model change will be updated.

public class MainActivity extends Activity {
    private int count = 0;

    public void onCreate(Bundle b) {
        super.onCreate(b);

        Anvil.mount(android.R.id.content, () -> {
            linearLayout(() -> {
                orientation(LinearLayout.VERTICAL);
                // Show clicks count
                textView(() -> {
                    text("Count: " + count);
                });
                // Increase count on every click
                button(() -> {
                    text(R.string.btn_text);
                    onClick((v) -> {
                        count++;
                    });
                });
            });
        });
    }
}

Installation

// build.gradle
repositories {
    jcenter()
}
dependencies {
    compile 'co.trikita:anvil-sdk15:0.2.0'
}
      

Or install manually from the sources:


$ git clone https://github.com/zserge/anvil
$ cd anvil
$ ./gradlew build
$ ./gradlew anvil:publishToMavenLocal
      

API

Anvil.Renderable is a functional interface that one may implement to describe layout structure, style and data bindings.

public class MyView implements Anvil.Renderable {
  public void view() { /* Your layout here */ }
}
Anvil.Renderable myView = () -> { /* Or here */ }

Anvil.mount(View, Renderable) mounts a renderable layout into a View or a ViewGroup

Anvil.mount(mContainerView, new MyView());
Anvil.mount(mContainerView, () -> {
   // Your layout here
});

Anvil.unmount(View) detaches a renderable layout from the View removing all its child views if it's a ViewGroup


Anvil.unmount(mContainerView);

Anvil.render() starts a new rendering cycle updating all mounted views. Can be called from background threads.

Anvil.mount(mContainer, () -> {
    // Will show text with the current "s" value
    textView(() -> { text(s); });
});
s = "Foo";
Anvil.render(); // Will change text to "Foo"

Anvil.currentView() returns the view instance which is currently being rendered. Useful in some very rare cases and only inside the Renderable's method view() to get access to the real view and modifying it manually. Often used with init().

EditText mEditText;
editText(() -> {
    size(FILL, FIL);
    init(() -> { // Executed only once
        mEditText = Anvil.currentView();
        mEditText.setSelection(mFrom, mTo);
    });
    text(mText); // Executed every time text changes
});

RenderableView is a most typical implementation of Anvil.Renderable. Extending this class and overriding its method view() allows to create self-contained reactive components that are mounted and unmounted automatically.

public class MyView extends RenderableView {
    public MyView(Context c) { super(c); }
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        // Initialize your view component
    }
    public void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        // De-initialize your view component
    }
    public void view() { /* your layout here */ }
}

RenderableAdapter class allows to override getCount(), getItem(int) and view(int) methods to create lists where each item is a standalone reactive renderable object.

public class MyAdapter extends RenderableAdapter {
    public int getCount() {
      return 5;
    }
    public Object getItem(int pos) {
      return "Line #" + pos;
    }
    public void view(int pos) {
        textView(() -> {
            text(getItem(pos));
        });
    }
}

RenderableAdapter.withItems(list, cb) is a shortcut to create simple adapters for the given list of items. cb(index, item) is a lambda that describes the layout and bindings of a certain list item at the given index.

List<String> myList = getItems();
RenderableAdapter a =
  RenderableAdapter.withItems(myList, (i, item) -> {
    textView(() -> {
      if (isImportant(i)) {
        textColor(importantColor);
      } else {
        textColor(normalColor);
      }
      text(item.toString());
    });
}

Learn more on Github...

Fork me on GitHub