Home

Awesome

Anvil demos

This is a collection of minimal apps to show various aspects of Anvil. All demos are written in Kotlin, which is a natural language of choice for Android platform these days. However, Anvil itself does not require Kotlin and can be used with Java 6 or newer.

These demos are purposely written in a simple and clear style. You will find no difficulty in following them to learn the powerful library.

Related projects

How to use

Fetch the code and use gradle to install any of the demos:

$ git clone git@github.com:zserge/anvil-kotlin-demos.git
$ cd anvil-kotlin-demos
$ ./gradlew :demo01:installDebug

If you want to run tests for the demos:

$ ./gradlew :demo01:connectedCheck

Each demo consists of a single Kotlin file (demoNN/src/main/java/com/example/MainActivity.kt), other project files are shared across all the demos.

Index

  1. Rendering a static layout
  2. Binding data and event listeners
  3. Refactoring, styles and components
  4. Adapters
  5. Accessing views directly
  6. XML layouts
  7. RenderableView

Demo 01: Rendering a static layout (source)

Anvil DSL syntax is very similar to Anko, which in its turn follows the structure of traditional android XML layouts.

Anvil.mount(findViewById(android.R.id.content)) {
	textView {
		text("Hello!")
	}
}

Every view starts with a view class name and follows by a pair curly braces (a renderable). Inside the renderable you define view attributes and other nested views.

If you want your renderable to be displayed - you have to mount it to some view attaching the described layout into the specified view. This is similar to setContentView() or LayoutInflater.inflate() in traditional Android.

Demo 02: Binding data and event listeners (source)

Anvil renderables are simple anonymous functions (lambdas), so unlike XMLs here you can write any kind of code inside (variables, conditionals, loops, other lambdas etc).

val names = arrayOf("Alice", "Emily", "Kate")
var selected = -1

Anvil.mount(findViewById(android.R.id.content)) {
	linearLayout {
		orientation(LinearLayout.VERTICAL)

		for ((i, name) in names.withIndex()) {
			textView {
				text(name)
				textSize(48f)
				if (i == selected) {
					textColor(Color.RED)
				} else {
					textColor(Color.WHITE)
				}
				onClick { v ->
					selected = i
				}
			}
		}
	}
}

If you use a variable as an attribute value and later change the variable - your layout will be updated, but only the changed view attributes will be actually modified (like in Incremental DOM).

In the example above the colors of the text views is be changed automatically as you click on the names, because the color is bound to the selected variable, which is modified inside the click listeners bound to the text views.

Demo 03: Refactoring, styles and components (source)

Full power of your programming language makes is very easy to refactor your code or to share common styles across various parts of your UI.

For example, here is a custom layout with a header on top and centered content below it. This simple function can be reused with different header titles and content:

layoutWithHeader("Demo Header") {
	textView {
		size(WRAP, WRAP)
		layoutGravity(CENTER)
		text("Content")
	}
}

fun layoutWithHeader(title: String, r: () -> Unit) {
	linearLayout {
		size(FILL, FILL)
		orientation(LinearLayout.VERTICAL)

		textView {
			size(FILL, dip(48))
			gravity(CENTER)
			text(title)
			textSize(28f)
			backgroundColor(Color.BLUE)
		}

		frameLayout {
			size(FILL, 0)
			weight(1f)

			r()
		}
	}
}

A common technique is to pass a renderable (r) as a last argument to your custom component function to override its behavior.

Demo 04: Adapters (source)

While it's now to render a collection of data into a number of views with a simple loop, it's still recommended to uses Adapters if the size of your data is unknown and can be large.

Anvil provides a special helper to create your own adapter of renderables, which handles their lifecycle automatically.

val items = Array(100, {i -> "Item #" + i}).asList()

val demoAdapter = RenderableAdapter.withItems(items, { pos, item ->
	textView {
		text(item)
		textColor(Color.HSVToColor(floatArrayOf(pos*3.6f, 1f, 1f)))
	}
})

Anvil.mount(findViewById(android.R.id.content)) {
	listView {
		size(FILL, FILL)
		adapter(demoAdapter)
		onItemClick { av, v, pos, id ->
			Toast.makeText(this, "Clicked on item #" + pos, Toast.LENGTH_LONG).show();
		}
	}
}

Demo 05: Accessing views directly (source)

Sometimes you have to deal with views/attributes not covered with Anvil DSL. Most often it will be the custom views or rarely used attribute setters.

Anvil.currentView() returns the instance of the real view being currently rendered. You should use that if you need to call view methods directly. This might be also helpful if you need to keep a reference to the view to use it from other parts of code (like findViewById), but you should not really need this in Anvil.

val loremIpsumText = "Lorem ipsum dolor sit amet..."
textView {
	size(FILL, FILL)
	text(loremIpsumText)
	init {
		val tv: TextView = Anvil.currentView()
		tv.setLineSpacing(0.5f, 2f)
	}
}

If you want to ensure that you code is executed once you can put it into the init {} block. This is helpful for setting up your custom views.

Demo 06: XML layouts (source)

Sometimes you have to use XML. A good example is style attribute which can not be set in code. Also, some poorly designed 3rd-party views don't provide a full Java API taking all parameters as AttributeSet from XML.

But this should not stop you from using such views in Anvil:

val names = arrayOf("Alice", "Emily", "Kate")

linearLayout {
	orientation(LinearLayout.VERTICAL)

	for ((i, name) in names.withIndex()) {
		xml(android.R.layout.simple_list_item_1) {
			withId(android.R.id.text1) {
				text(name)
			}
		}
	}
}

xml inflates a layout and attaches the resulting view into the current renderable as if you wrote that layout with Anvil DSL.

withId allows to find an existing view (may be on any level of hierarchy, but must be inside a given parent renderable node) and allows you to override its attributes, bind data and event listeners.

Demo 07: RenderableView (source)

While renderable functions are easy to combine and manage, sometimes you need self-contained stateful components.

You can extend from RenderableView and override the view() method to declare the layout. Renderable view acts as a simple renderable function, except you don't have to mount/unmount it - instead you should add/remove the view itself.

You may override onAttachedToWindow and onDetachedFromWindow to handle your view lifecycle (e.g. to start/stop timers or background tasks).

setContentView(Counter(this))

...

class Counter(c: Context): RenderableView(c) {
	var value = 0
	val exec = Executors.newSingleThreadScheduledExecutor()
	override fun onAttachedToWindow() {
		super.onAttachedToWindow()
		exec.scheduleAtFixedRate({
				value++
				Anvil.render()
		}, 0, 100, TimeUnit.MILLISECONDS)
	}
	override fun onDetachedFromWindow() {
		super.onDetachedFromWindow()
		exec.shutdown()
	}
	override fun view() {
		linearLayout {
			orientation(LinearLayout.VERTICAL)
			textView {
				text(""+value)
			}
			button {
				text("Reset")
				onClick { v ->
					value = 0
				}
			}
		}
	}
}

This is especially helpful if you support the ideas described in "Advocating against fragments".

You can keep your screens as separate RenderableViews. Each screen then acts as a standalone controller for the view defined in its view() method. You may use libraries like Flow or AndroidRouter to do navigation between your screen.

Demo 08: Different screen support (TODO)

Demo 09: Anvil extensions (TODO)