(Almost) Seamlessly Integrate Android Layout XML to Code Behind Reference
It’s been a while since my last post. With this post, I would like to share a little bit of my toolkit which I use extensively on my Android project. 🙂
Overview
So basically, when you are developing apps for Android, you use Android XML file to define the User Interface (UI) layout structure, then you code using Java for the implementation. Basically they resembles the .NET WPF/Silverlight’s XAML development, which they also use XML file to define the UI layout. But the worst thing I hate in the Android implementation is that basically the XML file and the Java code behind doesn’t really integrated seamlessly.
What made me say that .NET XAML is far years ahead compared to Android XML layout file? See the difference between those two frameworks below. Example: I want to make a simple WPF application containing a text box and a button. The button will be set disabled immediately after clicked, and show message box containing the text in the text box.
NET Framework WPF
In .NET Framework, the XAML file is seamlessly integrated with the code behind. In XAML you can define a Name for each of your control (widget), and call it directly from the C# code behind using the defined name
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox
x:Name="TextBoxA"
HorizontalAlignment="Left"
Height="23"
Margin="10,10,0,0"
TextWrapping="Wrap"
Text="TextBox"
VerticalAlignment="Top"
Width="497" />
<Button
x:Name="ButtonB"
Content="Button"
HorizontalAlignment="Left"
Margin="432,38,0,0"
VerticalAlignment="Top"
Width="75"
Click="ButtonB_Click" />
</Grid>
</Window>
[/xml]
{
public MainWindow()
{
InitializeComponent();
}
private void ButtonB_Click(object sender, RoutedEventArgs e)
{
ButtonB.IsEnabled = false;
MessageBox.Show(TextBoxA.Text);
}
}
[/csharp]
Android
To perform the same task in Android, you have to call findViewById for each widget ID defined in the XML layout file, and cast and store the returned value from the function call to the instance variable. This leads to messier code compared to .NET style, because you have to call and assign every reference in code behind before you can access what is defined inside XML layout file. Basically you do the integration between XML layout file and code behind manually.
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/listitem_bill_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="${packageName}.${activityClass}" >
<EditText
android:id="@+id/main_editText1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10" >
</EditText>
<Button
android:id="@+id/main_button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />
</LinearLayout>
[/xml]
private Button button1;
private EditText editText1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.button1 = (Button)findViewById(R.id.main_button1));
this.button1.setOnClickListener(this);
this.editText1 = (EditText)findViewById(R.id.main_editText1));
}
@Override
public void onClick(View v) {
button1.setEnabled(false);
Toast.makeText(this, editText1.getText(), 10000);
}
}
[/java]
Idea
I want Android to have similar capability like .NET Framework which I don’t have to assign the reference to the widget manually by calling findViewById on each widget. This is to simplify the code and to make the code cleaner.
Implementation
To achieve this, I know that once again I can use Reflection to perform this task. We have to put consideration on how findViewById works.
The findViewById works by matching the declared widget in Android XML with the auto-generated ID. Android SDK automatically generates a unique integer ID for each ID-name declared in XML layout file. The unfortunate nature of this ID is that the uniqueness of the name of the ID is in the Application scope. You should not reuse the same name for widgets even if those two widgets declared in separate layout file. But regardless of its natural properties, we have to rely on findViewById method to get the widget declared in XML layout file.
So, the next part is how to link between code-behind reference variable into the specified widget which is identified by generated ID? We have to attach some metadata to the variable to tell that it should be linked with the specific widget with specific ID. To achieve this, we have to use Annotation.
Annotation is one of Java features which increase the modularity of developing application in Java. You can ‘annotate’ any code elements in your source code using Annotation, from variables, methods, into classes. The Annotation can be retained until the runtime of the application, and the program can retrieve the information stored in annotation using Reflection.
Explaining the theory might be confusing. But just take a look at code below:
[java] public class MainActivity extends ActionBarActivityimplements View.OnClickListener {
@WidgetReference(R.id.main_button1)
private Button button1;
@WidgetReference(R.id.main_editText1)
private EditText editText1;
}
[/java]
I designed that WidgetReference annotation will store the value of ID of the target widget in layout file. The code above is translated to that the variable button1 has a WidgetReference with value R.id.main_button1, and editText1 has a WidgetReference with value R.id.main_editText1.
Where’s the Magic?
Of course by only using the Annotation alone, the magic would never going to happen. We have to create the implementation to process the annotation.
The basic algorithm of the process is:
- Iterates to all field declared in a class
- For each field that has WidgetReference annotation declared
- Get the ID stored in the annotation
- Get the View object in XML layout file by the ID from the annotation
- Store the View object retrieved into this current field
To implement that, we use the magic of Reflection. You can find below for the algorithm implementation and annotation:
View rootView) {
// Get targetStore class definition using reflection
Class<?> clazz = targetStore.getClass();
// For each declared fields within the class
for (Field f : clazz.getDeclaredFields()) {
// If annotation @WidgetReference is declared for the field, then
if (f.isAnnotationPresent(WidgetReference.class)) {
// Set the field accessible, regardless of what declared in
// the code
f.setAccessible(true);
// Get the R.id value from annotation
int id = ((WidgetReference) f
.getAnnotation(WidgetReference.class)).value();
try {
// Find view by ID then store it to the declared field of
// the target store object
View theWidget = rootView.findViewById(id);
f.set(targetStore, theWidget);
} catch (Exception e) {
// Any exception, just throw it away
throw new RuntimeException(e);
}
}
}
}
[/java]
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
public @interface WidgetReference {
public int value();
}
[/java]
So this library is compiled in my GitHub. Check out heliosky-ui at GitHub (https://github.com/gilanghamidy/heliosky-ui). Feel free to use the library, and please credit me in your source code if you find this code useful. 😉