Transcending from Java to XML

a quest about Android and dynamic Navigation Drawers


I am an Android Developer at ‘txtr in Berlin, where everyday we shape one of the most popular eBook readers out there. Since we usually make different rebrands (we have quite a lot of them and it would really be boring to copy-paste the same code over and over) for our core Vanilla app, I was asked to implement a custom Navigation Drawer using native Android technologies. Routine. Not quite. This particular task required this component to be fully customizable from an XML file — that means everything shall be defined, from labels, to icons, to behaviours, for each and every item. Didn’t seem a huge deal… Until I tried to define the specs for it.


I will avoid talking about the pain a couple of friends experienced while I was trying to define everything and they were listening to me — not sure they were actually listening — but, before we dig into the code, I would like to thank both of them.


Well, first things first. And I will try to make it short. You may know the basic structure of a Navigation Drawer (if you do not, check it out here) but I needed to extend it and acquire a degree of flexibility it normally doesn’t require. What does it change from the normal implementation, then?

Navigation Drawer
as from d.android.com

Callbacks

Let’s start off with elements of the menu: we do not know what they are, so I created a couple of callbacks extending Runnable: one for Fragments and one for Intents. This distinction has been made because we could encounter different situations:

  • A Fragment needs to replace the current content
  • An Intent should launch an external website
  • An Activity could pop in, every now and then

This Runnables’ run() method is then called when a click is performed on the list item they are attached. We still do not know how to specify those elements that need to be changed from rebrand to rebrand.

We wanted to achieve something like this, without using a single line of Java.

The Adapter

We only know, at the moment, that each and every one of the elements can be disabled or enabled based on user status and, when they can be pressed, they need to perform a callback that is, in fact, a Runnable. I then extended then the ArrayAdapter and, using both the definition in the object itself and the user status, creating a proper adapter was just a matter of overriding a couple of methods:

@Override
public boolean isEnabled (int position) {
return dummyElements[position].getStatus();
}
@Override
public int getItemViewType (int position) {
return isEnabled(position) ? ENABLED : DISABLED;
}

With the help of this code, assigning the right layout and values to each element based on the result of getItemViewType() was actually pretty easy in the getView() method.

The input

This was by far the most exciting part: using the Resources class, we load a XML structure and, node by node, we asynchronously create the Arrays of elements.

<entry
entry=”true”
intent=”android.intent.action.VIEW”
url=”http://www.google.it"
icon=”ic_intent”
label=”itemLable_intent”/>
<entry
entry=”true”
fragment=”com.example.ThisIsAFragment”
icon=”ic_fragment”
label=”itemLabel_fragment”
tag=”fragment”/>

Note

This XML contains a couple of interesting details: first, both icon and label are names of resources, dynamically loaded using getIdentifier() method while the tag attribute is used to recover a Fragment using the tag itself as key.

These will be then swapped (we might have 2 different sets of elements based on the user status, that can be upgraded while the user uses the app) and, for each of them, we create the callback. Here, another small problem popped up: how can I load the right Fragment from a String representation of its name?

The only way to do it at runtime is using a bit of Reflection:

Class<?> fragmentClass = Class.forName(fragmentName);
Fragment fragment = fragmentClass.newInstance();

Intents

For Intents we had no issues: we could create a dynamic configuration that wraps all the Intent constructors, giving us all the flexibility we needed.

The idea behind the Intents creation was to use a hash based system, in which every constructor was built thanks to the weights of the different parameters that it can receive. For example, if we needed to add a Class to start a new Activity, we would trigger the following code:

if (classInInput != null) {
if (classStored == null) {
hashWeight += CLASS_INTENT;
}
classStored = classInInput;
}

Then, when we actually create the Intent, the hashWeight variable will tell us exactly what kind of Intent we want to build.

In fact, using an hash system will assure us the uniqueness of the the constructor: with no chance of collisions (obtained by properly calculating all the weights), it was fairly straight forward the creation of a future-proof Intent callback builder.


Conclusion

This is more or less how I created a dynamic Navigation Drawer and, I must admit, this has been a surprisingly fun challenge. At the end of the day, we make things because we are addicted to the feeling of satisfaction we get when they finally work, am I right?