June 28, 2015

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


Pages: 1 2 3

O/RM with Sugar ORM

Most non-trivial Android applications will have to store data in some way. Because manually writing SQL queries is cumbersome and error-prone, there are object-relational mapping frameworks which abstract away CRUD operations on entity models. These frameworks are well known and fully integrated in the Java EE platform; for Android, one must choose a third party framework of which there are luckily quite a few.

After some quick research, I decided to go with Sugar ORM. Yes, it’s not one of the “big names” (OrmLite, GreenDAO), but it’s the only one where the usage instructions cover less than two screen pages. Given the small size of the example project and the reduced amount of time I wanted to fiddle around with ORM, I decided to give it a go and finally, I didn’t need to go back on that decision.

In action

Sugar ORM describes itself as an “insanely easy way to work with Android database” and this is certainly true. After the initial minimal setup, you don’t even need to annotate entity models. Just make sure to derive from SugarRecord:
class Card extends SugarRecord<Card> {
and all properties are auto-detected. Sugar ORM then enhances your model classes with CRUD persistence functionality:

Read a card:
card = Card.findById(Card, id)
Update a card:
card.save()
More information on the short but sweet official documentation.

Problems

Where is my database?

This is not a problem of Sugar ORM, but of Android itself. Apparently, inspecting the SQL DB is not possible on a real hardware device unless “rooted” due to security reasons. This is just ridiculous! Am I really supposed to implement persistent storage blindly? I haven’t checked all possible “hacks” and “workarounds” so maybe there is a way to achieve this. I just don’t get it why there is no straightforward support for such a vital development feature.

Dropping the database

This is not really a problem, just a lesson learnt: The easiest way to drop an app’s db seems to be to deinstall the application on the device, using the device’s application manager. Maybe there’s another way integrated into the deployment process, but I haven’t found it yet.

To-Many-Relationships, Cascading

As of Sugar ORM version 1.3, To-Many-Relationships need to be managing manually, and there is no cascading. For the minimalistic demo application, this wasn’t a big deal. For more complex entity relations, you may want to implement cascading by e.g. overwriting #save() and implement it there. Also, it’s apparently worked on for version 1.4.

Property mapping

Auto-magical property mapping comes with its price: you’re not in control over the mapping. In order to prevent a property from being mapped, you can use @Ignore (Java EE equivalent: @Transient), but that does only work for classes you own.

For instance, when we annotate our model classes with @Bindable to implement two-way data-binding (see above), this weaves a final private java.beans.PropertyChangeSupport this$propertyChangeSupport field into that class which Sugar ORM would then take as an additional property and persist as well! This may not be what you desire.

Putting it all together

This blog post really became a presentation of tools which work more or less independently of each other. Nevertheless, I’d like to emphasize here how seamlessly the Groovy + SwissKnife + Sugar ORM stack works as a unit by highlightling a complete example use case.

Let’s see here how to navigate from CardSetActivity to CardActivity when the user clicks on a card to open it, editing the card, and then saving it in CardActivity which leads back to CardSetActivity overview.

First of all, in CardSetActivity, we initialize the card set model:
List<CardSet> cardSets = CardSet.listAll(CardSet)
if (!cardSets.empty) {
    cardSet = cardSets[0]
}
else {
    cardSet = new CardSet()
}

cardSet.cards = Card.listAll(Card).sort {
    -it.correctGuesses
} as ObservableList
initListener()
And we then init the data-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
                ]))
            })
    )
}
The binding also builds the view elements, one for each object in cardSet.cards, and attaches them to the table View.

Note that TableBuilder.text(…) builds a TextView which outputs information about a card and registers an OnClickListener as a closure. Here, the listener starts an activity (startAcitvity(…) is provided by SwissKnife), which is associated with an Intent which takes a parameter named id. The current card’s id is thus passed into that activity.

In CardEditActivity, the id parameter is injected:
@Extra
Long id
and it’s used to load the card with the id provided from the database:
Card card = new Card()
if (id != null) {
    card = Card.findById(Card, id)
}
initListener()
You could even perceive this as “lazy loading”. Of course, DB access methods are provided by Sugar ORM.

Then, again, the data-binding is initialized:
TextViewBindings.bind(card, [
    text1       : text(findViewById(R.id.text1)),
    text2       : text(findViewById(R.id.text2)),
    correctGuesses: text(findViewById(R.id.correctGuesses)),
])
As this registers the respective TextWatcher, the card’s text1 and text2 properties will be updated on every key stroke, so we can just rely on the card model always containing the current value of the input components.

Thus all we need to do on save is save the model using Sugar ORM:
@OnClick(R.id.saveButton)
public void save() {
    card.save()
    setResult(RESULT_OK)
    finish()
}
Then we finish() the activity to go back to the parent activity. This latter is vanilla Android. However, note that registering the OnClick listener happens with a simple @OnClick annotation by SwissKnife.

Finally, we need to register a listener in CardSetActivity which re-initializes the model when a child activity is finished:
// Coming back from addNewCard
@Override
protected void onResume() {
    super.onResume()
    initModel()
}
Android wouldn’t call the onCreate(…) listener in that case. initModel() simply fetches the cardset and its associated cards from the database again. It’s the method with the source code shown at the beginning of this section.

I encourage you to take a look at the source code of these and the other two activities on the demo project’s GitHub repository. They contain some more variations of Groovy / SwissKnife / Sugar ORM interplay.

Conclusion

Honestly, I cannot imagine working as a professional Android app developer at the current state of the platform. Actually, I cannot imagine anyone working as a professional Android app developer! There are just too many problems left unsolved, and the developer’s burden is much too high. Coming from Java EE and Groovy, I’m accustomed to concentrate on adding business value when I develop, having everything else abstracted away. On Android, nothing is abstracted away, and I have to build everything from scratch, with every new app, before I could even think about adding business value.

Android apparently makes it easy to write poor, procedural, copy-pasted code, whilst making it hard to write proper, clean, modularized code. This is the opposite of what e.g. AngularJS does in the web app world, facilitating practices such as separation of concerns and inversion of control.

Still, there are potential solutions for these problems and I’m convinced that the Groovy + SwissKnife stack lays a great foundation to facilitate Android application development. What’s more, integration with an O/RM mapper such as Sugar ORM really is seamless.

Thus, I could actually imagine diving deeper into Android app programming, given that I’m free to choose and shape a truly productive development environment. The tools I used for this demo application would certainly be part of that tech stack. Note however that I neglected performance and security aspects for the most part so far.

Please let me know in the comments section below whether you found my insights useful and whether you share some of my concerns about Android application development. If I have missed any interesting library / tool to further increase productivity or which elegantly solves one of the problems I mentioned in this blog post, please let me know! I hate re-inventing the wheel and I would gladly add an additional tool to my stack if it helps me overcome some of Android’s shortcomings.


Pages: 1 2 3

No comments:

Post a Comment