How to use

Basic usage flow of Axon SDK

Axon Initialization

Please read README instructions on AxonSDK repo in order to learn how to integrate axon to your project.

AxonConfig config = new AxonConfig.Builder().baseUrl("yourBaseUrl").clientKey("yourClientKey").orgName("yourOrg").studyId("yourStudyId").build();  
  
Axon.sharedInstance().initialize(getApplication(), config, new SuccessOrFaultCallback() {  
    @Override  
  	public void onSuccess() {  
        ...  
    }  
  
    @Override  
  	public void onFault(@NotNull final Fault fault) {  
        ...  
    }  
});
val config = AxonConfig.Builder().baseUrl("yourBaseUrl").clientKey("yourClientKey").orgName("yourOrgName").studyId("YourStudyId").build()

Axon.sharedInstance().initialize(getApplication(), config, object : SuccessOrFaultCallback {
            override fun onSuccess() {
							...
            }

            override fun onFault(fault: Fault) {
							...
            }
        })

To use Axon just call:

Axon.sharedInstance()

Some studies can be configured to require participants an invitation to join the study.

Axon.sharedInstance().requiresInvite(new ObjectOrFaultCallback<Boolean>() {
	@Override
	public void onObject(Boolean requiresInvite) {
		..
	}

	@Override
	public void onFault(Fault fault) {
		..
	}
});
Axon.sharedInstance().requiresInvite(object : ObjectOrFaultCallback<Boolean> {
	override fun onObject(requiresInvite: Boolean?) {
  
  }

  override fun onFault(fault: Fault) {

  }
})
❗️

It is mandatory to check if studies requires invites or not before trying to download the study information

If the study doesn't requires invite, the sdk will have now a reference to a public user, so now, any results for the tasks user completes will be tied to this public user. (Unless the user logs in, in which case the public user is descarted )

If the study does require invite, then you'll need to validate the user email invitation to get access to the study info (and also get the public user)

Axon.sharedInstance().verifyInvitationPin("yourEmail", "thePinCode", new SuccessOrFaultCallback() {
	@Override
		public void onSuccess() {
		//pin verified
  	}

	@Override
	public void onFault(Fault fault) {
	}
});
override fun validateEmail() {
        Axon.sharedInstance().verifyInvitationPin("yourEmail", "yourPinCode", object : SuccessOrFaultCallback {
          override fun onSuccess() {

          }

          override fun onFault(fault: Fault) {

          }
        })
  }

Working with Axon's Studies

Each app is tied to an specific study, and each study could have multiple tasks. Before getting the user tasks, you should download the user study information.

To get the app study you can use downloadStudyInformation:

Axon.sharedInstance().downloadStudyInformation(new ObjectOrFaultCallback<Study>() {
 	@Override
  public void onObject(Study object) { ... }

  @Override
  public void onFault(Fault fault) { ... }
});
Axon.sharedInstance().downloadStudyInformation(object : ObjectOrFaultCallback<Study> {
	override fun onObject(study: Study) { .. }

	override fun onFault(fault: Fault) { .. }
})

Downloading required assets in advance

Before starting the surveys, you need to download all the assets belonging to the study.
To do so you need to first subscribe to the AssetsDownloader events:

Axon.sharedInstance().getAssetDownloader().addObserverToAllNotifications(this, new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if(intent.getAction() == AssetDownloader.NotificationStartedDownloadingStepImages) {
                ...
            }
            else if(intent.getAction() == AssetDownloader.NotificationDidDownloadedFileNotification) {
                Map notifInfo = (Map) intent.getExtras().get(Constants.kObject);
                //File Downloaded:notifInfo.get(kFile));
                ...
            }
            else if(intent.getAction() == AssetDownloader.NotificationDidReceiveFault) {
                Map notifInfo = (Map) intent.getExtras().get(Constants.kObject);
                //File failed: notifInfo.get(kMessage));
                ...
            }
            else if(intent.getAction() == AssetDownloader.NotificationDidEndDownloadingStepsImages) {
                ...
            }
        }
    });
Axon.sharedInstance().assetDownloader.addObserverToAllNotifications(this, object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        if (intent.action === AssetDownloader.NotificationStartedDownloadingStepImages) {

        } else if (intent.action === AssetDownloader.NotificationDidDownloadedFileNotification) {
            val notifInfo = intent.extras!!.get(Constants.kObject) as Map<*, *>
            //File Downloaded:notifInfo.get(kFile));

        } else if (intent.action === AssetDownloader.NotificationDidReceiveFault) {
            val notifInfo = intent.extras!!.get(Constants.kObject) as Map<*, *>
            //File failed: notifInfo.get(kMessage));

        } else if (intent.action === AssetDownloader.NotificationDidEndDownloadingStepsImages) {

        }
    }
})

And then just start the download process:

Axon.sharedInstance().getAssetDownloader().start();
🚧

Required assets

  • Only after you received a kDidEndDownloadingStepImagesNotification, it is safe to proceed to the next steps.
  • This process is required at least once before login AND once after login, since the required assets might differ.

3. Getting surveys: The User Tasks Manager

Now we are ready to get the user's current available tasks. There is a task manager who checks the group the user is part of, the task scheduling/dependencies/start and end dates/past user responses/etc, and returns the currently available tasks.

Axon.sharedInstance().getTaskManager().addObserverToAllNotifications(this, new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (intent.getAction().equals(UserTaskManager.NotificationStartedGettingUserTasks)) {
                    //fetching started
                } else if (intent.getAction().equals(UserTaskManager.NotificationEndedGettingUserTasks)) {
                    List<Task> userTasks = (List<Task>) intent.getExtras().get(Constants.kObject);
                } else if (intent.getAction().equals(UserTaskManager.NotificationFailedGettingUserTasks)) {
                    //there has been some error fetching tasks
                    Map notifError = (Map) intent.getExtras().get(Constants.kObject);
                }
            }
        });
Axon.sharedInstance().taskManager.addObserverToAllNotifications(this, object : BroadcastReceiver() {
    fun onReceive(context: Context, intent: Intent) {
        if (intent.getAction().equals(UserTaskManager.NotificationStartedGettingUserTasks)) {
            //fetching started
        } else if (intent.getAction().equals(UserTaskManager.NotificationEndedGettingUserTasks)) {
            val userTasks = intent.getExtras().get(Constants.kObject) as List<Task>
        } else if (intent.getAction().equals(UserTaskManager.NotificationFailedGettingUserTasks)) {
            //there has been some error fetching tasks
            val notifError = intent.getExtras().get(Constants.kObject) as Map<*, *>
        }
    }
})

Once you're subscribed, you just need to fetch the user tasks:

Axon.sharedInstance().getTaskManager().fetchUserTasks();

Tasks Manager and anonymous/non-anonymous access

  • Something important to mention is that fetchUserTasks works differently depending on if there is a logged in user or not at the moment of calling it.
    • If fetchUserTasks is called when there is a logged in user, the task manager returns the current available tasks for that user.
    • Conversely, if fetchUserTasks is called when there is no logged in user, the task manager tries to fetch the public tasks.

4. Taking surveys

To start a task, you just need to get the configured intent from Axon and use it to trigger the task activity:

Intent taskIntent = Axon.sharedInstance().getTaskActivityIntent(yourContext, someMedableTask);
startActivityForResult(taskIntent, TASK_RESULT_CODE);
val taskIntent = Axon.sharedInstance().getTaskActivityIntent(yourContext, someMedableTask)
startActivityForResult(taskIntent, TASK_RESULT_CODE)

5. Uploading survey responses: The Response Uploader

After a participant finishes a task, you can upload the responses to the cloud.

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

    if (requestCode == TASK_RESULT_CODE) {

        if (resultCode == RESULT_OK) {
            TaskResult taskResult = (TaskResult) data.getSerializableExtra(ViewTaskActivity.EXTRA_TASK_RESULT);
            Axon.sharedInstance().getResponseUploader().sendResponse(mdTask, taskResult);
        } else {
            //the user came back from the survey without finishing it
        }

    }
}
val onActivityResult: Unit()val requestCode:Int val resultCode:Int
val Intent: Int data) run({
    super.onActivityResult(requestCode, resultCode, data)

    if (requestCode == TASK_RESULT_CODE) {

        if (resultCode == RESULT_OK) {
            val taskResult = data.getSerializableExtra(ViewTaskActivity.EXTRA_TASK_RESULT) as TaskResult
            Axon.sharedInstance().responseUploader.sendResponse(mdTask, taskResult)
        } else {
            //the user came back from the survey without finishing it
        }

    }
})

Axon allows you to subscribe to the response uploader in order to know the status of your uploads:

Axon.sharedInstance().getResponseUploader().addObserverToAllNotifications(this, new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        Map notifInfo = (Map) intent.getExtras().get(Constants.kObject);
        if (intent.getAction().equals(ResponseUploader.NotificationPreparingToSendResponseToTask)) {
            //Do something..
        } else if (intent.getAction().equals(ResponseUploader.NotificationStartedToSendResponseToTask)) {
            //Do something..
        } else if (intent.getAction().equals(ResponseUploader.NotificationCompletedSendResponseToTask)) {
            //Do something..
        } else if (intent.getAction().equals(ResponseUploader.NotificationFailedToSendResponseToTask)) {
            //Do something..
        } else if (intent.getAction().equals(ResponseUploader.NotificationProgressSendResponseToTask)) {
            //Do something..
        } else if(intent.getAction().equals(ResponseUploader.NotificationProgressSendResponseToTask)) //only called when there are attachments to upload.
        {
            //Do something..
        }
        else if(intent.getAction().equals(ResponseUploader.NotificationGeneratedConsentPDF)) //only called in consent tasks
        {
            //Do something..
        }
    }
});
Axon.sharedInstance().responseUploader.addObserverToAllNotifications(this, object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val notifInfo = intent.extras!!.get(Constants.kObject) as Map<*, *>
        if (intent.action == ResponseUploader.NotificationPreparingToSendResponseToTask) {
            //Do something..
        } else if (intent.action == ResponseUploader.NotificationStartedToSendResponseToTask) {
            //Do something..
        } else if (intent.action == ResponseUploader.NotificationCompletedSendResponseToTask) {
            //Do something..
        } else if (intent.action == ResponseUploader.NotificationFailedToSendResponseToTask) {
            //Do something..
        } else if (intent.action == ResponseUploader.NotificationProgressSendResponseToTask) {
            //Do something..
        } else if (intent.action == ResponseUploader.NotificationProgressSendResponseToTask)
        //only called when there are attachments to upload.
        {
            //Do something..
        } else if (intent.action == ResponseUploader.NotificationGeneratedConsentPDF)
        //only called in consent tasks
        {
            //Do something..
        }
    }
})

A HashMap object is sent inside the BroadcastReceiver's Intent object. Map notifInfo = (Map) intent.getExtras().get(Constants.kObject)

Note: You can also theme the taskIntent before sending it to startActivityForResult

Intent taskIntent = Axon.sharedInstance().getTaskActivityIntent(yourContext, someMedableTask);
   Axon.sharedInstance().themeTaskIntent(taskIntent, colorPrimary, colorPrimaryDark, colorAccent,
                principalTextColor, secondaryTextColor, actionFailedColor);
   startActivityForResult(taskIntent, TASK_RESULT_CODE);

You can use Color.parseColor("#rrggbb") to get the int rgb colors.

Consents

You can get the consent pdf path file when the task response is sent, but you can also get all the consents signed by an specific user on the current study (they will be downloaded and stored locally if they don't exists):

Axon.sharedInstance().getConsentDocumentsForCurrentStudy(new ObjectsListOrFaultCallback<Uri>() {
            @Override
            public void onResult(final List<Uri> list, boolean hasMore){ /*list of uris for each pdf*/ }

            @Override
            public void onFault(final Fault fault, boolean hasMore){ });

        });
Axon.sharedInstance().getConsentDocumentsForCurrentStudy(object : ObjectsListOrFaultCallback<Uri>() {
    fun onResult(list: List<Uri>, hasMore: Boolean) { /*list of uris for each pdf*/
    }

    fun onFault(fault: Fault, hasMore: Boolean) {}

})

Or you can get the signed consent for an specific taskId:

Axon.sharedInstance().getConsentDocumentForTask(task.getId(), new ObjectOrFaultCallback<Uri>()
        {
            @Override
            public void onObject(final Uri uriPdf)
            {
                ..
            }

            @Override
            public void onFault(Fault fault)
            {
                ..
            }
        });

ResearchStack

Medable axon depends on ResearchStack's Backbone (as it does in iOS with Researchkit) to diplay the surveys. Researchstack documentation can be found in:

You also may want to re-configure the activities to fit your own needs.

    ...

    <activity
        android:name="org.researchstack.backbone.ui.ViewTaskActivity"
        android:screenOrientation="portrait"
        android:windowSoftInputMode="adjustResize"/>

    ...