Before you start
You should be comfortable constructing Android applications with the Android SDK to get the most from this tutorial. After completion, you will have learned how to perform application-to-web server communications with HTTP(S) and how to parse XML with the DOM parser. Along the way, you will create custom and dynamic user interface layouts, multi-threaded communications, message handlers, and progress dialogs. To a lesser degree, you will learn about AndroidManifest.xml and server side scripting.
About this tutorial
This tutorial introduces an architecture for dynamic forms for mobile data collection on Android devices. I will begin with a high level architecture and discussion of where such an application fits in the larger context of data collection. A sneak peak at the completed project, including every source file, provides a road map to where the tutorial is taking you. In cooking-show fashion, you build the application from the ground up with each Java class carefully introduced and related to other aspects of the application, most notably the data model upon which this forms engine is constructed. To conclude, you save form data to the server and look briefly at the server side of the application.
Prerequisites
Table 1 shows the tools required for this project.
Table 1. The necessary tools for the job
Tool | Comment |
---|---|
Android Studio | Integrated Development Environment (IDE) for constructing Android applications. |
Android SDK | Android Software Developer Kit – managed by Android Studio. |
Web server | Any variety that supports PHP. You can easily port the script to another server environment. |
I created the code samples for this tutorial on a MacBook with Android Studio 3.2.1 and Android SDK version 27, which supports the Android release labeled 8.1 (Oreo). The tutorial code is not leveraging any specific features of this SDK and the application should run just fine in Android versions dating back as far as 1.5. See Resources for links to all the tools.
Data collection
Let’s begin with a short discussion about data collection, and how it can be easy to implement when using an Android mobile device.
An Android data collection framework
Collecting data is a task that pre-dates the computer era. Computers have become a daily staple and have revolutionized the way you think about, find, and consume information. Companies with billion dollar market-caps exist thanks to their effectiveness in storing, retrieving, and managing vast amounts of information. The databases in use today are fed by systems of varying architectures including the main frame, client server, web applications, and now mobile applications.
Physical inventory and cycle counting applications were some of the early practical applications of mobile computing. These applications were often batch data collection, where the hardware required a docking station to upload the collected information.
The market for mobile applications has come a long way since those early days, and wireless connectivity and devices are nearly ubiquitous in many cultures and markets, invading virtually every aspect of daily life.
While the means of collecting data may have become more mobile, the core aspect of data collection has not changed significantly. The user must be presented a collection of questions and have a simple means of responding. This tutorial demonstrates the construction of a simple data collection framework for Android-powered mobile phones, leveraging a dynamic metadata structure enabled by XML.
The application architecture
Before diving into the code, examine the application setting from a very high level.
Forms Engine at a glance
Take a walk through all of the aspects of the Forms Engine application. Figure 1 depicts the relation of the application to one or more servers that provide data entry forms of varying content.
Figure 1. Application architecture

In Figure 1, Form 1 provides registration for Robotics Competition and Form 2 asks the user for information about his auto maintenance habit. Using HTTP(S), the forms and Android application communicate to:
- Download the form metadata.
- Present form to the user and optionally collect device-specific data, such as camera images, sound recordings, GPS location, or compass readings.
- Collect user-supplied data.
- Submit data to the appropriate server.
The server side of this tutorial is implemented as a pair of files: an XML document describing the form and a PHP document responsible for recording the submission of the form. The Android application is a native application written in Java code using the Android SDK and coded in Android Studio.
Table 2 shows the application source files for the complete application. You can download all of these source files. You will go through each of these files in detail in this tutorial.
Table 2. The required application source files
Filename | Comment |
---|---|
XmlGui.java | Entry point for Android Activity |
XmlGuiForm.java | High level data model and methods for a form |
XmlGuiFormField.java | Represents a form field and holds the metadata for each field of a form |
XmlGuiEditBox.java | Implements a text box type user interface element |
XmlGuiPickOne.java | Implements a drop-down list type user interface element |
RunForm.java | Processes a form, using the above classes |
activity_xml_gui.xml | Home page of application user interface |
AndroidManifest.xml | Deployment descriptor for the Android application |
xmlgui1.xml | Sample form for collecting Robotics competition registration |
xmlgui1-post.php | PHP script for processing form submissions |
xmlgui2.xml | Sample form for taking survey of automotive maintenance habits |
Figure 2 shows the project structure in Android Studio for the complete application as it will look at the end of this tutorial.
Figure 2. Project in Android Studio

If you do not have a working Android development environment, now is a great time to install the Android Studio. For more information on how to setup an Android development environment, look in Resources for links to both of the required tools, plus some introductory articles and tutorials on developing applications for Android. Having a familiarity with Android is helpful in understanding this tutorial.
Now that you have an overview of the architecture and the application, you can get started!
The project and data model
We are now ready to start the Android project in Android Studio, creating the data model and the class that will allow you to store and manage metadata for the Forms Engine application.
Creating the project
Creating Android applications starts in the familiar place: Open Android Studio and select File > New as in Figure 3.
Figure 3. Creating a new Android application

This step launches the Android Studio New project wizard. Select Project. Be sure to give the project a valid identifier (I used XMLGUI). ON the next screen, select the version of devices you want your project to target. I am choosing Android 81.1 (Oreo) as shown in Figure 4.
Figure 4. Setting up a new project

Next we need to determine which Android version we want to target with our application. As shown in Figure 5, I am choosing Android 8.1 (Oreo).
Figure 5. Target Android Devices

Next, select Empty Activity as shown in Figure 6.
Figure 6. Select Empty Activity

On the next screen, give your Activity a name. I chose XmlGui. Your associated layout file will be named activity_xml_gui.xml, as shown in Figure 7.
Figure 7. Naming your Activity

Once the project is created, it should look very similar to the image shown previously in Figure 1.
With the project now created, it is good practice to ensure that the application builds cleanly and runs in the Android Emulator. To make sure it builds, select the hammer icon in the Android Studio menu and then the run application button (VCR play button). This causes various build steps to generate the required intermediate files and prepare the application to run on either a real device or an instance of the Android emulator. See the References section for help on the basics of building and running Android applications with Android Studio.
Now that the project is created, is configured, and starts properly in the Android emulator, it is time to create the XML driven data collection tool for Android.
The data model
The nuts and bolts of this application require it to present input elements to a user, collect data, validate the data, and then submit that data to a specified server location. It is worth noting that this application is set up for new records only. There are no provisions to look up an existing record for editing or deleting.
To provide enough direction to the application on how to present the data entry forms, a set of information (commonly referred to as metadata) is required. Metadata is data about data. In general terms, this application must understand a few data elements including:
- Form Name—Human readable name of the form
- Form Identifier—Unique identifier for this metadata collection
- Submission URL—Where to send the collected data
- One or more fields—These can be text, numeric, or “choose from a list” kind of fields
Virtually all kinds of questions map to one of these three types of user interface elements. For example, you can implement a check box as a Yes or No choice field. You can implement multi-select as multiple choice fields. Of course, you can extend the code shown in this tutorial as desired.
For your application, the usage scenario is as follows: You are at an event where you can register for one or more activities. You could fill out a piece of paper, or you could wait until you get home and hope that you remember to sign onto the organization’s website to register. In this case, you will assume that a user will fill out a simple form on the spot from his phone by pulling up a dynamic form on an Android device, providing the entrant’s first name, last name, gender, and age.
Listing 1 shows the contents of xmlgui1.xml, which represents a registration form for a Robotics club event.
<?xml version="1.0" encoding="utf-8"?>
<xmlgui>
<form id="1" name="Robotics Club Registration"
submitTo="http://serverurl/xmlgui1-post.php" >
<field name="fname" label="First Name" type="text" required="Y" options=""/>
<field name="lname" label="Last Name" type="text" required="Y" options=""/>
<field name="gender" label="Gender" type="choice" required="Y" options="Male|Female"/>
<field name="age" label="Age on 15 Oct. 2010" type="numeric" required="N" options=""/>
</form>
</xmlgui>
Note the following things about this XML document:
- The XML is very simple to parse thanks to extensive use of element attributes. This approach is used because it makes extracting the data easier than multiple child elements and tags. Using attributes in this manner also keeps the download size small and aids in keeping the parse time low.
- The
submitTo
attribute tells the application where to send the data once it is collected. If you are building this application yourself, you will want to replace serverurl with the path to your own web server, equipped with the ability to interpret PHP script files. - Each
field
element provides attributes for both a field name and a label. While these values are related, you want to keep the value of eachname
attribute unique from the othername
attribute values so the receiving application can properly parse and process them. You must also provide an informativelabel
value to the user as a cue to what kind of data should go into a particular field. - You can readily expand this approach to include default values for each field, a
regex
expression for validation, or a link for more information about a particular field. - The
options
field is used as a delimited list for thechoice
field.
With a basic familiarity with the data model, now look at the code responsible for turning this XML data into a useful application.
Representing the data
Parsing the data is a rather mechanical exercise shown later in this tutorial. Before examining the parsing process, the application needs some place to store and manage the metadata in memory. For this purpose, you have two Java classes, one for the form and one to represent the form field. Start by looking at XmlGuiForm.java
in Listing 2.
package com.navitend.xmlgui;
import android.util.Log;
import java.util.Vector;
import java.util.ListIterator;
import java.net.URLEncoder;
public class XmlGuiForm {
private String formNumber;
private String formName;
private String submitTo;
public Vector<XmlGuiFormField> fields;
public XmlGuiForm()
{
this.fields = new Vector<XmlGuiFormField>();
formNumber = "";
formName = "";
submitTo = "loopback"; // ie, do nothing but display the results
}
// getters & setters
public String getFormNumber() {
return formNumber;
}
public void setFormNumber(String formNumber) {
this.formNumber = formNumber;
}
public String getFormName() {
return formName;
}
public void setFormName(String formName) {
this.formName = formName;
}
public String getSubmitTo() {
return submitTo;
}
public void setSubmitTo(String submitTo) {
this.submitTo = submitTo;
}
public Vector<XmlGuiFormField> getFields() {
return fields;
}
public void setFields(Vector<XmlGuiFormField> fields) {
this.fields = fields;
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("XmlGuiForm:\n");
sb.append("Form Number: " + this.formNumber + "\n");
sb.append("Form Name: " + this.formName + "\n");
sb.append("Submit To: " + this.submitTo + "\n");
if (this.fields == null) return sb.toString();
ListIterator<XmlGuiFormField> li = this.fields.listIterator();
while (li.hasNext()) {
sb.append(li.next().toString());
}
return sb.toString();
}
public String getFormattedResults()
{
StringBuilder sb = new StringBuilder();
sb.append("Results:\n");
if (this.fields == null) return sb.toString();
ListIterator<XmlGuiFormField> li = this.fields.listIterator();
while (li.hasNext()) {
sb.append(li.next().getFormattedResult() + "\n");
}
return sb.toString();
}
public String getFormEncodedData()
{
try {
int i = 0;
StringBuilder sb = new StringBuilder();
if (this.fields == null) return sb.toString();
ListIterator<XmlGuiFormField> li = this.fields.listIterator();
while (li.hasNext()) {
if (i != 0) sb.append("&");
XmlGuiFormField thisField = li.next();
sb.append(thisField.name + "=");
String encstring = new String();
String rawString = (String) thisField.getData();
encstring = URLEncoder.encode(rawString);
sb.append(encstring);
i++;
}
return sb.toString();
}
catch (Exception e) {
e.printStackTrace();
return "ErrorEncoding " + e.getMessage();
}
}
}
Here are some important items to note about the XmlGuiForm
class.
- There are four member variables:
formNumber
: This is the unique identifier for the server side form distribution mechanism.formName
: This becomes the title of the form, providing context and confirmation for the user.submitTo
: This is the URL for the application to submit the data entered (after validation). Alternatively this value can be a loopback. In the loopback scenario, the data is displayed to the user rather than submitted to the server. This is useful for testing purposes.fields
: This is a Vector class templated to hold the form’s field data. Listing 3 shows the details forXmlGuiFormField.java
. - A series of
getters
andsetters
for each of these variables. - The
toString()
andgetFormattedResults()
methods are responsible for generating human readable summarizations of theXmlGuiForm
class. - The
getFormEncodedData()
method is used when preparing data for submission to the URL indicated in thesubmitTo
attribute. - Rather than using strictly concatenated
java.lang.String
classes, the code employs aStringBuilder
as a more memory efficient means of building the desired data strings. - The
URLEncoder
class is leveraged to prepare data for submission to the server. This makes the data look like it was actually created by a traditional HTML form. - Some potential expansions of this application include: Local storage or caching of metadata to make repetitive tasks run more quickly. Local storage to record data over a period of time prior to submission. GPS recording—stamp each record with location data.
Now look at the construction of the XmlGuiFormField
class in Listing 3.
package com.navitend.xmlgui;
import com.navitend.xmlgui.XmlGuiEditBox;
// class to handle each individual form
public class XmlGuiFormField {
String name;
String label;
String type;
boolean required;
String options;
Object obj; // holds the ui implementation, i.e., the EditText for example
// getters & setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public boolean isRequired() {
return required;
}
public void setRequired(boolean required) {
this.required = required;
}
public String getOptions() {
return options;
}
public void setOptions(String options) {
this.options = options;
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Field Name: " + this.name + "\n");
sb.append("Field Label: " + this.label + "\n");
sb.append("Field Type: " + this.type + "\n");
sb.append("Required? : " + this.required + "\n");
sb.append("Options : " + this.options + "\n");
sb.append("Value : " + (String) this.getData() + "\n");
return sb.toString();
}
public String getFormattedResult()
{
return this.name + "= [" + (String) this.getData() + "]";
}
public Object getData()
{
if (type.equals("text") || type.equals("numeric"))
{
if (obj != null) {
XmlGuiEditBox b = (XmlGuiEditBox) obj;
return b.getValue();
}
}
if (type.equals("choice")) {
if (obj != null) {
XmlGuiPickOne po = (XmlGuiPickOne) obj;
return po.getValue();
}
}
// todo, add other UI elements here
return null;
}
}
Look more closely at the XmlGuiFormField
class.
- There are six class level members:
name
holds the name of the field—this is the field name of the data value, analogous to a form field name in HTML or a database column name.label
holds the description of the field or the value shown to the user.type
indicates the flavor of user interface field to construct.text
means this is field is implemented with anEditText
field for alphanumeric entries. This is the most common value.numeric
is also anEditText
, but it is constrained to a numeric entry value.choice
makes the field a drop-down list.required
is a Boolean value marking the field as required or not. If the field is required and not populated, an error message is displayed to the user when the user attempts to submit the form.options
is a string value used to convey the list of available selections for a choice field. This field is available for other fields to be used as perhaps aregex
expression for validation or it can be overridden to specify a default value.obj
represents the user interface widget. For example, this variable holds anEditText
for a text or numeric field. For a choice field, theobj
member contains aspinner
widget. This approach is further explained later in this tutorial.
- As expected, these variables have a number of
getters
andsetters
. - The
toString()
andgetFormattedResult()
methods both leverage thegetData()
method, explained next. - In the
XmlGuiFormField
class, you need to manage more than one type of data, so the code needs to be explicit about how data is stored and accessed. ThegetData()
method examines the type field and performs a type-cast on theobj
field to properly interact with the stored object. If you wish to add new field types to this framework, you can expand thegetData()
method to support the new field type (see the comment near the end of Listing 3.
You now have a way to store and manage metadata. It is time to look at the application in action and then tie the various pieces together.
Assemble a user interface
Start by creating a form for a mobile user to enter data into.
Taking it from the top
The entry point of the application resides in XmlGui.java
, as in Listing 4.
package com.navitend.xmlgui;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class XmlGui extends AppCompatActivity {
final String tag = XmlGui.class.getName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_xml_gui);
Button btnRunForm = (Button) this.findViewById(R.id.btnRunForm);
btnRunForm.setOnClickListener(new Button.OnClickListener()
{
public void onClick(View v)
{
EditText formNumber = (EditText) findViewById(R.id.formNumber);
Log.i(tag,"Attempting to process Form # [" + formNumber.getText().toString() + "]");
Intent newFormInfo = new Intent(XmlGui.this,RunForm.class);
newFormInfo.putExtra("formNumber", formNumber.getText().toString());
startActivity(newFormInfo);
}
});
}
The user interface for the main Activity
is very simple, consisting of only:
- A label (“TextView)
- An entry box (“EditText)
- A button (“Button)
The code for XmlGui Activity
is very standard in nature. You inflate a layout
created at design time and then define and create a button handler to implement the desired functionality (which is explained further below).
You define the user interface in the file main.xml (found in the layout subfolder of the res folder). Listing 5 shows main.xml.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/Title"
/>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<EditText
android:layout_width="100px"
android:layout_height="wrap_content"
android:text="1"
android:id="@+id/formNumber"
android:numeric="integer"/>
<Button android:text="Run Form" android:id="@+id/btnRunForm"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</Button>
</LinearLayout>
</LinearLayout>
As a reminder, you can modify layouts by editing the XML directly or by using the Layout tool contained in the Android Studio as in Figure 8.
Figure 8. Layout tool

Build and run the application. Android Studio will prompt you to choose either a connected device or a configured Android Virtual Device as shown in Figure 9.
Figure 9. Select a device to test the application

Figure 10. Application in action

When the user enters a form number and taps the Run Form button, a series of events is kicked off. Let’s go through the onClick()
method, line by line. Recall that the onClick() method is in the XmlGui class in Listing 4.
You get a reference to the EditText
field named formNumber
. The R.id.formNumber
enumeration is automatically generated by the Android build tools whenever the activity_xml_gui.xml layout file is saved:
EditText formNumber = (EditText) findViewById(R.id.formNumber);
Next, you put a line into the log. You can see the output of this log in the LogCat window in Android Studio:
Log.i(tag,"Attempting to process Form # [" + formNumber.getText().toString() + "]");
The actual implementation of the Form Engine is provided in the RunForm
class, which is a separate Activity
. To launch this Activity
, create an Intent
, explicitly identifying the RunForm
class:
Intent newFormInfo = new Intent(XmlGui.this,RunForm.class);
Not only do you want to launch the RunForm Activity
, but you also want to specify which form to display. To do this, add the form number to the Intent through the putExtra
method:
newFormInfo.putExtra("formNumber", formNumber.getText().toString());
This value is extracted by the RunForm class, shown later. Now that you have set up the Intent, you launch the Activity
with a call to startActivity
:
startActivity(newFormInfo);
With your application, the user enters the form number and clicks the Run Form button. This triggers the events described above, causing the RunForm
class to process the request. Entering a form number is really just a test tool for the purposes of this tutorial. There are other means by which this triggering event can take place. More practical examples include customized links from a web page, a message pushed through Short Message Service (SMS), a location-based trigger based on proximity, or even a scanned QR code (Quick Response code).
Running the form
The RunForm
class is the choreographer for this application. It is launched with a form number to process. Now examine the onCreate()
method in Listing 6.
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String formNumber = "";
Intent startingIntent = getIntent();
if(startingIntent == null) {
Log.e(tag,"No Intent? We're not supposed to be here...");
finish();
return;
}
formNumber = startingIntent.getStringExtra("formNumber");
Log.i(tag,"Running Form [" + formNumber + "]");
String url = this.getString(R.string.fetchformurl) + formNumber + ".xml";
new GetFormData().execute(url);
}
As seen in the code in Listing 6, first you extract the formNumber
from the Intent
that triggered the Activity
. Without a form number to process, this Activity
has nothing to perform.
Once you extract the value, the next requirement is to connect to the server to download the form specifications. (Note that an enhancement of this approach might be to look for this form’s metadata in a local cache prior to fetching the data.) In order to download the data from the server, we need to perform the network request on a “background thread,” as you are not permitted to perform this sort of operation on the main UI thread. We also need a server URL to access. Note that this is the kind of constant value that we want to store outside of the source code. For this application, we store it in the res/strings.xml file with a reference identifier of fetchformaturl. The getString()
method retrieves this value for us. Listing 7 shows the GetFormData()
class, which extends the AsyncTask
class` which performs the actual download of our form meta data.
private class GetFormData extends AsyncTask<String, Void, Boolean> {
@Override
protected Boolean doInBackground(String... strings) {
String formData = null;
try {
String formURL = strings[0];
URL url = new URL(formURL);
Log.i(tag,url.toString());
InputStream is = url.openConnection().getInputStream();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder db = factory.newDocumentBuilder();
Document dom = db.parse(is);
Element root = dom.getDocumentElement();
NodeList forms = root.getElementsByTagName("form");
if (forms.getLength() < 1) {
// nothing here??
Log.e(tag,"No form, let's bail");
return false;
}
Node form = forms.item(0);
theForm = new XmlGuiForm();
// process form level
NamedNodeMap map = form.getAttributes();
theForm.setFormNumber(map.getNamedItem("id").getNodeValue());
theForm.setFormName(map.getNamedItem("name").getNodeValue());
if (map.getNamedItem("submitTo") != null)
theForm.setSubmitTo(map.getNamedItem("submitTo").getNodeValue());
else
theForm.setSubmitTo("loopback");
// now process the fields
NodeList fields = root.getElementsByTagName("field");
for (int i=0;i<fields.getLength();i++) {
Node fieldNode = fields.item(i);
NamedNodeMap attr = fieldNode.getAttributes();
XmlGuiFormField tempField = new XmlGuiFormField();
tempField.setName(attr.getNamedItem("name").getNodeValue());
tempField.setLabel(attr.getNamedItem("label").getNodeValue());
tempField.setType(attr.getNamedItem("type").getNodeValue());
if (attr.getNamedItem("required").getNodeValue().equals("Y"))
tempField.setRequired(true);
else
tempField.setRequired(false);
tempField.setOptions(attr.getNamedItem("options").getNodeValue());
theForm.getFields().add(tempField);
}
Log.i(tag,theForm.toString());
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
DisplayForm();
} else {
Log.e(tag,"Couldn't parse the Form.");
finish();
Toast.makeText(getApplicationContext(), "Form not valid!",
Toast.LENGTH_LONG).show();
}
}
}
This code is responsible for taking data from a metadata repository, in this case by downloading an XML file from a Webserver.
In the doInBackground method, we extract the passed-in url by referencing the first string value and passing it to the URL class constructor:
String formURL = strings[0];
URL url = new URL(formURL);
Manipulate the XML data through a DOM parser, to extract the form and field elements plus attributes and store them in instances of the XmlGuiForm
and XmlGuiFormField
classes respectively. The bulk of this method is dedicated to the parsing and populating tasks.
Two main approaches to XML parsing are DOM and SAX. The DOM parser works by parsing a document into memory and then the application walks a Document Object Model tree to gain access to various elements of data contained in the XML. You also could also use the SAX parser model here because you build your own representation of the document by populating the two classes.
The advantage of the DOM approach for this application is that it is somewhat procedural and easy to follow the code, whereas the SAX approach requires call-back functions where only the desired data is stored. The complexity of the code written by the developer to implement the SAX callback functions can be noticeably higher than the DOM approach in some instances. Because the XML data is fully parsed in the DOM approach, it is a bit more memory intensive. For your purposes in this application, the simplicity and easy-to-follow nature of DOM was a bigger driver than memory management as the metadata form is quite small.
See Resources for an alteraative approache to coding XML parsers in Android.
You have transformed the XML metadata form to Java class instances. It is now time to display the form to gather data from the user.
Assuming a successful parsing of the xml form meta data, we return true in the doInBackground
method. The onPostExecute
method handles the next step where we either call the DisplayForm
method or display an error message and close this Activity
.
Gather user data
Now that you’ve created the main Activity
screen layout, you can create user interface forms for collecting data. Here you’ll create a Robotics Club Registration form and an Auto Maintenance survey form.
Using the metadata
This application hinges upon the ability of Android programmers to dynamically manipulate the user interface. Earlier in the tutorial, you examined the activity_xml_gui.xml file, the file that defines the screen layout of the XmlGui
class (the main Activity
). This application would be virtually impossible in its current form if you had to always define user interface elements at design or compile time.
Thankfully, you are not constrained in that manner. The DisplayForm()
method is responsible for converting the metadata into user interface elements for the purposes of collecting data. The code is essentially broken into two main functional areas: the layout of the user interface elements and then the handling of the submit button.
First, examine the layout logic. This is the code that turns the XmlGuiForm
object into a real on-the-screen form. Listing 8 shows this code.
private boolean DisplayForm()
{
try
{
ScrollView sv = new ScrollView(this);
final LinearLayout ll = new LinearLayout(this);
sv.addView(ll);
ll.setOrientation(android.widget.LinearLayout.VERTICAL);
// walk through the form elements and dynamically create them,
// leveraging the mini library of tools.
int i;
for (i=0;i<theForm.fields.size();i++) {
if (theForm.fields.elementAt(i).getType().equals("text")) {
theForm.fields.elementAt(i).obj = new
XmlGuiEditBox(this,(theForm.fields.elementAt(i).isRequired()
? "*" : "") + theForm.fields.elementAt(i).getLabel(),"");
ll.addView((View) theForm.fields.elementAt(i).obj);
}
if (theForm.fields.elementAt(i).getType().equals("numeric")) {
theForm.fields.elementAt(i).obj = new
XmlGuiEditBox(this,(theForm.fields.elementAt(i).isRequired()
? "*" : "") + theForm.fields.elementAt(i).getLabel(),"");
((XmlGuiEditBox)theForm.fields.elementAt(i).obj).makeNumeric();
ll.addView((View) theForm.fields.elementAt(i).obj);
}
if (theForm.fields.elementAt(i).getType().equals("choice")) {
theForm.fields.elementAt(i).obj = new
XmlGuiPickOne(this,(theForm.fields.elementAt(i).isRequired()
? "*" : "") + theForm.fields.elementAt(i).getLabel(),
theForm.fields.elementAt(i).getOptions());
ll.addView((View) theForm.fields.elementAt(i).obj);
}
}
Button btn = new Button(this);
btn.setLayoutParams(new LayoutParams
(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.
WRAP_CONTENT));
ll.addView(btn);
btn.setText("Submit");
btn.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
// check if this form is Valid
if (!CheckForm())
{
AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
AlertDialog ad = bd.create();
ad.setTitle("Error");
ad.setMessage("Please enter all required (*) fields");
ad.show();
return;
}
if (theForm.getSubmitTo().equals("loopback")) {
// just display the results to the screen
String formResults = theForm.getFormattedResults();
Log.i(tag,formResults);
AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
AlertDialog ad = bd.create();
ad.setTitle("Results");
ad.setMessage(formResults);
ad.show();
return;
} else {
if (!SubmitForm()) {
AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
AlertDialog ad = bd.create();
ad.setTitle("Error");
ad.setMessage("Error submitting form");
ad.show();
return;
}
}
}
} );
setContentView(sv);
setTitle(theForm.getFormName());
return true;
} catch (Exception e) {
Log.e(tag,"Error Displaying Form");
return false;
}
You must anticipate the availability of more fields than can fit on a single screen, so use a ScrollView
as the parent view or container. Within that ScrollView
you employ a vertical LinearLayout
to organize the various user interface widgets into a vertical column.
The approach is pretty simple:
- You enumerate through the list of
XmlGuiFormField
objects contained within thefields
member of theXmlGuiForm
instance. - Depending on the type of field requested, a different user interface element is instantiated and added to the
LinearLayout
. You will examine the different UI widgets momentarily.
Once the UI elements are created and added to the linear layout, you assign the entire ScrollView
instance to the content of this screen and assign the form name as the title of the screen. Figure 11 shows a Robotics club registration screen ready for user input. This form is the result of processing the XML data found back in Listing 1.
Figure 11. Robotics registration form in action

Let’s have a look at the different custom user interface widgets created for this application.
Recall that three types of data entry fields are defined for this application: text, numeric, and choice. These three types are implemented through two different custom widgets: XmlGuiEditBox
and XmlGuiPickOne
.
The text and numeric values are so similar you can leverage the same EditView
approach, but with different input filters to switch between alpha-numeric and numeric only. Listing 9 shows the code for the XmlGuiEditBox
class.
package com.navitend.xmlgui;
import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.EditText;
import android.text.method.DigitsKeyListener;
public class XmlGuiEditBox extends LinearLayout {
TextView label;
EditText txtBox;
public XmlGuiEditBox(Context context,String labelText,String initialText) {
super(context);
label = new TextView(context);
label.setText(labelText);
txtBox = new EditText(context);
txtBox.setText(initialText);
txtBox.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams
.FILL_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT));
this.addView(label);
this.addView(txtBox);
}
public XmlGuiEditBox(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public void makeNumeric()
{
DigitsKeyListener dkl = new DigitsKeyListener(true,true);
txtBox.setKeyListener(dkl);
}
public String getValue()
{
return txtBox.getText().toString();
}
public void setValue(String v)
{
txtBox.setText(v);
}
}
The XmlGuiEditBox
class extends the LinearLayout
class and contains both a textual label to describe the requested input and an EditText
to actually collect the entered data. All of the object initialization is done in the constructor. This may be considered bad form, but this is an exercise left to you if you’re uncomfortable with that approach.
There are three other methods to discuss. The getValue()
and setValue()
methods do just what you would think. They are the getter
and setter
for interacting with the EditText
field.
The third method, makeNumeric()
is only called when setting up a form field type of numeric. An instance of the DigitsKeyListener
is employed to filter out any non-numeric keys. The other nice thing that you get for free is the proper keyboard is shown depending on which type of XmlGuiEditBox
is in use— with or without the numeric setting.
Figure 12 shows the form in action with an alpha keyboard shown because the Last Name field is set for alpha entry; in other words, text.
Figure 12. Alphanumeric key entry

Figure 13 shows the numeric keyboard in use because the age field is set for the data type of numeric
.
Figure 13. Numeric keyboard

The choice field, implemented in the user interface through the XmlGuiPickOne
class, is a little different. The choice field is implemented as an Android Spinner
widget. This user interface element is analogous to a drop-down list box in other programming environments, where the user must select from one of the existing choices. Figure 14 shows three instances XmlGuiPickOne
widget.
Figure 14. Auto maintenance survey with three XmlGuiPickOne instances

In this example, the data being collected is for statistical purposes, so normalizing the possible entries makes the data processing cleaner than dealing with free text entry fields. Of course, you can define the State field as a choice field if you wanted to constrain the survey to a particular geographical region.
Listing 10 shows the code for the XmlGuiPickOne
class.
package com.navitend.xmlgui;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Spinner;
import android.widget.ArrayAdapter;
public class XmlGuiPickOne extends LinearLayout {
String tag = XmlGuiPickOne.class.getName();
TextView label;
ArrayAdapter<String> aa;
Spinner spinner;
public XmlGuiPickOne(Context context,String labelText,String options) {
super(context);
label = new TextView(context);
label.setText(labelText);
spinner = new Spinner(context);
String []opts = options.split("\\|");
aa = new ArrayAdapter<String>( context,
android.R.layout.simple_spinner_item,opts);
spinner.setAdapter(aa);
this.addView(label);
this.addView(spinner);
}
public XmlGuiPickOne(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public String getValue()
{
return (String) spinner.getSelectedItem().toString();
}
}
This class looks very similar to the XmlGuiEditBox
class. The major difference is that a Spinner
control is employed rather than an EditText
control. Also, note that this class only implements the getValue()
method. An obvious enhancement to this class is permit the user to specify a default value.
Note the use of the options
member to populate the list of choices. In this code, the String containing the available choices is split into an array using a regex
expression and then passed to an instance of an ArrayAdapter
. The constant android.R.layout.simple_spinner_item
is built-in to Android. It was not supplied in the tutorial application code. Once the adapter is set up, you assign it to the Spinner. Figure 15 shows the list of choices displayed on the screen, prompting the user for the typical number of miles between oil changes.
Figure 15. XmlGuiPickOne asking about oil changes

Now that the user can enter data into the form, it is time to validate and submit the data.
Save and submit data
You must now create a way for users to save the data by validating it and submitting it to a server.
Saving data
It is time to jump back into the DisplayForm()
method of the RunForm
class. Recall that the first portion of this method is responsible for the drawing of the form. Next, you will examine the onClick()
handler of the submit button, in Listing 11.
btn.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
// check if this form is Valid
if (!CheckForm())
{
AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
AlertDialog ad = bd.create();
ad.setTitle("Error");
ad.setMessage("Please enter all required (*) fields");
ad.show();
return;
}
if (theForm.getSubmitTo().equals("loopback")) {
// just display the results to the screen
String formResults = theForm.getFormattedResults();
Log.i(tag,formResults);
AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
AlertDialog ad = bd.create();
ad.setTitle("Results");
ad.setMessage(formResults);
ad.show();
return;
} else {
if (!SubmitForm()) {
AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
AlertDialog ad = bd.create();
ad.setTitle("Error");
ad.setMessage("Error submitting form");
ad.show();
return;
}
}
}
} );
When the submit button is selected by the user, the form entries are checked to make sure that all of the required fields are populated. If not, an AlertDialog
reminds the user to populate all of the fields. Assuming the data has been entered in a satisfactory manner, it is time to submit the data.
The process of submitting the data falls into one of two camps for this tutorial application. If the submitTo
field of the form has been set to the value of loopback, the values are simply echoed to the screen. This is useful for testing purposes. Once you are satisfied that the form tool is collecting data properly, it is time to point it to a server page which is responsible for recording the entries.
Listing 12 shows the CheckForm()
method. This code is rather straight-forward. Each field is checked to see if it is required. If the field is required but the user has not provided the information, a flag is set. You could enhance this to provide more specific feedback to the user.
private boolean CheckForm()
{
try {
int i;
boolean good = true;
for (i=0;i<theForm.fields.size();i++) {
String fieldValue = (String)
theForm.fields.elementAt(i).getData();
Log.i(tag,theForm.fields.elementAt(i)
.getName() + " is [" + fieldValue + "]");
if (theForm.fields.elementAt(i).isRequired()) {
if (fieldValue == null) {
good = false;
} else {
if (fieldValue.trim().length() == 0) {
good = false;
}
}
}
}
return good;
} catch(Exception e) {
Log.e(tag,"Error in CheckForm()::" + e.getMessage());
e.printStackTrace();
return false;
}
}
Now it is time to submit the collected data to the server. Examine the SubmitForm()
method in Listing 13.
private boolean SubmitForm()
{
try {
boolean ok = true;
this.progressDialog = ProgressDialog.show(this,
theForm.getFormName(), "Saving Form Data", true,false);
this.progressHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// process incoming messages here
switch (msg.what) {
case 0:
// update progress bar
progressDialog.setMessage("" + (String) msg.obj);
break;
case 1:
progressDialog.cancel();
finish();
break;
case 2:
progressDialog.cancel();
break;
}
super.handleMessage(msg);
}
};
Thread workthread = new Thread(new TransmitFormData(theForm));
workthread.start();
return ok;
} catch (Exception e) {
Log.e(tag,"Error in SubmitForm()::" + e.getMessage());
e.printStackTrace();
// tell user that the submission failed....
Message msg = new Message();
msg.what = 1;
this.progressHandler.sendMessage(msg);
return false;
}
}
Earlier when we downloaded the form’s meta data, we used an extension of the AsyncTask
class. This time around, we are going to take a slightly different approach of using a Progress Dialog and explicitly creating a separate Thread to submit the data to the server. Let’s have a look.
First, you set up an instance of the android.os.Handler
class. The Handler
class is helpful when an application needs to share data with different threads. Another important item to note in the SubmitForm()
method is the use of a ProgressDialog
. Note that the ProgressDialog
and Handler
are defined as class level variables in Listing 14.
public class RunForm extends Activity {
/** Called when the activity is first created. */
String tag = RunForm.class.getName();
XmlGuiForm theForm;
ProgressDialog progressDialog;
Handler progressHandler;
...
}
You don’t want to unnecessarily block the application while communicating with the server, so you employ a background Thread to communicate, but you rely on the Handler to receive notifications from the communications thread in order to provide feedback to the user. As a reminder, we need to take this extra trouble because only the main thread is supposed to interact with the user interface.
As the application connects to the server to transfer the data, it has the opportunity to inform the user of the status of the operation, which is of course good practice. Figure 16 shows the ProgressDialog
in action.
Figure 16. The ProgressDialog

The actual server communications code is found in Listing 15, in the TransmitFormData()
class, which implements the Runnable interface.
private class TransmitFormData implements Runnable
{
XmlGuiForm _form;
Message msg;
TransmitFormData(XmlGuiForm form) {
this._form = form;
}
public void run() {
try {
msg = new Message();
msg.what = 0;
msg.obj = ("Connecting to Server");
progressHandler.sendMessage(msg);
URL url = new URL(_form.getSubmitTo());
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
BufferedOutputStream wr = new BufferedOutputStream
(conn.getOutputStream());
String data = _form.getFormEncodedData();
wr.write(data.getBytes());
wr.flush();
wr.close();
msg = new Message();
msg.what = 0;
msg.obj = ("Data Sent");
progressHandler.sendMessage(msg);
// Get the response
BufferedReader rd = new BufferedReader(new
InputStreamReader(conn.getInputStream()));
String line = "";
Boolean bSuccess = false;
while ((line = rd.readLine()) != null) {
if (line.indexOf("SUCCESS") != -1) {
bSuccess = true;
}
// Process line...
Log.v(tag, line);
}
wr.close();
rd.close();
if (bSuccess) {
msg = new Message();
msg.what = 0;
msg.obj = ("Form Submitted Successfully");
progressHandler.sendMessage(msg);
msg = new Message();
msg.what = 1;
progressHandler.sendMessage(msg);
return;
}
} catch (Exception e) {
Log.d(tag, "Failed to send form data: " + e.getMessage());
msg = new Message();
msg.what = 0;
msg.obj = ("Error Sending Form Data");
progressHandler.sendMessage(msg);
}
msg = new Message();
msg.what = 2;
progressHandler.sendMessage(msg);
}
}
The TransmitFormData
class is responsible for connecting to the server listed in the submitTo
member of the XmlGuiForm
instance (as taken from the metadata). It periodically updates the main application thread by sending an instance of a Message
class to the handler through the sendMessage()
method. Two members are populated on the Message
class:
- The
what
value acts as a high-level switch informing the Handler what it should do with the message. - The
obj
value specifies an optionaljava.lang.Object
. In this case, ajava.lang.String
instance is passed and used for displaying in the Progress Dialog.
The schema used by any given application is arbitrary. This application uses the values in Table 3.
Table 3. The application values allowed for what
Value | Comment |
---|---|
0 | Obj contains a text string to display to the user. |
1 | Successful completion of transmission, you’re done! |
2 | An error occurred. Tell the user that something is wrong and don’t throw away the data! |
Figure 17 shows the final message in the ProgressDialog
upon a successful transmission of Form Data.
Figure 17. Form submission

Once the form has been successfully submitted, the application returns to the main page. For a production-ready application, what takes place next is highly dependent on the motives of the data gathering organization. The screen can simply reset to take another entry, as in a physical inventory application. Or perhaps you can direct the user to some other screen.
For the application to run properly, the AndroidManifest.xml
file must contain references to all of the used Activity
classes and must include the uses-permission for Internet access. Listing 16 shows the AndroidManifest.xml
file for the tutorial’s application.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.navitend.xmlgui "
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".XmlGui"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".RunForm">
</activity>
</application>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
</manifest>
Before wrapping up, take a brief look at the server side script.
Provide a server side script
For the purposes of this tutorial, you will use a PHP script to gather the required data, and append it to a text file.
On the server
Exactly what transpires on the server is dependent on the needs of the organization collecting the data. A common approach for data collection is to store the form data in a relational database such as DB2®, MySQL, SQL Server, Oracle, and so on. Once the data is in the database, it can be sliced, diced, and analyzed.
For this tutorial, the data is gathered by a PHP script and appended to a text file. Listing 17 shows the PHP form associated with the Robotics registration form.
<?php
// xmlgui form # 1
// this page is expecting
// fname
// lname
// gender
// age
$filename = "/pathtowritablefile/datafile.txt";
$f = fopen($filename,"a");
fprintf($f,"Data received @ ".date(DATE_RFC822));
fprintf($f,"\n");
fprintf($f,'First Name:['.$_POST['fname'].']');
fprintf($f,"\n");
fprintf($f,'Last Name:['.$_POST['lname'].']');
fprintf($f,"\n");
fprintf($f,'Gender:['.$_POST['gender'].']');
fprintf($f,"\n");
fprintf($f,'Age:['.$_POST['age'].']');
fprintf($f,"\n");
fclose($f);
print "SUCCESS";
?>
If the script returns the string SUCCESS
, the RunForm
class will reset. Any other value will cause an error message to be displayed to the user and permit them to correct their entries or otherwise obtain help in submitting the form.
IMPORTANT SECURITY NOTE: Never permit data to be uploaded to your server in a place where someone can subsequently execute it. In Listing 17, make sure that the path you are writing to is in a temp folder that is not accessible from the public internet.
Conclusion
This tutorial presented a framework for serving dynamic questions to an Android user based on a native application approach utilizing the Android SDK. You saw dynamic form presentation, validation and processing techniques, and application-to-web server communications.