Terence Bennett - October 16, 2013

This case study provides an overview of how Circa5 uses DreamFactory as a backend for Heisenbug, a collaborative bug tracking system designed for mobile devices. The case study describes how the iOS version of the application interacts with the DreamFactory open source backend services platform.

We refer to each instance of the DreamFactory platform as a “DSP” which is short for DreamFactory Services Platform. As you’ll see, each DSP provides a database and a full-featured REST API to simplify the process of building your mobile application.

App Overview

Heisenbug is a simple issue tracking system that allows users to log and track project issues from their phone or tablet. The app is designed to motivate and encourage resolution using a gamification points system.

The primary use case for Heisenbug is creating and tracking “projects.” A project is either a bug to fix or a feature to complete. The details of a project are configurable to suit the working methods of each Heisenbug user. For example, a project can have an executive overview as well as more detailed information appropriate to an individual tasked with finishing the project.

Heisenbug motivates users to complete projects with a points system, a leveling game mechanic, and fun encouragement messages. For example, a user can close a bug to earn points and receive positive affirmation after reaching each level. The app provides a series of default phrases that can easily be customized.

The application is designed for collaboration. Groups of people can easily collaborate to finish a particular project. An access management screen allows an administrator to configure user permissions and sharing assignments for organizations, groups, or single-user accounts working on projects.

Other collaboration features include a Share feature that displays the ongoing discussion that a user is part of and as well as simple workflow notifications with SMS and email. These features help users stay in the loop when project information changes, enabling better collaboration across a project team.

Heisenbug iPad affirmation resized 600

Administering the DreamFactory Backend

Before building the frontend application, you need to configure your backend settings using the Dreamfactory admin console.

The first step of setting up the your backend instance is to create the application. On the left side menu choose Applications, enter your app name, check the box for native app, and click the “Create Application” button.

The next step is to create a Role for users of the application. Go to the Roles tab and enter a role name, for example “End User.” You need to associate the role with your application and also select which services are available for this role. Heisenbug end users all have the same service permissions, so the configuration is simple: just select “All” from the dropdown list and click “Add Service Access.” Don’t forget to check the “Active” checkbox before saving.

describe the image

Setting Up Database Tables

DreamFactory comes with a SQL database and a simple admin console for defining tables and fields. You can also import schema definitions in JSON format.

You set up your application’s schema from the “Manage Schema” tab. The Heisenbug schema consists of Records, Components, Messages, Priorities, Severities and Statuses. The tables are connected using SQL relationships. For example, the Records table references the Priorities table using a foreign key relationship. You can explore the complete schema using the Live API /schema.

Note: You can also easily connect RESTfully to your own SQL or NoSQL database by simply adding your database as a service in the DreamFactory admin console. This blog post provides an example of how to connect to your own database.

describe the image

Using the Live API Documentation

In the DSP admin console, you can click on the “API Documentation” tab to view every available API call. The API is documented with a tool called Swagger, which lets you quickly learn about each API call by viewing the request URL, response URL, and data returned in JSON format.

This is a powerful capability because it enables you to set up your backend, create some sample data, and make API calls before you even start implementing your application. For example, Heisenbug makes an API call to log users into the application to start a session. With Swagger, we could quickly see how the POST/user/session API call works before we implemented the call in our app.

Circa5 used an open source library called AFNetworking to make API calls from the client app. AFNetworking is very popular among iOS developers and is hosted on GitHub here.

describe the image

API Calls for User Authentication and Sessions

User management is a common requirement for many applications. DreamFactory’s REST API for user management made it very easy to manage user authentication, user roles, and session security.

The Heisenbug app uses DreamFactory’s REST API for user authentication and session management.

When a new user registers, a POST is made to /user/register to create a new user in the database.

 -(void)
 registerWithEmail:(NSString*)email 
 firstName:(NSString*)firstName 
 lastName:(NSString*)lastName 
 displayName:(NSString*)displayName{
 
 static NSString *const baseURLString = 
 @"https://dsp-creativeappsagencydemo.cloud.dreamfactory.com";
 
 NSString *userSessionURLString = 
 [baseURLString stringByAppendingString:@"/rest/user/register"];
  
 NSDictionary *parametersDictionary = 
 [NSDictionary dictionaryWithObjectsAndKeys:email,@"email",firstName,@"first_name",lastName,@"last_name",displayName,@"display_name", nil];
 
 AFHTTPClient *httpClient = 
 [[AFHTTPClient alloc] 
 initWithBaseURL:[NSURL URLWithString:baseURLString]];
 [httpClient setDefaultHeader:@"X-DreamFactory-Application-Name" value:@"Heisenbug"];
 [httpClient setParameterEncoding:AFJSONParameterEncoding];
 
 NSMutableURLRequest *request = 
 [httpClient requestWithMethod:@"POST"
 path:userSessionURLString
 parameters:parametersDictionary];
 
 AFJSONRequestOperation *operation =
 [AFJSONRequestOperation 
 JSONRequestOperationWithRequest:request
 success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {NSDictionary *jsonDic = (NSDictionary *)JSON;
 
 //registration completed successfully
 }
 
 failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {NSLog(@"%@",error);
 
 //registration error                                                
                                                       
 }];
 
 [operation start];
   
 }

Each time a user logs into Heisenbug, a POST is made to /user/session to create a new session.

 -(void)
 loginWithEmail:(NSString *)email 
 password:(NSString *)password{
     
 static NSString *const baseURLString = 
 @"https://dsp-creativeappsagencydemo.cloud.dreamfactory.com";
 
 NSString *userSessionURLString = 
 [baseURLString stringByAppendingString:@"/rest/user/session"];
     
 NSDictionary *parametersDictionary = 
 [NSDictionary dictionaryWithObjectsAndKeys:email,@"email",password,@"password", nil];
     
 AFHTTPClient *httpClient = 
 [[AFHTTPClient alloc] 
 initWithBaseURL:[NSURL URLWithString:baseURLString]];
 [httpClient setDefaultHeader:@"X-DreamFactory-Application-Name" value:@"Heisenbug"];
 [httpClient setParameterEncoding:AFJSONParameterEncoding];
 
 NSMutableURLRequest *request = 
 [httpClient 
 requestWithMethod:@"POST"
 path:userSessionURLString
 parameters:parametersDictionary];
 
 AFJSONRequestOperation *operation =
 [AFJSONRequestOperation 
 JSONRequestOperationWithRequest:request
 success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {NSDictionary *jsonDic = (NSDictionary *)JSON;
 
 UserData *userData = [UserData sharedInstance];
 userData.sessionId = [jsonDic objectForKey:@"session_id"];
 userData.userId = [(NSNumber *)[jsonDic valueForKey:@"id"] intValue];
 userData.displayName = [jsonDic objectForKey:@"display_name"];
 userData.email = [jsonDic objectForKey:@"email"];
 
 User *user = [[User alloc] init];
 user.email = userData.email;
 user.userID = userData.userId;
 user.sessionId = userData.sessionId;
 user.displayName = userData.email;
 user.password = password;
                                                    
 [Utility saveUser:user];
                                                         
 [self.delegate loginResult:YES];
                                                        
 }
 
 failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {NSLog(@"%@",error);
 
 [self.delegate loginResult:NO];
                                            
 }];
 
 [operation start];
 
 }

API Calls for Database CRUD Operations

Creating, reading, updating, and deleting records is a universal requirement for any data-driven mobile application that stores information in a database. DreamFactory comes with a SQL database already installed and a straightforward REST API for CRUD operations, making it simple to operate on single records or multiple records at once.

Heisenbug uses a “bug” object to store information about bugs entered by users. In some cases, the application needs to perform CRUD operations on multiple records at once, for example fetch all the bugs assigned to a specific user.

In the case of multiple records, the application calls /db/{table_name} with either a POST (create), GET (retrieve), PUT (update), or DELETE (delete) API call. The DSP returns standard JSON in the API response.

For example, the following code performs a GET request to get a list of all bugs.

 -(void)requestBugs{
     
 static NSString *const DBBaseURLString = 
 @"https://dsp-creativeappsagencydemo.cloud.dreamfactory.com/rest/db/Bugs";
 
 AFHTTPClient *httpClient =
 [[AFHTTPClient alloc] 
 initWithBaseURL:[NSURL URLWithString:DBBaseURLString]];
 [httpClient setDefaultHeader:@"X-DreamFactory-Application-Name" value:@"Heisenbug"];
 [httpClient setDefaultHeader:@"X-DreamFactory-Session-Token" value:[UserData sharedInstance].sessionId];
 [httpClient setParameterEncoding:AFJSONParameterEncoding];
 
 NSMutableURLRequest *request = 
 [httpClient requestWithMethod:@"GET"
 path:DBBaseURLString
 parameters:nil];
 
 AFJSONRequestOperation *operation =
 [AFJSONRequestOperation JSONRequestOperationWithRequest:request
 success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
  NSDictionary *jsonDic = (NSDictionary *)JSON;
  NSArray *records  = [jsonDic objectForKey:@"record"];
  NSMutableArray *bugs = [[NSMutableArray alloc] init];
                                                         
  [records enumerateObjectsUsingBlock:^(id obj,NSUInteger idx, BOOL *stop){
  
   Bug *bug = [[Bug alloc] init];
   bug.bugID  = [(NSNumber *)[obj valueForKey:@"id"] intValue];
   bug.title = [obj valueForKey:@"title"];
   bug.details = [obj valueForKey:@"details"];
   bug.points = [(NSNumber *)[obj valueForKey:@"points"] intValue];
   bug.rank =[(NSNumber *)[obj valueForKey:@"rank"] intValue];
   int type = [(NSNumber *)[obj valueForKey:@"type"] intValue];
   bug.type = (type == 1)? bugTypeBug : bugTypeFeature;
   bug.speedToComplete = [(NSNumber *)[obj valueForKey:@"speedToComplete"] boolValue];
   bug.milestone = [(NSNumber *)[obj valueForKey:@"milestone"] boolValue];                                                            
   bug.archived = [(NSNumber *)[obj valueForKey:@"archived"] boolValue];
   bug.severity = [(NSNumber *)[obj valueForKey:@"severity"] intValue];
   bug.priority = [(NSNumber *)[obj valueForKey:@"priority"] intValue];
   bug.status = [(NSNumber *)[obj valueForKey:@"status"] intValue];
   bug.component = [(NSNumber *)[obj valueForKey:@"component"] intValue];
   bug.assignedUser = [(NSNumber *)[obj valueForKey:@"assignedUser"] intValue];;
   bug.issueType = [(NSNumber *)[obj valueForKey:@"issueType"] intValue];
   bug.notes = [obj valueForKey:@"notes"];
 
  }];
 
  self.bugs = bugs;
  [self.delegate bugsUpdateResult:YES dataReturned:bugs error:nil];
 }
 
 failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
  NSLog(@"%@",error);
  [self.delegate bugsUpdateResult:NO dataReturned:nil error:error];
                                                     
  }];
 
 [operation start];
 
 }

In other cases, the application simply needs to perform a CRUD operation on a single record, for example delete a specific bug.

In the case of single records, the application simply calls /db/{table_name}/{id} with the relevant HTTP verb for that record: either GET, POST, PUT, or DELETE. The DSP returns standard JSON in the API response.

 -(void)
 deleteBug:(Bug*)bug {
 
 NSString *DBBaseURLString = 
 [NSString stringWithFormat:@"https://dsp-creativeappsagencydemo.cloud.dreamfactory.com/rest/db/Bugs?ids=%d",bug.bugID];
  
 AFHTTPClient *httpClient = 
 [[AFHTTPClient alloc] 
 initWithBaseURL:[NSURL URLWithString:DBBaseURLString]];
 [httpClient setDefaultHeader:@"X-DreamFactory-Application-Name" value:@"Heisenbug"];
 [httpClient setDefaultHeader:@"X-DreamFactory-Session-Token" value:[UserData sharedInstance].sessionId];
 [httpClient setParameterEncoding:AFJSONParameterEncoding];
 
 NSMutableURLRequest *request = 
 [httpClient requestWithMethod:@"DELETE"
 path:DBBaseURLString
 parameters:nil];
 
 AFJSONRequestOperation *operation =
 [AFJSONRequestOperation JSONRequestOperationWithRequest:request
 success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
 
 [self.delegate bugDeleted:YES];
                                         
 }
 
 failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
 NSLog(@"%@",error);
                                           
 [self.delegate bugDeleted:NO];
                                               
 }];
 
 [operation start];
   
 }

Learn More

If you’re building a native iOS app, think about whether using a backend services platform with REST might work well for you. DreamFactory’s REST API made it very easy for Circa5 to develop Heisenbug without writing any server-side code or having to create their own API.

We’ve just covered a small part of what’s available in the DreamFactory backend. Other services are available for NoSQL data, binary documents, user permissions, and external services (like remote databases, file storage, and web services). For more information or to try it out for free, check out the following links. Thanks for reading and good luck with your apps!