ResearchStack Tutorial: Getting Started

Tom Blankenship

ResearchStack LogoIn April of 2016, Open mHealth announced the release of ResearchStack, an open-source SDK for building research study apps on Android.

This opens up exciting possibilities for researchers looking to roll out large-scale studies to Android users.

One of ResearchStack’s primary goals is to make it easy to port existing iOS apps using Apple’s ResearchKit. Since its release, ResearchKit apps have reached thousands of users to help study conditions ranging from melanoma to autism.

In this ResearchStack tutorial, you will duplicate the functionality in the excellent ResearchKit tutorial written by Matt Luedke. Along the way you will learn:

  • How to set up a new ResearchStack project from scratch.
  • How to translate key ResearchKit concepts to ResearchStack.
  • How to create consent, survey and active tasks.

If you are beginning Android Development, you’ll want to work through the Beginning Android Development Tutorial Series to get familiar with the basic concepts and development tools.

Getting Started

In this tutorial, you are going to take the very important research collected by Matt’s ResearchKit app and open it up to the world of Android users.

This research study attempts to answer the following question: “Does one’s name, quest, or favorite color affect vocal chord variations?”. Don’t worry, as with the iOS version, participants are not asked the airspeed velocity of an unladen swallow. :]

To begin, download the starter project and open with Android Studio. Take a minute to look through the project files.

Starter Project

To verify that everything compiles, build and run the app. You will see three buttons labeled Consent, Survey and Microphone.

Starter First Run

ResearchStack Modules

There are two primary modules for building ResearchStack apps:

  1. Backbone: The core ResearchStack API. This includes Tasks, Steps, Results, Consent, File/Database Storage and Encryption.
  2. Skin: Built on top of Backbone, this provides a way to build ResearchStack apps with minimal Android knowledge. This is mostly compatible with ResearchKit’s AppCore template engine and works with minor changes to AppCore resources.

This tutorial focuses on teaching the core Backbone components. The sample asthma app provides a good way to get more familiar with using Skin.

Application Setup

First, you need to include ResearchStack in the project.

Open the app build.gradle file and add the following to the dependencies section:

compile 'org.researchstack:backbone:1.1.1'

Open the project build.gradle file and add the following to the repositories section under jcenter():

maven { url "https://jitpack.io" }

Sync the gradle changes, then build the app. If your build succeeds, you’re ready to start using ResearchStack!

Custom Application class

Next, you need to initialize the Backbone StorageAccess component. Even though you will not be using the storage engine in this tutorial, Backbone will not run without this initialization.

Open CamelotApplication.java and add the following code at the bottom of OnCreate():

PinCodeConfig pinCodeConfig = new PinCodeConfig();

EncryptionProvider encryptionProvider = new UnencryptedProvider();

FileAccess fileAccess = new SimpleFileAccess();

AppDatabase database = new DatabaseHelper(this,
    DatabaseHelper.DEFAULT_NAME,
    null,
    DatabaseHelper.DEFAULT_VERSION);

StorageAccess.getInstance().init(pinCodeConfig, encryptionProvider, fileAccess, database);

Here you construct PinCodeConfig, UnencryptedProvider, SimpleFileAccess and DatabaseHelper objects and pass them into the StorageAccess singleton object. For more advanced apps, you may provide custom versions of any of these objects.

Note: Throughout this tutorial, you may see errors with resolving names after typing in or pasting blocks of code. If this happens, you can resolve the imports manually or turn on the option in Android Studio preferences to Insert imports on paste and Optimize imports on the fly under Editor\General\Auto Import.

You are now ready to create the first part of the research study!

Informed Consent

The first step in any research study is to get consent from the test subject. ResearchStack’s consent features are designed to let you easily present the study’s goals and requirements and get signed consent from the subject.

The consent section of your ResearchKit app breaks down into four main steps:

  1. Create a consent document.
  2. Create consent steps for the consent document.
  3. Create a consent task from the consent steps.
  4. Display the consent task.

Start by creating a ConsentDocument object. The consent document holds all the information necessary to inform the user and get their consent. This is analogous to ORKConsentDocument in ResearchKit.

Open MainActivity.java and add the following method:

private ConsentDocument createConsentDocument() {

  ConsentDocument document = new ConsentDocument();

  document.setTitle("Demo Consent");
  document.setSignaturePageTitle(R.string.rsb_consent);
  
  return document;
}

This creates a new ConsentDocument and assigns a main title and a signature page title. Note that R.string.rsb_consent comes from the ResearchStack backbone (rsb) library. You will find such references in other code snippets as well.

Consent Document Contents

You can now add ConsentSections to the consent document. Each ConsentSection will show as a new screen with a built-in graphic illustration. ResearchStack has a comprehensive list of section types defined by the ConsentSection.Type enum. This is comparable to the ORKConsentSectionType enum from ResearchKit.

You have several ContentSections to create, so start by adding the following helper method to MainActivity.java:

private ConsentSection createSection(ConsentSection.Type type, String summary, String content) {

  ConsentSection section = new ConsentSection(type);
  section.setSummary(summary);
  section.setHtmlContent(content);

  return section;
}

This method creates and returns a new ConsentSection based on the passed in type, summary and content parameters.

Add the following before return document; in createConsentDocument():

List<ConsentSection> sections = new ArrayList<>();

sections.add(createSection(ConsentSection.Type.Overview, "Overview Info", "<h1>Read " +
    "This!</h1><p>Some " +
    "really <strong>important</strong> information you should know about this step"));
sections.add(createSection(ConsentSection.Type.DataGathering, "Data Gathering Info", ""));
sections.add(createSection(ConsentSection.Type.Privacy, "Privacy Info", ""));
sections.add(createSection(ConsentSection.Type.DataUse, "Data Use Info", ""));
sections.add(createSection(ConsentSection.Type.TimeCommitment, "Time Commitment Info", ""));
sections.add(createSection(ConsentSection.Type.StudySurvey, "Study Survey Info", ""));
sections.add(createSection(ConsentSection.Type.StudyTasks, "Study Task Info", ""));
sections.add(createSection(ConsentSection.Type.Withdrawing, "Withdrawing Info", "Some detailed steps " +
    "to withdrawal from this study. <ul><li>Step 1</li><li>Step 2</li></ul>"));

document.setSections(sections);

Here you start by creating a new ArrayList named sections to hold the consent sections. Next, you call createSection() for each section and add it to the sections list. Finally, you add the sections to document.

In your own research app, you will likely choose a subset of section types. You will also provide detailed information for each section.

Collecting a Consent Signature

Next, you need to define the ConsentSignature object. This is comparable to ORKConsentSignature object in ResearchKit.

Add the following before return document; in createConsentDocument():

ConsentSignature signature = new ConsentSignature();
signature.setRequiresName(true);
signature.setRequiresSignatureImage(true);

document.addSignature(signature);

You create a new ConsentSignature object requiring a name and signature. You then add the signature to the document.

Adding the Review Content

The last piece needed in the document is the summary content. ResearchKit has a built-in ConsentReviewStep to help with this. In ResearchStack you define this view yourself.

Add the following before return document; in createConsentDocument():

document.setHtmlReviewContent("<div style=\"padding: 10px;\" class=\"header\">" +
        "<h1 style='text-align: center'>Review Consent!</h1></div>");

You add the review content to the document using full HTML syntax.

The Consent Task

Now that you have the consent document created, you will use it to construct a Task. ResearchStack uses the class OrderedTask to keep track of an ordered list of steps to show the user. This class coincides with ORKOrderedTask in ResearchKit.

You will add a VisualConsent object for each document section. This is different from ResearchKit, where a single ORKVisualConsentStep uses the ORKConsentDocument to automatically create the individual screens.

Add the following method to MainActivity.java.

private List<Step> createConsentSteps(ConsentDocument document) {

  List<Step> steps = new ArrayList<>();
  
  for (ConsentSection section: document.getSections()) {
    ConsentVisualStep visualStep = new ConsentVisualStep(section.getType().toString());
    visualStep.setSection(section);
    visualStep.setNextButtonString(getString(R.string.rsb_next));
    steps.add(visualStep);
  }
  
  return steps;
}

You create an array of steps and loop through all sections in ConsentDocument to create a ConsentVisualStep for each one. For each visualStep, you set the title, section and a next button label. Finally, you add visualStep to the list of steps.

Note: You can pass in any unique identifier when creating steps. In this case, using the string representation of the ConsentSection type is an easy way to supply the identifier.

Next is the Consent Review step. In ResearchKit, RKConsentReviewStep includes the review step with the signature form. In ResearchStack, the signature form is a separate step.

Add the following before return steps; in createConsentSteps():

ConsentDocumentStep documentStep = new ConsentDocumentStep("consent_doc");
documentStep.setConsentHTML(document.getHtmlReviewContent());
documentStep.setConfirmMessage(getString(R.string.rsb_consent_review_reason));

steps.add(documentStep);

Here, you create a new ConsentDocumentStep and set the HTML content from the ConsentDocument. You also set a confirmation message that displays in a dialog and add the step to the steps list.

Next, add a step to capture the user’s full name. ResearchKit includes this in ORKConsentReviewStep.

Add the following code before return steps; in createConsentSteps().

ConsentSignature signature = document.getSignature(0);

if (signature.requiresName()) {
  TextAnswerFormat format = new TextAnswerFormat();
  format.setIsMultipleLines(false);

  QuestionStep fullName = new QuestionStep("consent_name_step", "Please enter your full name",  
      format);
  fullName.setPlaceholder("Full name");
  fullName.setOptional(false);
  steps.add(fullName);
}

First, you retrieve the signature object from the consent document. If the signature name is required, you create a question step to ask for the user’s name and add the step to the steps list.

Answer formats define how a question step will be formatted. You can explore other answer formats here.

Note: If you want to collect multiple questions on a single screen, use FormStep with a series of QuestionSteps.

Next, add a step to collect a signature from the user. ResearchKit includes this in ORKConsentReviewStep. Again, paste this before return steps; in createConsentSteps().

if (signature.requiresSignatureImage()) {

  ConsentSignatureStep signatureStep = new ConsentSignatureStep("signature_step");
  signatureStep.setTitle(getString(R.string.rsb_consent_signature_title));
  signatureStep.setText(getString(R.string.rsb_consent_signature_instruction));
  signatureStep.setOptional(false);
  
  signatureStep.setStepLayoutClass(ConsentSignatureStepLayout.class);

  steps.add(signatureStep);
}

If the signature requires an image, you create a new signature step and assign the properties, set the step layout class and add the step to the steps list.

Congratulations — you’ve finished the hard part! On to the UI.

Presenting the Consent Task

You will now create and present an OrderedTask, using the steps you created in createConsentSteps().

The tutorial diverges from ResearchKit due to the different UI concepts between iOS and Android. ResearchKit uses a ORKTaskViewController, whereas ResearchStack uses a ViewTaskActivity.

Add the following constant to the top of MainActivity:

private static final int REQUEST_CONSENT = 0;

Add the following method to MainActivity:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
}

onActivityResult() is used to process the results of a task, using requestCode as a unique task identifier. Normally this is where you would save the results or forward them to a web service.

Add the following to displayConsent() in MainActivity:

// 1
ConsentDocument document = createConsentDocument();

// 2
List<Step> steps = createConsentSteps(document);

// 3
Task consentTask = new OrderedTask("consent_task", steps);

// 4
Intent intent = ViewTaskActivity.newIntent(this, consentTask);
startActivityForResult(intent, REQUEST_CONSENT);

Here’s what’s going on in the code above:

  1. You call createConsentDocument() to create the ConsentDocument.
  2. You pass document into createConsentSteps() to create the consent steps.
  3. You create a new OrderedTask, passing in a unique identifier along with the consent steps.
  4. You create an intent with the task and launch the task activity.

Like any other Android activity, the ViewTaskActivity must be added to the application manifest file. ResearchStack also uses a ViewWebDocumentActivity when displaying Learn More content.

Add the following to AndroidManifest.xml within the Application section:

<activity
    android:name="org.researchstack.backbone.ui.ViewTaskActivity"
    android:windowSoftInputMode="adjustResize"
    />
    
<activity
    android:name="org.researchstack.backbone.ui.ViewWebDocumentActivity"
    android:label="@string/app_name"
    />    

Now is the big moment — your Consent task is ready to go!

Build and run the app. Tap the CONSENT button and work your way through the consent screens and notice the custom graphics for each section:

Consent 1 Consent 2 Consent 3
Consent 4 Consent 5 Consent 6
Consent 7 Consent 8 Consent 9
Consent 10 Consent 11

Note: If you’ve been paying close attention, you may be asking “Why is ConsentDocument needed?” The answer is: it’s not! You could create the consent task and steps without a consent document. Using ConsentDocument separates the model from the presentation. A different consent workflow can be shown to the user by changing what is added to the consent document.

The Survey Module

You are now ready to move onto the heart of the study: the Survey.

Instruction Step

First, you’ll give the user some general instructions using an InstructionStep. This equals ORKInstructionStep in ResearchKit.

Add the following to displaySurvey() in MainActivity.java:

List<Step> steps = new ArrayList<>();

InstructionStep instructionStep = new InstructionStep("survey_instruction_step",
    "The Questions Three",
    "Who would cross the Bridge of Death must answer me these questions three, ere the other side they see.");
steps.add(instructionStep);

You create a list of steps for this task, then create a new instruction step and add it to the steps list.

Text Input Question

Next, you display the first question. This is covered by ORKQuestionStep in ResearchKit. You will format the question using TextAnswerFormat which equals ORKTextAnswerFormat in ResearchKit.

Add the following to the bottom of displaySurvey();

TextAnswerFormat format = new TextAnswerFormat(20);

QuestionStep nameStep = new QuestionStep("name", "What is your name?", format);
nameStep.setPlaceholder("Name");
nameStep.setOptional(false);
steps.add(nameStep);

You create a text answer format with a maximum length of 20. You create a question step with the answer format and add it to the steps list.

Text Choice Question

Next up is a question that lets the user choose from several predefined options. This also uses a QuestionStep, but with a ChoiceAnswerFormat which equals ORKTextChoiceAnswerFormat in ResearchKit.

Add the following to the bottom of displaySurvey:

AnswerFormat questionFormat = new ChoiceAnswerFormat(AnswerFormat.ChoiceAnswerStyle
    .SingleChoice,
    new Choice<>("Create a ResearchKit App", 0),
    new Choice<>("Seek the Holy Grail", 1),
    new Choice<>("Find a shrubbery", 2));
    
QuestionStep questionStep = new QuestionStep("quest_step", "What is your quest?", questionFormat);
questionStep.setPlaceholder("Quest");
questionStep.setOptional(false);
steps.add(questionStep);

You create a choice–answer format using a .SingleChoice style and pass in three choices. You then create a QuestionStep with the answer format and add it to the steps list.

Note: ChoiceAnswerFormat also allows multiple choice answers using the .MultipleChoice style.

Color Choice Question

The last question asks for the user’s favorite color. In ResearchKit, this is done with ORKImageChoiceAnswerFormat, but ResearchStack doesn’t have a built-in option for image choice format.

You could use ChoiceAnswerFormat and display the colors as simple text labels, but how boring would that be? Instead, you’ll create a custom answer format that displays colors.

ChoiceAnswerFormat has almost everything you need except for color radio buttons. You’ll need a custom StepBody and AnswerFormat to override the default behavior.

Create a new class file named ImageChoiceAnswerFormat.java and replace its contents with the following:

public class ImageChoiceAnswerFormat extends ChoiceAnswerFormat implements AnswerFormat.QuestionType
{
  public ImageChoiceAnswerFormat(ChoiceAnswerStyle answerStyle, Choice... choices) {
    super(answerStyle, choices);
  }

  @Override
  public QuestionType getQuestionType()
  {
    return this;
  }

  @Override
  public Class<?> getStepBodyClass() {
    return ImageChoiceQuestionBody.class;
  }
}

You inherit from ChoiceAnswerFormat and implement the QuestionType interface. You then override getQuestionType() and return the current object. Finally, you implement getStepBodyClass() and return the custom step body class.

Android Studio will complain that it cannot resolve ImageChoiceQuestionBody.class. You’ll fix that now.

ResearchStack provides a class named SingleChoiceQuestionBody that creates a radio group with buttons for the choices. You will extend this class and modify the radio group to provide custom radio buttons.

Create a new class file named ImageChoiceQuestionBody.java and replace the contents with the following:

public class ImageChoiceQuestionBody <T> extends SingleChoiceQuestionBody
{
  private Choice[] mChoices;

  public ImageChoiceQuestionBody(Step step, StepResult result) {
    super(step, result);

    QuestionStep questionStep = (QuestionStep)step;
    ImageChoiceAnswerFormat format = (ImageChoiceAnswerFormat)questionStep.getAnswerFormat();
    mChoices = format.getChoices();
  }

  @Override
  public View getBodyView(int viewType, LayoutInflater inflater, ViewGroup parent)
  {
    RadioGroup group = (RadioGroup)super.getBodyView(viewType, inflater, parent);

    for (int i=0; i<mChoices.length; i++) {

      RadioButton button = (RadioButton)group.getChildAt(i);
      button.setButtonDrawable(Integer.parseInt(mChoices[i].getValue().toString()));
    }

    return group;
  }
}

You inherit from SingleChoiceQuestionBody. The constructor calls the parent constructor and saves the answer choices. You then override getBodyView() and call the base class to create the view. You finish off by looping through the radio buttons and replacing the button drawables using the resource values from mChoices.

With that in place, you can create an ImageChoiceAnswerFormat using resource IDs as the values for your choices.

Add the following to the bottom of displaySurvey() in MainActivity.java:

AnswerFormat colorAnswerFormat = new ImageChoiceAnswerFormat(AnswerFormat.ChoiceAnswerStyle
    .SingleChoice,
    new Choice<>("Red", R.drawable.red_selector),
    new Choice<>("Orange", R.drawable.orange_selector),
    new Choice<>("Yellow", R.drawable.yellow_selector),
    new Choice<>("Green", R.drawable.green_selector),
    new Choice<>("Blue", R.drawable.blue_selector),
    new Choice<>("Purple", R.drawable.purple_selector));

QuestionStep colorStep = new QuestionStep("color_step", "What is your favorite color?",    
    colorAnswerFormat);
colorStep.setOptional(false);
steps.add(colorStep);

You create an ImageChoiceAnswerFormat using a SingleChoice style and pass in the color choices, then create a question step with the answer format and add it to the steps list.

Note: The color drawables are included in the starter project. These are standard XML selectors with images for checked and unchecked states.

Summary Step

The last step indicates that the survey is complete. ResearchKit uses ORKCompletionStep for this, but you will use ResearchStack's InstructionStep.

Add the following to the bottom of displaySurvey():

InstructionStep summaryStep = new InstructionStep("survey_summary_step",
    "Right. Off you go!",
    "That was easy!");
steps.add(summaryStep);

Here, you simply create an instruction step and add it to the steps list.

Presenting the Survey

All that's left is to the display the survey. Add the following constant to the top of MainActivity:

private static final int REQUEST_SURVEY  = 1;

Add the following to the bottom of displaySurvey():

OrderedTask task = new OrderedTask("survey_task", steps);

Intent intent = ViewTaskActivity.newIntent(this, task);
startActivityForResult(intent, REQUEST_SURVEY);

In the code above, you create a new OrderedTask, pass in the steps and then launch the activity. You set REQUEST_SURVEY as the request code.

Build and run the app; tap on the Survey button and work your way through the survey.

Survey 1 Survey 2 Survey 3
Survey 4 Survey 5

Active Tasks

Besides surveys, you may want to collect active data as well. ResearchStack doesn’t have any active tasks built-in, so you will create your own with a custom step.

Your custom step will use the device's microphone to record audio samples. ResearchKit uses a built-in AudioTask.

The following diagram illustrates the main components involved in the custom audio task. You will build out the AudioStep and AudioStepLayout parts of the diagram.

AudioStep Diagram

Instruction Step

Start by displaying some basic instructions. Add the following code to displayAudioTask() in MainActivity.java:

List<Step> steps = new ArrayList<>();

InstructionStep instructionStep = new InstructionStep("audio_instruction_step",
    "A sentence prompt will be given to you to read.",
    "These are the last dying words of Joseph of Aramathea.");
steps.add(instructionStep);

Here you add the familiar instruction step to the steps list.

Custom Audio Step

To create a fully custom Step, you need to create the following classes:

  • AudioStepLayout: An Android Layout class that implements the StepLayout interface.
  • AudioStep: A class that extends the base Step class.

Create a new layout resource file named audio_step_layout.xml in the res/layout folder and add the following code:

<?xml version="1.0" encoding="utf-8"?>
<merge
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

  <TextView
      android:id="@+id/title"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_below="@+id/image"
      android:layout_marginLeft="@dimen/rsb_margin_left"
      android:layout_marginRight="@dimen/rsb_margin_right"
      android:layout_marginTop="20dp"
      android:textColor="?attr/colorAccent"
      android:textSize="20sp"
      tools:text="@string/lorem_name"
      />

  <TextView
      android:id="@+id/summary"
      style="@style/TextAppearance.AppCompat.Subhead"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_below="@+id/title"
      android:layout_marginLeft="@dimen/rsb_margin_left"
      android:layout_marginRight="@dimen/rsb_margin_right"
      android:layout_marginTop="36dp"
      tools:text="@string/lorem_medium"
      />

  <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_below="@id/summary"
      android:layout_centerHorizontal="true"
      android:orientation="vertical"
      >

    <Button
        android:id="@+id/begin_recording"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="36dp"
        android:text="Begin"
        />

    <TextView
        android:id="@+id/countdown_title"
        style="@style/TextAppearance.AppCompat.Title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="36dp"
        android:text="Recording"
        android:visibility="gone"
        />

    <TextView
        android:id="@+id/countdown"
        style="@style/TextAppearance.AppCompat.Subhead"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="36dp"
        tools:text="@string/lorem_medium"
        />

  </LinearLayout>

  <org.researchstack.backbone.ui.views.SubmitBar
      android:id="@+id/submit_bar"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_alignParentBottom="true"/>

  <android.support.v7.widget.AppCompatTextView
      android:id="@+id/layout_consent_review_signature_clear"
      style="@style/Widget.AppCompat.Button.Borderless.Colored"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentBottom="true"
      android:layout_alignParentLeft="true"
      android:layout_alignParentStart="true"
      android:layout_alignTop="@+id/submit_bar"
      android:text="@string/rsb_consent_signature_clear"
      android:textColor="@color/rsb_submit_bar_negative"
      />

</merge>

Most of this is boilerplate code used by any custom step layout. The LinearLayout section contains your custom layout UI. This provides a button for the user to start the recording and some labels to show progress.

You’ll now create a Layout class to manage the layout.

Create a new file named AudioStepLayout.java and add the following:

public class AudioStepLayout extends RelativeLayout implements StepLayout
{
  public AudioStepLayout(Context context)
  {
    super(context);
  }

  public AudioStepLayout(Context context, AttributeSet attrs)
  {
    super(context, attrs);
  }

  public AudioStepLayout(Context context, AttributeSet attrs, int defStyleAttr)
  {
    super(context, attrs, defStyleAttr);
  }

  @Override
  public void initialize(Step step, StepResult result) {

  }

  @Override
  public View getLayout() {
    return null;
  }

  @Override
  public boolean isBackEventConsumed() {
    return false;
  }

  @Override
  public void setCallbacks(StepCallbacks callbacks) {

  }
}

Here you inherit from RelativeLayout and implement the StepLayout interface. You add standard layout constructors and overrides for the StepLayout interface. You'll fill in the details for the overrides soon.

Next, you will create the custom audio step. Create a new file named AudioStep.java and add the following code:

public class AudioStep extends Step
{
  private int mDuration;

  public AudioStep(String identifier)
  {
    super(identifier);
    setOptional(false);
    setStepLayoutClass(AudioStepLayout.class);
  }

  public int getDuration() {
    return mDuration;
  }

  public void setDuration(int duration) {
    mDuration = duration;
  }
}

Here you define a custom audio step with one property, mDuration, to control the length of the audio recording. In the constructor, you call the base class, set the step as required and define the default layout class as AudioStepLayout.class.

Now to finish AudioStepLayout. Add the following to the top of the AudioStepLayout class:

public static final String KEY_AUDIO = "AudioStep.Audio";

private StepCallbacks mStepCallbacks;
private AudioStep mStep;
private StepResult<String> mResult;
private boolean mIsRecordingComplete = false;
private String mFilename;

Add the following methods to AudioStepLayout.java:


// 1
private void setDataToResult()
{
  mResult.setResultForIdentifier(KEY_AUDIO, getBase64EncodedAudio());
}

// 2
private String getBase64EncodedAudio()
{
  if(mIsRecordingComplete)
  {
  
    // 3
    File file = new File(mFilename);

    try {
      byte[] bytes = FileUtils.readAll(file);

      String encoded = Base64.encodeToString(bytes, Base64.DEFAULT);
      return encoded;

    } catch (Exception e) {
      return null;
    }
  }
  else
  {
    return null;
  }
}

Taking it comment-by-comment;

  1. You define setDataToResult() to assign the audio data returned from getBase64EncodedAudio() to the step results. KEY_AUDIO is a unique identifier that distinguishes the audio data from any other results.
  2. You define getBase64EncodedAudio() to translate the raw audio to a Base64 string.
  3. If the recording is complete, you open the recording file, use ResearchStack's FileUtils.readAll() to read in the data from the file and then use Base64.encodeToString() to encode the data to a string and return it.

Now you will create a method to handle initialization. Add the following to AudioStepLayout.java:

private void initializeStep()
{
  LayoutInflater.from(getContext())
      .inflate(R.layout.audio_step_layout, this, true);

  TextView title = (TextView) findViewById(R.id.title);
  title.setText(mStep.getTitle());

  TextView text = (TextView) findViewById(R.id.summary);
  text.setText(mStep.getText());

  final TextView countdown = (TextView) findViewById(R.id.countdown);
  countdown.setText("Seconds remaining: " + Integer.toString(mStep.getDuration()));

  final TextView countdown_title = (TextView) findViewById(R.id.countdown_title);

  final Button beginButton = (Button) findViewById(R.id.begin_recording);

  // TODO: set onClick listener
}

Here you inflate the layout defined earlier and bind the UI elements to variables.

Now replace //TODO: set onClick listener with the following:

// 1
beginButton.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {

    // 2
    mFilename = getContext().getFilesDir().getAbsolutePath();
    mFilename += "/camelotaudiorecord.3gp";

    final AudioRecorder audioRecorder = new AudioRecorder();
    audioRecorder.startRecording(mFilename);

    // 3
    beginButton.setVisibility(GONE);
    countdown_title.setVisibility(View.VISIBLE);

    // 4
    CountDownTimer Count = new CountDownTimer(mStep.getDuration()*1000, 1000) {
      
      // 5
      public void onTick(long millisUntilFinished) {
        countdown.setText("Seconds remaining: " + millisUntilFinished / 1000);
      }

      // 6
      public void onFinish() {

        mIsRecordingComplete = true;

        audioRecorder.stopRecording();

        AudioStepLayout.this.setDataToResult();
        mStepCallbacks.onSaveStep(StepCallbacks.ACTION_NEXT, mStep, mResult);
      }
    };

    // 7
    Count.start();
  }
});

There’s quite a few things going on here, so to break it down:

  1. You create an onClick listener on the beginButton to start recording when tapped.
  2. You get a filename based on the app’s files directory and use the AudioRecorder object to start recording the file. AudioRecorder.java was included in the starter project and provides basic audio recording capabilities.
  3. You hide the Begin button and display the recording countdown timer label.
  4. You start a CountDownTimer based on the requested recording duration.
  5. The CountDownTimer calls onTick() periodically. You’ll use this opportunity to update the countdown timer with the remaining time in seconds.
  6. CountDownTimer calls onFinish() when it is complete. You set the mIsRecordingComplete flag, stop the recording and save the results. You call mStepCallbacks.onSaveStep() to automatically jump to the next step.
  7. Finally, you start the countdown timer.

Now that initializeStep() implements the core functionality for AudioStepLayout, it's time to wire everything up.

Add the following code to initialize():

this.mStep = (AudioStep)step;
this.mResult = result == null ? new StepResult<>(step) : result;

initializeStep();

initialize() is called when the step is about to be displayed. StepResult will contain the user's previous answer if they have already visited this step, otherwise it is null. You save the step along with the step result to member variables and then call the previously defined initializeStep().

Replace the contents of getLayout() with the following:

return this;

getLayout returns the view to be displayed. In this case, it is the current object.

Replace the contents of isBackEventConsumed() with the following:

setDataToResult();
mStepCallbacks.onSaveStep(StepCallbacks.ACTION_PREV, mStep, mResult);
return false;

isBackEventConsumed should return false unless you have special handling in mind when the user tries to back up. This is your chance to save the results. You call onSaveStep() to notify ResearchStack that it can save the results and perform the ACTION_PREV action.

Replace the contents of setCallbacks() method with the following:

this.mStepCallbacks = callbacks;

setCallbacks provides you with a callback object, and you save it for future use.

Great job! You have completed a custom step, and now you can finish the audio task.

Add the following to displayAudioTask() in MainActivity.java:

AudioStep audioStep = new AudioStep("audio_step");
audioStep.setTitle("Repeat the following phrase:");
audioStep.setText("The Holy Grail can be found in the Castle of Aaaaaaaaaaah");
audioStep.setDuration(10);
steps.add(audioStep);

You create the new audio step, set its title, text and duration and add it to the steps list.

Summary Step

The final step display displays a summary. Add the following to displayAudioTask():

InstructionStep summaryStep = new InstructionStep("audio_summary_step",
        "Right. Off you go!",
        "That was easy!");
steps.add(summaryStep);

You create an instruction step and add it to the steps list.

All that's left is to present the task!

Presenting the Audio Task

Add the following constant to the top of MainActivity:

private static final int REQUEST_AUDIO = 2;

Add the following to displayAudioTask():

OrderedTask task = new OrderedTask("audio_task", steps);

Intent intent = ViewTaskActivity.newIntent(this, task);
startActivityForResult(intent, REQUEST_AUDIO);

You create a new OrderedTask and pass in the steps and then launch the activity and set REQUEST_AUDIO as the request code.

Build and run the app. Tap the Microphone button and run through the task:

Audio 1 Audio 2 Audio 3

Where to Go From Here?

You can download the final project for this tutorial here.

While this tutorial covered three typical tasks you'll find in research studies — informed consent, surveys and active tasks – a real-world study will require some additional work. You may need to store or print results, send results to a server, schedule tasks to run periodically and deal with IRB approval.

For more information, visit the official project page for ResearchStack.

Take some time to look at the Skin module. It builds on top of Backbone and provides several advanced features, including:

  • Additional built-in tasks such as ConsentTask and SmartSurveyTask.
  • The ability to build out your studies using JSON files and use ResearchKit resources.
  • Task scheduling system with notifications.

The MoleMapper Open Source app available on GitHub provides a great example of a fully-featured research app created for both iOS and Android.

Thank you for reading this tutorial, and I hope you have enjoyed it. Please join the discussion below if you have any questions!

Team

Each tutorial at www.raywenderlich.com is created by a team of dedicated developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Tom Blankenship

Tom has been addicted to coding since he was a young teenager, writing his first programs on Atari computers in the early 80s. He currently works as an independent contractor focused on native iOS and Android app development.

In addition to working on side projects such as Animon Arena, a monster battle puzzle adventure game, he enjoys playing tennis, guitar, and drums, and spending time with his wife and two children.

Other Items of Interest

Save time.
Learn more with our video courses.

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 20 total!

Swift Team

... 15 total!

iOS Team

... 44 total!

Android Team

... 15 total!

macOS Team

... 11 total!

Unity Team

... 11 total!

Articles Team

... 15 total!

Resident Authors Team

... 17 total!

Podcast Team

... 8 total!

Recruitment Team

... 9 total!