IOS App via AWS
Contents
The Line of Least Resistance
Overview
In order to make an iOS app for our project, we utilized Amazon Web Services. This tutorial will explain how to call functions from a project created on Xcode, Apple's app development software.
An important thing to note is that AWS updated their protocol for calling functions, and saving them in Cloud Logic, to a much more sophisticated version in late 2016. We were able to use the original protocol because we had created the project before the switch. Since we didn't actually use the new system, additional research will have to be done for creating and using APIs, but there are a lot of YouTube and AWS tutorials for the old system so we expect there will be more help for the new system soon.
A great guide to learn the basics of Swift and simple tools for creating an app on Xcode can be found here on Apple's website, and YouTube has some very helpful tutorials for more specifics.
Setting Up Your Project
Go to the AWS home page, click "Sign into your console" and enter your Amazon.com credentials (if you don't have an Amazon account, you'll have to create one).
Under "Mobile Services," click Mobile Hub, click "Create a new mobile project," enter a project name, and create your project.
Next, decide which features you want in your app. We utilized NoSQL Database and Cloud Logic, but you can also do User Sign In, Push Notifications, etc. For NoSQL Database, click the plus button and see the section below on setting up a server.
For Cloud Logic, click the plus button and create a new API. Note: this is where they made the changes to the system. Before they had "legacy" Lambda Functions that you could simply write code to call, but they have employed a new system with "paths" that allow you to do more. Although this will need adjustment in order to fit the new system, this is the code we used to call our Lambda Function:
@IBAction func handleSubmit(sender: AnyObject) {
let functionName = "testfunction2"
let early = earlyPickerData[earlypicker.selectedRowInComponent(0)]
let late = latePickerData[latepicker.selectedRowInComponent(0)]
let size = (sizeTextField.unwrappedText)
let name = (nameTextField.unwrappedText)
let inputText =
"{\n \"r\":\"Killer Burger\",\n \"early\":\""+early+"\",\n \"late\":\""+late+"\",\n \"size\":"+size+",\n \"name\":\""+name+"\"\n}"
print("Function Name: \(functionName)")
let jsonInput = inputText.makeJsonable()
let jsonData = jsonInput.dataUsingEncoding(NSUTF8StringEncoding)!
var parameters: [String: AnyObject]
do {
let anyObj = try NSJSONSerialization.JSONObjectWithData
(jsonData, options: []) as! [String: AnyObject]
parameters = anyObj
} catch let error as NSError {
resultTextView.text = "JSON request is not well-formed."
print("json error: \(error.localizedDescription)")
return
}
print("Json Input: \(jsonInput)")
AWSCloudLogic.defaultCloudLogic().invokeFunction(functionName,
withParameters: parameters, completionBlock: {(result: AnyObject?, error: NSError?) -> Void in
if let result = result {
dispatch_async(dispatch_get_main_queue(), {
print("KillerBurgerViewController: Result: \(result)")
self.resultTextView.text = prettyPrintJson(result)
})
}
var errorMessage: String
if let error = error {
if let cloudUserInfo = error.userInfo as? [String: AnyObject],
cloudMessage = cloudUserInfo["errorMessage"] as? String {
errorMessage = "Error: \(cloudMessage)"
} else {
errorMessage = "Error occurred in invoking the Lambda Function. No error message found."
}
dispatch_async(dispatch_get_main_queue(), {
print("Error occurred in invoking Lambda Function: \(error)")
self.activityIndicator.stopAnimating()
self.resultTextView.text = errorMessage
let alertView = UIAlertController(title: NSLocalizedString("Error", comment: "Title bar for error alert."), message: error.localizedDescription, preferredStyle: .Alert)
alertView.addAction(UIAlertAction(title: NSLocalizedString("Dismiss", comment: "Button on alert dialog."), style: .Default, handler: nil))
self.presentViewController(alertView, animated: true, completion: nil)
})
}
})
}
Creating a Project in Xcode
Now that you have a project set up in AWS, download Xcode from the Apple App Store (warning: it takes up a ton of space (5-10 GB), so make sure you have a lot to spare).
At the top of your project in AWS, click "integrate with my app." You can either integrate the features you chose with a project started from scratch in Xcode, or you can download a sample app from AWS and adapt from there. We would highly recommend the latter option because there are lots of helper codes, frameworks, and software development kits that you have to load into your app if you do it from scratch.
Click "Download a sample app," and open the Zip file.
You now have an app with the capability to call Lambda functions, populate a database, and do whatever other features you chose. You can look at the Storyboards under MySampleApp -> MySampleApp -> Demo -> Cloud Logic/UserIdentity/etc to see the pre-made app has, and you can click the play button in the top left to open the Simulator and test functions. You can also run the simulator on your iOS device, but I had to follow these's directions to get around the developer licensing issue (we were able to do the whole project without spending any money on software or development).
Connecting to an AWS Database
In order to connect to an AWS Database, you must have a Lambda functions. This can be done in either Java or Python. Since we wrote our Lambda function in Java, we will only be writing about how to do it in Java.
First you must open Eclipse. At the top of your screen, go to Help --> Install New Software. Enter the necessary URL provided on the Amazon website and follow the instructions. When it asks you to install sample code, make sure to download a sample Lambda Function.
Add any instance variables you might need at the top
private static AmazonDynamoDBClient dynamoDB; //this one is already created
private static Map<String, Restaurant> map;
Add your credentials. This will be needed in order to send the information to your AWS account. You can find your credentials by logging into the AWS console and clicking on your account name. A menu will appear. Click on My Security Credentials. A pop-up window will appear. Click on the button saying Continue to Security Credentials. After that, expand the tab saying Access Keys. Click the button saying Create New Access Key and follow the instructions. You will see an Access Key ID and a Secret Key ID. Keep these in a secure place so that you can access them. You will place them into the code below.
String access_key_id = "********************"; //replaced with stars for privacy reasons
String secret_key_id = "****************************************"; //replaced with stars for privacy reasons
BasicAWSCredentials credentials = new BasicAWSCredentials(access_key_id, secret_key_id);
dynamoDB = new AmazonDynamoDBClient(credentials);
Region usEast1 = Region.getRegion(Regions.US_EAST_1);
dynamoDB.setRegion(usEast1);
Modify any other instance variables you may have created.
Entire function must be written within a try and catch statement. It should look something like this.
try {
//your code goes here
} catch (AmazonServiceException ase) {
System.out.println("Caught an AmazonServiceException, which means your request made it "
+ "to AWS, but was rejected with an error response for some reason.");
System.out.println("Error Message: " + ase.getMessage());
System.out.println("HTTP Status Code: " + ase.getStatusCode());
System.out.println("AWS Error Code: " + ase.getErrorCode());
System.out.println("Error Type: " + ase.getErrorType());
System.out.println("Request ID: " + ase.getRequestId());
return "AmazonServiceException"; //return statement only for debugging purposes
} catch (AmazonClientException ace) {
System.out.println("Caught an AmazonClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with AWS, "
+ "such as not being able to access the network.");
System.out.println("Error Message: " + ace.getMessage());
return "AmazonClientException"; //return statement only for debugging purposes
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return "InterruptedException"; //return statement only for debugging purposes
}
Initialize your talbes
String tableName = "RestaurantTable";
String size1 = "Size1";
String size2 = "Size2";
String size3 = "Size3";
String size4 = "Size4";
String size5 = "Size5";
String size6 = "Size6";
String size7 = "Size7";
String size8 = "Size8";
String size9 = "Size9";
String size10 = "Size10";
String rest = "RestaurantNumTables";
String queue = "NumberInQueue";
//------------------This section of code creates a table----------------
// Create a table with a primary hash key named 'name', which holds a string
CreateTableRequest RestaurantTable = new CreateTableRequest().withTableName(tableName)
.withKeySchema(new KeySchemaElement().withAttributeName("Size:Index").withKeyType(KeyType.HASH))
.withAttributeDefinitions(new AttributeDefinition().withAttributeName("Size:Index").withAttributeType(ScalarAttributeType.S))
.withProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(1L).withWriteCapacityUnits(1L));
CreateTableRequest Size1 = new CreateTableRequest().withTableName(size1)
.withKeySchema(new KeySchemaElement().withAttributeName("Name").withKeyType(KeyType.HASH))
.withAttributeDefinitions(new AttributeDefinition().withAttributeName("Name").withAttributeType(ScalarAttributeType.S))
.withProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(1L).withWriteCapacityUnits(1L));
CreateTableRequest Size2 = new CreateTableRequest().withTableName(size2)
.withKeySchema(new KeySchemaElement().withAttributeName("Name").withKeyType(KeyType.HASH))
.withAttributeDefinitions(new AttributeDefinition().withAttributeName("Name").withAttributeType(ScalarAttributeType.S))
.withProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(1L).withWriteCapacityUnits(1L));
CreateTableRequest Size3 = new CreateTableRequest().withTableName(size3)
.withKeySchema(new KeySchemaElement().withAttributeName("Name").withKeyType(KeyType.HASH))
.withAttributeDefinitions(new AttributeDefinition().withAttributeName("Name").withAttributeType(ScalarAttributeType.S))
.withProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(1L).withWriteCapacityUnits(1L));
CreateTableRequest Size4 = new CreateTableRequest().withTableName(size4)
.withKeySchema(new KeySchemaElement().withAttributeName("Name").withKeyType(KeyType.HASH))
.withAttributeDefinitions(new AttributeDefinition().withAttributeName("Name").withAttributeType(ScalarAttributeType.S))
.withProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(1L).withWriteCapacityUnits(1L));
CreateTableRequest Size5 = new CreateTableRequest().withTableName(size5)
.withKeySchema(new KeySchemaElement().withAttributeName("Name").withKeyType(KeyType.HASH))
.withAttributeDefinitions(new AttributeDefinition().withAttributeName("Name").withAttributeType(ScalarAttributeType.S))
.withProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(1L).withWriteCapacityUnits(1L));
CreateTableRequest Size6 = new CreateTableRequest().withTableName(size6)
.withKeySchema(new KeySchemaElement().withAttributeName("Name").withKeyType(KeyType.HASH))
.withAttributeDefinitions(new AttributeDefinition().withAttributeName("Name").withAttributeType(ScalarAttributeType.S))
.withProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(1L).withWriteCapacityUnits(1L));
CreateTableRequest Size7 = new CreateTableRequest().withTableName(size7)
.withKeySchema(new KeySchemaElement().withAttributeName("Name").withKeyType(KeyType.HASH))
.withAttributeDefinitions(new AttributeDefinition().withAttributeName("Name").withAttributeType(ScalarAttributeType.S))
.withProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(1L).withWriteCapacityUnits(1L));
CreateTableRequest Size8 = new CreateTableRequest().withTableName(size8)
.withKeySchema(new KeySchemaElement().withAttributeName("Name").withKeyType(KeyType.HASH))
.withAttributeDefinitions(new AttributeDefinition().withAttributeName("Name").withAttributeType(ScalarAttributeType.S))
.withProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(1L).withWriteCapacityUnits(1L));
CreateTableRequest Size9 = new CreateTableRequest().withTableName(size9)
.withKeySchema(new KeySchemaElement().withAttributeName("Name").withKeyType(KeyType.HASH))
.withAttributeDefinitions(new AttributeDefinition().withAttributeName("Name").withAttributeType(ScalarAttributeType.S))
.withProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(1L).withWriteCapacityUnits(1L));
CreateTableRequest Size10 = new CreateTableRequest().withTableName(size10)
.withKeySchema(new KeySchemaElement().withAttributeName("Name").withKeyType(KeyType.HASH))
.withAttributeDefinitions(new AttributeDefinition().withAttributeName("Name").withAttributeType(ScalarAttributeType.S))
.withProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(1L).withWriteCapacityUnits(1L));
CreateTableRequest RestaurantNumTables = new CreateTableRequest().withTableName(rest)
.withKeySchema(new KeySchemaElement().withAttributeName("Name").withKeyType(KeyType.HASH))
.withAttributeDefinitions(new AttributeDefinition().withAttributeName("Name").withAttributeType(ScalarAttributeType.S))
.withProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(1L).withWriteCapacityUnits(1L));
CreateTableRequest NumberInQueue = new CreateTableRequest().withTableName(queue)
.withKeySchema(new KeySchemaElement().withAttributeName("Name").withKeyType(KeyType.HASH))
.withAttributeDefinitions(new AttributeDefinition().withAttributeName("Name").withAttributeType(ScalarAttributeType.S))
.withProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(1L).withWriteCapacityUnits(1L));
// Create table if it does not exist yet
TableUtils.createTableIfNotExists(dynamoDB, RestaurantTable);
TableUtils.createTableIfNotExists(dynamoDB, Size1);
TableUtils.createTableIfNotExists(dynamoDB, Size2);
TableUtils.createTableIfNotExists(dynamoDB, Size3);
TableUtils.createTableIfNotExists(dynamoDB, Size4);
TableUtils.createTableIfNotExists(dynamoDB, Size5);
TableUtils.createTableIfNotExists(dynamoDB, Size6);
TableUtils.createTableIfNotExists(dynamoDB, Size7);
TableUtils.createTableIfNotExists(dynamoDB, Size8);
TableUtils.createTableIfNotExists(dynamoDB, Size9);
TableUtils.createTableIfNotExists(dynamoDB, Size10);
TableUtils.createTableIfNotExists(dynamoDB, RestaurantNumTables);
TableUtils.createTableIfNotExists(dynamoDB, NumberInQueue);
// wait for the table to move into ACTIVE state
TableUtils.waitUntilActive(dynamoDB, tableName);
TableUtils.waitUntilActive(dynamoDB, size1);
TableUtils.waitUntilActive(dynamoDB, size2);
TableUtils.waitUntilActive(dynamoDB, size3);
TableUtils.waitUntilActive(dynamoDB, size4);
TableUtils.waitUntilActive(dynamoDB, size5);
TableUtils.waitUntilActive(dynamoDB, size6);
TableUtils.waitUntilActive(dynamoDB, size7);
TableUtils.waitUntilActive(dynamoDB, size8);
TableUtils.waitUntilActive(dynamoDB, size9);
TableUtils.waitUntilActive(dynamoDB, size10);
TableUtils.waitUntilActive(dynamoDB, rest);
TableUtils.waitUntilActive(dynamoDB, queue);