I’ve seen quite a few discussions pop up in various forums like Stack Overflow and the Android Developer Google Group about dynamically creating U.I. in Android applications. The official Android documentation seems to be very keen on the xml for layouts approach. This approach allows you to easily separate views from logic, which is a good thing. However, writing xml files for U.I. is not always flexible when working with dynamic data.
I’m working on a project right now that involves dynamically generating form-based user interfaces. The interfaces are assembled based on JSON schema files generated by a web service. I’ve written a small framework for handling this kind of task. In this post I’ll walk through the basic usage of the framework. I won’t go into a much detail about how the internal code is put together, but I will cover the basic API and the schema structure.
Core Features
- auto-creation of form-based U.I. from json schema files
- auto-population of form-based U.I. from existing json data ( must match schema format )
- auto-layout based on priority indicated in schema ( also handles creating scroll bars for forms taller than the screen )
- conditional visibility of form elements based on user input
- functionality for saving user input into compatible json format
Download the source code here.
Basic API Usage
This section will explain the methods for creating, populating and saving the state of a form-based u.i.
Form Activity
The FormActivity class is an abstract class that takes care of most of the heavy lifting for you. It is intended to be sub-classed in your application. In the example project the sub-class is called FormGeneratorExample. It will work just like other Activity classes in the Android framework. Below we will look at its public methods.
generateForm( String data )
This method is responsible for parsing the JSON schema and creating the form. It should be supplied a string of valid JSON data. Note that the method does not require a JSONObject. There is a method in the FormActivity class for converting a file to a String. To generate a form-based U.I. we use the following method which produces the image below:
generateForm( FormActivity.parseFileToString( this, "schemas.json" ) );
populate( String data )
When supplied a String of JSON data that matches the current schema, this method will populate each field of the form. The syntax is nearly identical to generating a form, again parsing a file with JSON data to a String.
populate( FormActivity.parseFileToString( this, "data.json" ) );
save()
This method zips through each field of the form and caches the current attribute/value pairs into a JSONObject. From the image above it produces this JSON data.
{
"genre":"2",
"imported_from":"1",
"release_year":"1999",
"import":"1",
"label":"1",
"artist":"Boards Of Canada ",
"album_title":"Music Has The Right To Children",
"formats":"1"
}
The Form Schema
As mentioned, each form is created from a JSON schema file. Below is a snippet from the schema included in the example project. It represents a form for audio recordings. The complete schema file is located in the assets folder of the example project. Each field in the schema represents a widget in the form-based U.I.
{
"artist": {
"type": "string",
"id": "0",
"default": "",
"priority": "0"
},
"genre": {
"type": "integer",
"id": "5",
"default": "0",
"priority": "1",
"options": {
"0": "None",
"1": "Rock",
"2": "Electronic",
"3": "Country",
"4": "Hip-Hop",
"5": "Jazz",
"6": "Pop",
"7": "World"
}
}
}
The first field in the snippet above is “artist”. The name of each field is used as a label for the input widget representing the field. This is important to note because it means that the names you give each field in the schema are converted to displayable names. You can use multiple word names by separating words with underscores. As an example, a field with the name “album_title” would be converted to Album Title when displayed as a label for the widget.
Required Fields
Each field in the schema has a few required attributes. These attributes are required because the code that parses a schema uses them to build the widgets in the form-based U.I. The required attributes are:
type – the data type of the field; this value ultimately informs the generator which kind of widget to create
default - a default value for the field; this value should match the data type specified by the type attribute; in the case of Spinner widgets the default should be an integer corresponding to an item in the options list ( not the actual string value )
priority – priority is used to visually order the widgets; by default all widgets are stacked vertically; if you do not need the widgets in a form to be in a specific order you can just set the value to zero; however do not omit this field from the schema
The Type Attribute
The type attribute of each field indicates which type of widget to use to represent the field.
string:
indicates to the parser to create an FormEditText widget; fields of type string also support an optional attribute called “hint”; for text input widgets the hint attribute sets the default text for when the widget contains no text; below is an example:
"release_date": {
"type": "string",
"id": "2",
"default": "",
"priority": "2",
"hint":"Example: 10/30/1999"
},
integer:
indicates to the parser either a FormNumericEditText widget or a FormSpinner widget if there is an options object; below is an example of an integer type with an options object
"genre": {
"type": "integer",
"id": "5",
"default": "0",
"priority": "1",
"options": {
"0": "None",
"1": "Rock",
"2": "Electronic",
"3": "Country",
"4": "Hip-Hop",
"5": "Jazz",
"6": "Pop",
"7": "World"
}
}
boolean:
indicates to the parser to create a FormCheckBox widget; notice that the default value of the boolean field is “0″ and not false; booleans are represented as one or zero in the schema
"import": {
"type": "boolean",
"id": "6",
"default": "0",
"priority": "7",
},
Optional Attributes
Toggles
The framework allows you to specify the conditional visibility of form widgets based on user selections. In the example project there is a field called “import” which is of type boolean and represented in the U.I. as a FormCheckBox. When it is selected an “Imported From” FormSpinner widget is displayed. To achieve this we add an attribute to the “import” field called “toggles”. Below is an example of the “toggles” attribute:
"import": {
"type": "boolean",
"id": "6",
"default": "0",
"priority": "7",
"toggles": {
"1": [ "imported_from" ]
}
},
"imported_from": {
"type": "integer",
"id": "7",
"default": "0",
"priority": "8",
"options": {
"0": "N/A",
"1": "Spain",
"2": "Italy",
"3": "Germany",
"4": "France",
"5": "Japan",
"6": "Australia",
"7": "China",
"8": "United States"
}
}
The syntax of the toggles attribute works like this. The “1″ indicates that when the value of the FormCheckBox changes to “1″ or true make all of the widgets in the corresponding array visible. The field could also indicate that multiple items toggle their visibility or that when the value is set to false or “0″ that other widgets become invisible, as in the example below:
"import": {
"type": "boolean",
"id": "6",
"default": "0",
"priority": "7",
"toggles": {
"1": [ "imported_from", "record_label", "original_release_date", "import_release_date" ],
"0":[ "distributor" ]
}
},
The toggles attribute can also be used with FormSpinner widgets. Below is an example:
"label": {
"type": "integer",
"id": "3",
"default": "-1",
"priority": "3",
"options": {
"0": "none",
"1": "Warp Records",
"2": "Touch & Go",
"3": "Warner Bros.",
"4": "Sony Music",
"5": "S.Y.R.",
"6": "The Leaf Label"
},
"toggles": {
"1":["formats"],
"2":["formats"]
}
},
"formats": {
"type": "integer",
"id": "4",
"default": "0",
"priority": "4",
"options": {
"0": "None",
"1": "Compact Disk",
"2": "Record/Vinyl",
"3": "MP3/Download",
"4": "Cassette",
"5": "8-Track",
"6": "Wax Cylinder",
"7": "Laser Disc",
"8": "Mini-Disk"
}
},
In the example above the formats field will only be displayed when option 1 or 2 is selected.
Note: if you populate a form-based U.I. with existing data and the data makes use of fields that toggle the visibility of other fields this functionality should still work. If it doesn’t work then there is a bug, please leave a comment…
Stability
This is an early version of this little framework. The code has been implemented in my production code, however it still needs to be thoroughly tested. It would be great to hear back from the community on this. If you have suggestions or think certain parts could be improved, don’t hesitate to leave a comment or question.
Future
I am hoping to add a couple of new features to this code as needed. It would be nice if there were a few additional parameters on a per widget basis and I’d like to implement more of the component set. That said, I don’t see a need for this project to go into a massive coding effort. I would like any additional features to be completely optional so that it can still be used as simply as it currently is.
Collaborators
If you have something you would like to contribute to this project don’t hesitate to let me know. It would be great to work with some other coders to optimize and extend what is already written.




