June 28, 2015

Painless Android development with Groovy and SwissKnife (part 2 of 3)


Pages: 1 2 3

Groovy with SwissKnife

SwissKnife is “a multi-purpose Groovy library containing view injection and threading for Android using annotations”. It tremendously increases ease of development and maintainability, as we will see presently.

Setup

To use it, follow the official documentation or take a look at my module’s build.gradle file where it’s activated. I installed the accompanying SwissKnife Android Studio plugin as well for further convenience.

In action

With above setup completed, you’re ready to use the SwissKnife enhancements in your Groovy code. Please refer to its excellent documentation page for more information about its components.

Note that you always have to init SwissKnife in an activity’s #onCreate(...) method using e.g. SwissKnife.inject(this) for view injection and SwissKnife.loadExtras(this) for extra injection.

Here are some usage examples:

To toast:
toast("Correct answer was: $checked").show()
To start an activity (i.e. change to a different screen):
@OnClick(R.id.startCard)
public void onClick() {
    startActivity(intent(CardActivity))
}
To include extras from a parent activity:
@Extra
Long id

Problems

This is not a problem of SwissKnife itself, but a particular annoyance of Android: Apparently, invoking Intent#putExtra(...) with a null value leads to immediate NullPointerException. I have thus written a small helper method which allows for a concise, null-save syntax:
startActivity(ActivityUtil.intent(this, CardEditActivity, [
    id: card.id
]))
Please check out ActivityUtil’s source code for more details.

Two-way data-binding with Groovy

If you’ve ever enjoyed the pleasure of two-way data binding, keeping the model (M) and view (V) auto-magically in synch, as e.g. implemented by the AngularJS JavaScript framework, you just don’t want to get back to manual synchronization again. Android doesn’t even come with one-way data-binding out-of-the-box, neglecting any need for model / view separation. Time to change that.

After checking out a couple of available solutions such as Android’s official Data binding extension (one-way only, currently in beta), bindroid and ngAndroid I decided to try and implement it on my own using vanilla Groovy. I eventually ended up with a pretty decent solution in only about 150 lines of code. It doesn’t support conversion or validation, but it serves well the requirements of this demo application and it’s potentially extensible.

In action

This solution is based on Groovy’s @Bindable annotation which has to be applied on the model class:
@Bindable
class Card {
Then, you have to initialize the Binding:
TextViewBindings.bind(guessedCard, [
    text1       : text(findViewById(R.id.text1)),
    text2       : text(findViewById(R.id.text2)),
    text1Guessed: enabled(findViewById(R.id.text1)),
    text2Guessed: enabled(findViewById(R.id.text2))
])
#bind(...) takes the model and a map which associates each model property (by its name) with a View component. It registers the necessary TextChangedListener (for changes in the View part) and PropertyChangeListener (for changes in the model part). Note that the enabled property can be bound to a model’s Closure property returning true or false.

That’s it. Any change to the TextViews will immediately update the model (did I hear “backing bean”?), and a change to the model will update the TextView’s text and enabled property.

Please check out TextViewBindings’s source code for more details.

There’s also a data-binding solution for list models. However, I only implemented this as a one-way data-binding: From model to view. It is based on another Groovy feature, namely on ObservableList which must be the type of the list model:
ObservableList cards = [] as ObservableList
You can then initialize the Binding:
View table = findViewById(R.id.card_table_layout)
TableBinding.bind(cardSet.cards, table) { Card card ->
    TableBuilder.linearLayout(this,
            TableBuilder.button(this, "X", {
                card.delete()
                cardSet.cards.remove(card)
            }),
            TableBuilder.text(this, "($card.correctGuesses) $card.text1:\n$card.text2", {
                startActivity(ActivityUtil.intent(this, CardEditActivity, [
                        id: card.id
                ]))
            })
    )
}
Here, #bind(...) takes the model and the parent view and a closure which returns the child view created from a model which is newly added to the list (in the example, the closure is very elaborate, building a “table row” consisting of a “delete” button and a text view).

Check out TableBinding’s source code for more details.

With this setup, you can work with your model entities throughout the application. You don’t ever need to touch view components in your code.

Problems

Classes annotated with @Bindable must not be @CompileStatic. The listener would just not be fired otherwise. You thus have to stick with dynamically compiled classes, including the associated potential performance penalty and proguard minification problems. The TextViewBinding util class which initializes the actual data-binding also needs to be dynamically compiled.

Next steps

Of course, this solution is far from perfect. Still, for the demo application’s requirements, it’s almost overkill. I really want to show here that through sensible use of Groovy facilities, one could quite easily come up with a two-way data-binding framework which Android need desperately in my opinion.

The current solution even doesn’t make full use of two-way data-binding: As it works with a reference of a model variable, it has to be re-initialized as soon as that variable points to another model instance. Actually, the model variable should be @Bindable as well, and upon property change, the binding should be re-initialized.

Also, binding would need much more parameterization and support for observing / updating more View properties other than text, enabled, and hint.

Also, it should support conversion (only Strings are supported now) and maybe even validation (that could be realized using @Vetoable rather than @Bindable).

Declarative Layout building

Android views are obviously meant to be built in the layout XML file, preferably using the IDE’s “design” UI. As soon as you have to build / alter the UI in Java code, e.g. to change the UI dynamically at runtime, you’re falling back to Swing-style development which quickly devolves to a mess of procedural spaghetti code.

It’s a shame Android doesn’t provide any fluent, builder-style syntax out-of-the-box. I took a look at the grooid-tools project which aims to provide a true builder syntax for Android UIs, similar to Groovy’s built-in SwingBuilder, but as of now (June 2015), the project seems to lack any documentation and looks more or less abandoned.

In action

Thus I just built a very tiny little builder-like helper class for building a linear layout (it’s the same piece of code as in the previous section).
TableBuilder.linearLayout(this,
    TableBuilder.button(this, "X", {
        card.delete()
        cardSet.cards.remove(card)
    }),
    TableBuilder.text(this, "($card.correctGuesses) $card.text1:\n$card.text2", {
        startActivity(ActivityUtil.intent(this, CardEditActivity, [
                id: card.id
        ]))
    })
)
Please check out TableBuilder’s source code for more details.

Next steps

Obviously, this is only a very local solution for the very requirements of this one layout. A more general solution might either involve writing a true Groovy builder or just a builder-style method chaining API. Of course, it would have to provide support for all of Android’s view components.

Because we need context (activity) information to build a View component, we would probably have to implement this in a base bean for all activities in order to avoid having to pass this reference around all the time.

Escaping XML hell

Unfortunately, Android uses XML files for about every configuration, even if it is clearly ill-suited such as key-value mapping for message bundles. Java EE for example uses the properties file format for that which makes perfectly sense. XML is a valid format for many purposes, but for Android’s use cases, it is just bloated and obfuscates the code.

As a first step to overcome this problem, I ended up writing a small IntelliJ IDEA / Android Studio plugin which automatically converts XML files to properties files and vice versa on file save. I introduced that plugin in the previous blog post, and detailed usage information is provided on the plugin’s GitHub repository.

I thus used the plugin to create a properties format mirror of the values/strings.xml file and to thus be able to edit this file in the properties format rather than in the XML format, and having the changes automatically synched back to the XML file on save.

This will then e.g. turn file content like this:
<string name="cardset_template1_hint">Upper Textbox hint</string>
<string name="cardset_template2_hint">Lower Textbox hint</string>
<string name="save">Save</string>
<string name="cardset_add_card">Add new card</string>
<string name="title_activity_card_edit">Edit card</string>
Into file content like this:
cardset_template1_hint=Upper Textbox hint
cardset_template2_hint=Lower Textbox hint
save=Save
cardset_add_card=Add new card
title_activity_card_edit=Edit card
I am convinced that the latter is much easier to handle. It’s structure is much cleaner, it’s highly readable, and I have no problems bunch-editing many texts at the same time manually.

Next steps

The current implementation really is rather a proof of concept than a production-ready solution.

In a next step, the solution would have to get more generalized in order to apply it to other Android resource files as well. This really means to just implement the respective on save Groovy scripts. Eventually, I would love to have this thing for the entire layout XML files as well. I imagine being able to write the layout in a concise builder-like syntax, as sketched by the grooid-tools project, and compile it to XML on file save.

As an accompanying feature, it would be nice to have a Gradle task / plugin which reuses the on file save Groovy script to generate the Android XML files on build time. That way, the Android XML files would become a true passive artifact you wouldn’t even have to check in to source control.

Pages: 1 2 3