External Apps

This page is a reference guide for users who edit the raw XML in ODK forms. If you use the XLSForm form designer, see http://xlsform.org for form design help.

ODK Collect can launch 3rd party apps to populate string, integer or numeric fields, and, beginning with ODK Collect 1.4.3, an external app can populate a group of fields. Also beginning with ODK Collect 1.4.3, any number of additional values, beyond the current value(s) of the field(s) being updated, can be passed to the 3rd party app. These changes were contributed by SurveyCTO.

The key points are:

  1. A text/decimal/integer field with an "ex:intentString" appearance can specify extra parameters that are passed to the external app, in addition to the "value" parameter that holds the current value for that field. The names of the parameters are user defined and there are no reserved names (except, perhaps "value"). Any number of extra parameters can be specified. The parameter values can be four different things:
    1. An xpath expression to an other field.
    2. A string literal defined in single quotes.
    3. A raw number (integer or decimal)
    4. Any JavaRosa function.
  2. A "field-list" group can also have an "intent" attribute.
    1. This intent attribute is ONLY used when the group has an "appearance" of "field-list".
    2. The format and the functionality of the "intent" value is the same as above.
    3. The external app is launched with the parameters that are defined in the intent string PLUS the values of all the sub-fields that are either TEXT, DECIMAL, or INTEGER.
    4. Any other sub-field is invisible to the external app.
    5. If the returned bundle of values contains values whose keys match the type and the name of the sub-fields, then these values overwrite the current values of those sub-fields.

A sample SurveyCTO form that can be used is the following (assuming that there is an external app that handles the intent "org.myapp.COLLECT"). Newlines were inserted in the 'intent' and 'appearance' attributes of the group and the last <input> field to make them readable. The first call passes extras with the existing values for 'textFieldInGroup', 'integerFieldInGroup' and 'decimalFieldInGroup' (under those names) plus additional extras under the 'uuid' and 'deviceid' names (with the specified values) to the external app (org.myapp.COLLECT). The second call passes 'started', 'constant' and 'value' (the existing value of 'textField') to the external app (org.myapp.COLLECT).

<?xml version="1.0"?>
<h:html xmlns="http://www.w3.org/2002/xforms"
  xmlns:ev="http://www.w3.org/2001/xml-events"
  xmlns:h="http://www.w3.org/1999/xhtml" 
  xmlns:jr="http://openrosa.org/javarosa"
  xmlns:orx="http://openrosa.org/xforms/" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <h:head>
    <h:title>External Intent Test</h:title>
    <model>
      <instance>
        <externaltest id="externaltest" version="2013032217">
          <starttime/>
          <endtime/>
          <deviceid/>
          <subscriberid/>
          <simid/>
          <devicephonenum/>
          <noteField/>
          <consented>
            <textFieldInGroup/>
            <integerFieldInGroup/>
            <decimalFieldInGroup/>
          </consented>
          <textField/>
          <integerField/>
          <decimalField/>
          <meta>
            <instanceID/>
          </meta>
        </externaltest>
      </instance>
      <bind jr:preload="timestamp" 
            jr:preloadParams="start" nodeset="/externaltest/starttime" type="dateTime"/>
      <bind jr:preload="timestamp" 
            jr:preloadParams="end" nodeset="/externaltest/endtime" type="dateTime"/>
      <bind jr:preload="property"
            jr:preloadParams="deviceid" nodeset="/externaltest/deviceid" type="string"/>
      <bind jr:preload="property" 
            jr:preloadParams="subscriberid" nodeset="/externaltest/subscriberid" type="string"/>
      <bind jr:preload="property" 
            jr:preloadParams="simserial" nodeset="/externaltest/simid" type="string"/>
      <bind jr:preload="property" 
            jr:preloadParams="phonenumber" nodeset="/externaltest/devicephonenum" type="string"/>
      <bind nodeset="/externaltest/noteField" 
            type="string" readonly="true()"/>
      <bind nodeset="/externaltest/consented"/>
      <bind nodeset="/externaltest/consented/textFieldInGroup" 
            required="true()" type="string"/>
      <bind nodeset="/externaltest/consented/integerFieldInGroup"
            required="true()" type="integer"/>
      <bind nodeset="/externaltest/consented/decimalFieldInGroup"
            required="true()" type="decimal"/>
      <bind nodeset="/externaltest/textField" type="string"/>
      <bind calculate="concat('uuid:', uuid())" 
            nodeset="/externaltest/meta/instanceID" readonly="true()" type="string"/>
    </model>
  </h:head>
  <h:body>
    <input ref="/externaltest/noteField" >
       <label>Welcome again!</label>
    </input>
    <group ref="/externaltest/consented" appearance="field-list" 
            intent="org.myapp.COLLECT(uuid=/externaltest/meta/instanceID, 
                                      deviceid=/externaltest/deviceid)">
      <label>Please populate these:</label>
      <input ref="/externaltest/consented/textFieldInGroup">
        <label>A text</label>
      </input>
      <input ref="/externaltest/consented/integerFieldInGroup">
        <label>An integer</label>
      </input>
      <input ref="/externaltest/consented/decimalFieldInGroup">
        <label>A decimal</label>
      </input>
    </group>
    <input appearance="ex:org.myapp.COLLECT(started= /externaltest/starttime ,
                                            constant='----', randomNumber=random())" 
             ref="/externaltest/textField" >
       <label>Click launch to see an external-fetched string</label>
    </input>
  </h:body>
</h:html>

The source code for example of an external application that collects and returns a single field value is provided, as-is, here: BreathCounter. That project includes the form definition (.xml) file that works with the application. You will need Java and Android development experience to build the example.