How to use

Basic usage flow of Axon SDK

The Research Singleton

Most of the time you will interact with the MDRAxon singleton. The first thing to do is to initialize it. Make sure you have internet connection.

1. Axon Initialization

// Init Research
[Medable axon];

2. Downloading required assets in advance

The next thing to do, is to download in advance, all the assets any of the steps from any task of your study might need. If there is any step with image(s) in any of your Study's tasks --i.e. Instruction step configured with an image, Image capture step configured with a template image, Image choice step, etc; there's a manager to download all of them at once and it fires NSNotifications when it starts/ends/etc.

// Download Step images
[[[Medable axon] assetDownloader] start];
	
// Listen to these notifications to be informed about progress
extern NSString *kDidStartDownloadingStepImagesNotification;
extern NSString *kDidDownloadAFileNotification;
extern NSString *kDidReceiveAFaultNotification;
extern NSString *kDidEndDownloadingStepImagesNotification;

// Consult documentation in MDRStepImagesDownloader's header file.

Here is an example of how to handle these notifications:

// First, you have to listen
- (ret)someMethod
{
	NSNotificationCenter* nCenter = [NSNotificationCenter defaultCenter];
	
	[nCenter addObserver:self selector:@selector(handleAssetDownloaderNotification:) name:kDidStartDownloadingStepImagesNotification object:nil];
	[nCenter addObserver:self selector:@selector(handleAssetDownloaderNotification:) name:kDidDownloadAFileNotification object:nil];
	[nCenter addObserver:self selector:@selector(handleAssetDownloaderNotification:) name:kDidReceiveAFaultNotification object:nil];
	[nCenter addObserver:self selector:@selector(handleAssetDownloaderNotification:) name:kDidEndDownloadingStepImagesNotification object:nil];
}
// Then, handle
- (void)handleAssetDownloaderNotification:(NSNotification*)notif
{
    NSString* notifName = notif.name;
    
    if ([notifName isEqualToString:kDidStartDownloadingStepImagesNotification])
    {
        // Assets download started
    }
    else if ([notifName isEqualToString:kDidDownloadAFileNotification])
    {
        // Download finished for an asset
        NSString* fileName = notif.userInfo[kFileKey];
    }
    else if ([notifName isEqualToString:kDidReceiveAFaultNotification])
    {
        // Fault
        MDFault* fault = notif.object;
    }
    else if ([notifName isEqualToString:kDidEndDownloadingStepImagesNotification])
    {
        // Assets Download finished
        [self refreshTaskList];
    }
}

If you are interested in listening to all notifications, there is a convenient method to add an observer:

[MDRAssetDownloader addAllNotificationsObserver:self selector:@selector(handleAssetDownloaderNotification:)];
🚧

Required assets

  • Only after you received a kDidEndDownloadingStepImagesNotification, it is safe to proceed to the next steps.
  • This can be done before or after login as needed --i.e.: if you have public tasks that have steps with images.

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.

// Get the user's "current tasks"
[[[Medable axon] userTaskManager] fetchUserTasks];

// Listen to these notifications to be informed about progress
extern NSString *kDidStartGettingUserTasksNotification;
extern NSString *kDidEndGettingUserTasksNotification;
extern NSString *kDidReceiveAFaultGettingUserTasksNotification;

// Consult documentation in MDRUserTaskManager's header file.

Here is an example of how to handle these notifications:

// First, you have to listen
- (ret)someMethod
{
	NSNotificationCenter* nCenter = [NSNotificationCenter defaultCenter];
	
	[nCenter addObserver:self selector:@selector(handleTaskManagerNotification:) name:kDidStartGettingUserTasksNotification object:nil];
	[nCenter addObserver:self selector:@selector(handleTaskManagerNotification:) name:kDidEndGettingUserTasksNotification object:nil];
	[nCenter addObserver:self selector:@selector(handleTaskManagerNotification:) name:kDidReceiveAFaultGettingUserTasksNotification object:nil];
}
// Then, handle
- (void)handleTaskManagerNotification:(NSNotification*)notif
{
    NSString* notifName = notif.name;
    
    if ([notifName isEqualToString:kDidStartGettingUserTasksNotification])
    {
        // Started fetching - "Loading Study..."
    }
    else if ([notifName isEqualToString:kDidEndGettingUserTasksNotification])
    {
        // Get the tasks objects
        NSArray<MDRTask*>* tasks = notif.object;
        
        // Update UI with tasks - make sure to run this in the main thread...
        [[NSOperationQueue mainQueue] addOperationWithBlock:^
         {
             // The study instance is held by the MDRAxon singleton
             NSLog(@"Study: %@", [Medable axon].userStudy);
             
             // Currently available tasks
             NSLog(@"Tasks: %@", tasks);
         }];
    }
    else if ([notifName isEqualToString:kDidReceiveAFaultGettingUserTasksNotification])
    {
        // Get the fault and handle it
        MDFault* fault = (MDFault*)notif.object;
        NSLog(@"Fault: %@", fault);
    }
}

If you are interested in listening to all of the notifications, there is a convenient method to add an observer:

[MDRUserTaskManager addAllNotificationsObserver:self selector:@selector(handleTaskManagerNotification:)];

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 --read about public tasks in theEnrollment section. Success of this public tasks fetching depends on if the study is open or limited enrollment and if an invitation token has been provided as described in the Invitation token section.

4. Taking surveys: ResearchKit

Once we get the currently available tasks from the Tasks Manager, we need to convert those to ResearchKit objects and pass them to ResearchKit. A method like this one, in your view controller class, would do the job:

- (void)startTask:(MDRTask*)task
{
    if (!task) return;
    
    // Store your last task. You're going to need this later (for responses).
    self.lastTask = task;
    
    // Convert Medable Task object (MDRTask) to a ResearchKit task object (id<ORKTask>).
    id<ORKTask> rkTask = [task researchKitTask];
    
    ORKTaskViewController* taskViewController = [[ORKTaskViewController alloc] initWithTask:rkTask taskRunUUID:nil];
    
    taskViewController.title = task.name;
    
    taskViewController.delegate = self; // ORKTaskViewControllerDelegate
    
    taskViewController.showsProgressInNavigationBar = YES;  // only works for ordered tasks (tasks without branching).
    
    [self presentViewController:taskViewController animated:YES completion:nil];
}

Then, in the ORKTaskViewControllerDelegate method:

#pragma mark - ORKTaskViewControllerDelegate

- (void)taskViewController:(ORKTaskViewController *)taskViewController
       didFinishWithReason:(ORKTaskViewControllerFinishReason)reason
                     error:(nullable NSError *)error
{
    switch (reason)
    {
        case ORKTaskViewControllerFinishReasonCompleted:
        {
            ORKTaskResult* taskResult = taskViewController.result;
            
            // Last task
            MDRTask* lastTask = self.lastTask;
            
            // Send responses
            [[[Medable axon] responseUploader] sendResponseToTask:lastTask result:taskResult];
        }
            break;
        ...
    }
    
    [self dismissViewControllerAnimated:YES completion:nil];
}

5. Uploading survey responses: The Response Uploader

After a participant finishes a task, you can upload the responses to the cloud. You can do it right from the ORKTaskViewControllerDelegate's method, like this:

- (void)taskViewController:(ORKTaskViewController *)taskViewController
       didFinishWithReason:(ORKTaskViewControllerFinishReason)reason
                     error:(nullable NSError *)error
{
    switch (reason)
    {
        case ORKTaskViewControllerFinishReasonCompleted:
        {
            ORKTaskResult* taskResult = taskViewController.result;
            
            // Send responses
            MDRTask* task = a reference to the task object
            [[[Medable axon] responseUploader] sendResponseToTask:task result:taskResult];
		...
	}
	
	...
}

// Listen to these notifications to get noticed about the state and progress of the uploading of responses.
extern NSString *kCreatedTaskResponseNotification;
extern NSString *kStartedToSendResponseToTaskNotification;
extern NSString *kProgressToTaskResponseNotification;
extern NSString *kCompletedResponseToTaskNotification;

Here is an example of how to handle these notifications:

// First, you have to listen
- (ret)someMethod
{
	NSNotificationCenter* nCenter = [NSNotificationCenter defaultCenter];
	
	[nCenter addObserver:self selector:@selector(handleResponseUploaderNotification:) name:kCreatedTaskResponseNotification object:nil];
	[nCenter addObserver:self selector:@selector(handleResponseUploaderNotification:) name:kStartedToSendResponseToTaskNotification object:nil];
	[nCenter addObserver:self selector:@selector(handleResponseUploaderNotification:) name:kProgressToTaskResponseNotification object:nil];
	[nCenter addObserver:self selector:@selector(handleResponseUploaderNotification:) name:kCompletedResponseToTaskNotification object:nil];
}
// Then, handle
- (void) handleResponseUploaderNotification:(NSNotification*)notif
{
    NSString* notifName = notif.name;
    
    if ([notifName isEqualToString:kCreatedTaskResponseNotification])
    {
        // Created a task response object - these are related to step responses, and need to be created first.
        NSString* taskResponseId = notif.userInfo[kTaskResponse];
    }
    else if ([notifName isEqualToString:kStartedToSendResponseToTaskNotification])
    {
        // Started to send the data - responses for that with Id:
        NSString* taskId = notif.userInfo[kTask];
    }
    else if ([notifName isEqualToString:kProgressToTaskResponseNotification])
    {
        // Progress changed - sent response for taskId / step. NOT USED FOR NOW...
        NSString* taskId = notif.userInfo[kTask];
        MDRStep* step = notif.userInfo[kStep];
        MDFault* fault = notif.userInfo[kFaultKey];
        
        if (fault)
        {
        	// Handle fault
        }
    }
    else if ([notifName isEqualToString:kCompletedResponseToTaskNotification])
    {
        // Response upload completed
        NSString* taskId = notif.userInfo[kTask];
        MDFault* fault = notif.userInfo[kFaultKey];
        
        if (fault)
        {
        	// Handle fault
        }
    }
}

If you are interested in listening to all these notifications, there is a convenient method to add an observer:

[MDRResponseUploader addAllNotificationsObserver:self selector:@selector(handleResponseUploaderNotification:)];