schema is a cross-platform mobile application for deploying mHealth monitoring and intervention studies.
It supports:
- A diverse range of elements, including slider, text input, date/time, audio, video, image, and more, with support for branching logic.
- Flexible module scheduling, to deliver surveys and/or interventions to participants at random or fixed intervals.
- Participant randomisation into distinct conditions with different modules and scheduling.
- Study registration via scanning a QR code or directly entering protocol URL.
- Dynamic feedback charts to track participant progress on specific variables.
- Distributed architecture, such that study protocols and data can be stored on your own server.
If you use schema in your own research, please cite the following:
Shatte, A. B. R., & Teague, S. J. (2020). schema: An open-source, distributed mobile platform for deploying mHealth research tools and interventions. BMC Medical Research Methodology, 20(1), 1-12. Retrieved from https://bmcmedresmethodol.biomedcentral.com/articles/10.1186/s12874-020-00973-5
Shatte, A. B. R., & Teague, S. J. (2019, June 12). schema (Version 1.0). Zenodo. http://doi.org/10.5281/zenodo.3243918
schema depends on the following frameworks:
- Ionic - Cross-platform mobile app development
- node.js - Cross-platform JavaScript run-time environment
- Chart.js - Open source HTML5 charts
schema uses the following Ionic Native plugins to achieve native functionality:
Plugin | Documentation |
---|---|
Barcode Scanner | https://ionicframework.com/docs/native/barcode-scanner |
LocalNotifications | https://ionicframework.com/docs/native/local-notifications |
SQLite | https://ionicframework.com/docs/native/sqlite |
FileTransfer | https://ionicframework.com/docs/native/file-transfer |
File | https://ionicframework.com/docs/native/file |
Install the dependencies and platforms
$ cd schema
$ ionic cordova prepare
The Ionic Docs contain detailed instructions on the next steps.
schema uses the ngx-translate library for translation. If you wish to contribute a localised version of the app's strings in another language, the file src/assets/i18n/en.json
should be used as a template.
To host your own study on the schema platform, the following steps are required:
- Create a study protocol and upload it to a web server (follow the instructions below)
- Create a page on a server to receive post requests and save data
A study protocol is defined in a JSON file. At the highest level, this file contains two attributes: the properties object which stores the metadata about the study; and the modules array which stores the individual survey/intervention tasks that will be delivered to the participants.
{
"properties": {
/* property attributes */
},
"modules": [
/* module objects */
]
}
We recommend using a GUI service like JSON Editor Online to build your study protocol.
The properties object must define the following attributes:
Property | Type | Description | Example |
---|---|---|---|
study_id |
String | An identifier for the study which is sent to the server with response data. | "study_id":"ABC123" |
study_name |
String | The name of the current study. | "study_name": "Sleep Study" |
instructions |
String | Brief description/instructions for the study that is displayed in the app. Basic HTML supported. | "instructions": "This study will track your sleep." |
banner_url |
String | The URL to an image that will be displayed on the home page of your study. It will be displayed at 100% width and maintain the aspect ratio of the original image. | "banner_url": "https://getschema.app/banner.png" |
support_email |
String | An email address that participants can contact for support with the study. | "support_email": "support@getschema.app" |
support_url |
String | A web link to the study's homepage or support information that is linked to in the app. | "support_url": "https://getschema.app/" |
ethics |
String | An ethics statement for the study. | "ethics": "This study was approved by ethics committee with approval number 0093423" |
pls |
String | A web URL to a PDF file containing the study's Plain Language Statement. | "pls": "https://getschema.app/pls.pdf" |
empty_msg |
String | A message displayed to the user when there are no tasks currently available to complete. | "empty_msg": "Relax, you're all up to date." |
post_url |
String | An endpoint to receive participant responses (POST data) from the app. | "post_url": "https://getschema.app/post.php" |
conditions |
Array | A list of conditions that participants can be randomised into. | "conditions": [ "Control", "Intervention" ] |
cache |
Boolean | Indicates whether media elements will be cached for offline mode during study enrollment. Note: media should be optimised to reduce download times. | "cache": true |
The modules array contains one or many module objects, which encapsulate the surveys and/or interventions that will be delivered to the participants of your study. A module object has this high-level structure:
{
"type": "...",
"name": "...",
"submit_txt": "...",
"condition": "...",
"alerts": {
/* alert properties */
},
"graph": {
/* graph properties */
},
"sections": [
/* section objects with questions */
]
}
The properties of a module object are defined as follows:
Property | Type | Description | Example |
---|---|---|---|
type |
String | The type of the module. Accepted values are survey , info , video , and audio . |
"type": "survey" |
name |
String | The name of the module. Basic HTML supported. | "name": "Daily Checklist" |
submit_txt |
String | The label of the submit button for this module. Note: this value appears only on the final section of a module. | "submit_txt": "Finish" |
condition |
String | The condition that this module belongs to. It must match one of the values from the conditions array from the study properties, or have the value * to be scheduled for all participants. |
"condition":"Control" |
alerts |
Object | Contains information about the scheduling of this module. Used to control access to the task and set notifications. | See alerts. |
graph |
Object | Contains information about the graph relating to this module (if any). Used to render the graph in the Feedback tab. | See graph. |
sections |
Array | An array of section objects that contain the questions/elements for this module. | See sections. |
uuid |
String | A unique identifier for this module. | "uuid": "5f8c6ec7-463d-4e51-9ea3-480115bd9f53" |
unlock_after |
Array | A list of UUIDs of modules that must be completed before this module will appear on the task list. | "unlock_after": [ "b79bc562-1dd2-4c3f-a1ed-6bb359cbfaaa" ] |
shuffle |
Boolean | Used for counterbalancing. If true , the order of the sections will be randomised every time the module is accessed. |
"shuffle": true |
The alerts object must define the following attributes:
Property | Type | Description | Example |
---|---|---|---|
title |
String | The title that is displayed in the notification (main text). | "title": "Time to check in! |
message |
String | The message that is displayed in the notification (secondary text). | "message": "Tap here to open the app." |
start_offset |
Integer | Indicates when the module should first be displayed to the user, where zero is the day that the participant enrolled. | "start_offset": 1 |
duration |
Integer | Indicates the number of consecutive days that the module should be scheduled to display. | "duration": 3 |
times |
Array | The times that this module should be scheduled for each day. hours indicates the hours (24-hour time) and minutes indicates the minutes (so should be between 0 and 59). |
"times": [ { "hours": 8, "minutes": 30 } ] |
random |
Boolean | Indicates whether the alert times should be randomised. If true, each value from times will be set using the value of random_interval . |
"random": true |
random_interval |
Integer | The number of minutes before and after that an alert time should be randomised. For example, if the alert is scheduled for 8.30am and the random_interval is 30, the alert will be scheduled randomly between 8 and 9am. |
"random_interval": 30 |
sticky |
boolean | Indicates whether the module should remain available in the Tasks list upon response, allowing the user to access this module repeatedly. | "sticky": true |
sticky_label |
String | A title that appears above a sticky module on the home screen. Multiple sticky modules that are set to appear in succession will be grouped under this title. | "sticky_label": "Warm up videos" |
timeout |
Boolean | If timeout is true, the task will disappear from the list after the number of minutes specified in timeout_after have elapsed (if the module is not completed before this time). |
"timeout": true |
timeout_after |
Integer | The number of minutes after a task is displayed that it will disappear from the list. timeout must be true for this to have any effect. |
"timeout_after": 30 |
The graphs object must define the following attributes:
Property | Type | Description | Example |
---|---|---|---|
display |
Boolean | Indicates whether this module displays a feedback graph in the Feedback tab. If the value is false , the remaining variables are ignored. |
"display": true |
variable |
String | The id of a question object to graph. It must match one of the module's question ids. |
"variable": "q4" |
title |
String | The title of the graph to be displayed in the Feedback tab. | "title": "Daily sleep" |
blurb |
String | A brief description of the graph to be displayed below it in the feedback tab. Basic HTML supported. | "blurb": "Your daily sleep in hours" |
type |
String | The type of graph. Currently bar and line are supported. |
"type": "line" |
max_points |
Integer | The maximum number of data points to display in the graph, e.g. 10 will only show the ten most recent responses. |
"max_points": 10 |
The sections array contains one or many section objects, which have this high level structure:
{
"name": "Demographics",
"questions": [
/* question objects */
]
}
The properties are defined as follows:
Property | Type | Description | Example |
---|---|---|---|
name |
String | The title of this section, which is displayed at the top of the screen. | "name": "Demographics" |
questions |
Array | An array containing all of the questions for this section of the module. | See questions. |
shuffle |
Boolean | Used for counterbalancing. If true , the order of the questions in this section will be randomised. |
"shuffle": true |
There are several types of question object that can be added to a section, including:
- Instruction
- Text Input
- Date/Time
- Yes/No (boolean)
- Slider
- Multiple Choice
- Media
All question objects must include the following properties:
Property | Type | Description | Example |
---|---|---|---|
id |
String | A unique id to identify this question. This id is sent to the server along with any response value. Note: Every element in the entire study protocol must have a unique id for some features to function correctly. |
"id": "q1" |
type |
String | The primary type of this question. Accepted values are instruction , datetime , multi , text , slider , video , audio , and yesno . |
"type": "slider" |
text |
String | The label displayed alongside the question. Basic HTML supported. | "text": "How do you feel?" |
required |
Boolean | Denotes whether this question is required to be answered. The app will force the participant to answer all required questions that are not hidden by branching. | "required": true |
Many question types have additional properties that they must include, which are outlined in the following sections.
Text Input questions must have the following additional property:
Property | Type | Description | Example |
---|---|---|---|
subtype |
String | The specific type of text input for this field. Accepted values are short , long , and numeric . |
"subtype": "long" |
Date/Time questions must have the following additional property:
Property | Type | Description | Example |
---|---|---|---|
subtype |
String | The specific type of date/time input for this field. Accepted values are date (datepicker only), time (timepicker only), and datetime (both). |
"subtype": "time" |
Yes/No questions must have the following additional properties:
Property | Type | Description | Example |
---|---|---|---|
yes_text |
String | The label for a true/yes response. | "yes_text": "Agree" |
no_text |
String | The label for a false/no response. | "no_text": "Disagree" |
Slider questions must have the following additional properties:
Property | Type | Description | Example |
---|---|---|---|
min |
Integer | The minimum value for the slider range. | "min": 0 |
max |
Integer | The maximum value for the slider range. | "max": 100 |
hint_left |
String | A label displayed to the left of the slider. | "hint_left": "less" |
hint_right |
String | A label displayed to the right of the slider. | "hint_right": "more" |
Multiple choice questions must have the following additional properties:
Property | Type | Description | Example |
---|---|---|---|
radio |
Boolean | Denotes whether the multiple choice should be radio buttons (one selection only) or checkboxes (multiple selections allowed). | "radio": true |
modal |
Boolean | Denotes whether the selections should appear in a modal popup (good for longer lists) | "modal": false |
options |
Array | The list of choices to display. | "options": [ "Dog", "Cat", "Fish" ] |
shuffle |
Boolean | If true , the order of the choices will be randomly shuffled. |
"shuffle": true |
Media questions must have the following additional properties:
Property | Type | Description | Example |
---|---|---|---|
subtype |
String | The type of media. Accepted values are video , audio , and image . |
"subtype": "video" |
src |
String | A direct URL to the media source. | "src": "https://getschema.app/video.mp4" |
thumb |
String | Required for video elements. A direct URL to the placeholder image that is displayed in the video player while loading. |
"thumb": "https://getschema.app/thumbnail.png" |
To use branching, you need to add two additional properties to the question object that is to be dynamically shown/hidden.
Property | Type | Description | Example |
---|---|---|---|
hide_id |
String | The id of the question that will trigger this question to dynamically show/hide. |
"hide_id": "q5" |
hide_value |
String/Boolean | The value that needs to be selected in the question denoted by hide_id which will make this question appear. When using sliders, the value should be prefixed with a direction and is inclusive, e.g. >50 or <50 . |
"hide_value": "10" |
hide_if |
Boolean | Indicates the branching behaviour. If true , the element will disappear if the value of the question equals hide_value . If false, the element will appear appear instead. |
"hide_if": false |
Currently, branching is supported by the multi
, yesno
, and slider
question types.
Elements can also be grouped for randomisation, such that every time a module is accessed only one of the random elements will be displayed. An example use case would be to display a random image from a set of images. To achieve this, add the following property to each group of elements:
Property | Type | Description | Example |
---|---|---|---|
rand_group |
String | An identifier that groups a set of elements together so that only one will randomly appear every time a module is accessed. Note: To identify which element was visible, it will be given a response value of 1 . If the element can record a response this value will be replaced with that response. All hidden elements will record no response. |
"rand_group": "sad_images" |
The post_url
defined in the study protocol's properties object should point to an endpoint that can receive POST requests. The endpoint should return the boolean value true
if data has been successfully saved - schema will continue submitting each data point to the server until it receives this acknowledgement.
schema posts the following variables to the server whenever a task is completed:
POST id | Type | Description |
---|---|---|
data_type |
String | Describes whether log or survey_response data is being submitted. |
study_id |
String | The identifier of the study taken from the study_id property of the study protocol. |
user_id |
String | The unique id of the user. |
module_index |
Integer | The index of the module in the modules array (zero-based). |
platform |
String | The platform the user responded on. Value will be iphone , ipad or android . |
For survey_response
data, these additional variables are included:
POST id | Type | Description |
---|---|---|
module_name |
String | The name of the module. |
responses |
String | The questions responses for this task, provided as a stringified JSON object. The key is the id of the question, for example { "q1": 56 , "q2": "No", "q3": "" } . |
response_time |
Timestamp | The timestamp when the module was completed, in the user's local time, e.g. 2019-05-08T23:16:21+10:00 . |
alert_time |
Timestamp | The timestamp when the module was first scheduled to appear, e.g. 2019-05-08T23:00:21+10:00 . |
For log
data, these additional variables are included:
POST id | Type | Description |
---|---|---|
page |
String | The page the user visited in the app. Value can be home , my-progress , settings , or survey . If survey , the module_index variable will differentiate which module was accessed. |
timestamp |
Timestamp | The timestamp when the user visited the page, e.g. 2019-10-29T16:08:58+11:00 . |
Participants can sign up to your study by scanning a QR code or entering a URL. Upload your JSON study protocol to a web server and distribute the link. We recommend using a service like QRCode Monkey to generate a QR code that points to your study protocol link. The URL can be shortened for distribution using Bitly.
The variability in devices that support mHealth apps and the diversity of possible research designs within schema may result in unintended bugs. Therefore it is important that you conduct thorough testing of any program you deploy to schema before sharing it with participants.
Please post any bugs, issues or suggested features in the Issues tab.
schema uses the ngx-translate library for translation. If you wish to contribute a localised version of the app's strings in another language, the file src/assets/i18n/en.json
should be used as a template. Follow the documentation from ngx-translate to determine the correct name for your language file.