Menu
AWS Mobile
Developer Guide

Add Online Data Access to the Notes App

In the previous section of this tutorial, we added a simple sign-up / sign-in flow to the sample note-taking app with email validation. This tutorial assumes you have completed the previous tutorials. If you jumped to this step, please go back and start from the beginning. In this tutorial, we will add a NoSQL database to our mobile backend, then configure a basic data access service to the note-taking app.

The notes app uses iOS Core Data as a persistence framework. NotesContentProvider.swift is custom content provider used as a clean interface for managing your application content locally. In the following steps, you will modify the content provider code to use DynamoDB and sync with the local Core data.

You should be able to complete this section of the tutorial in about 30-45 minutes.

Set Up Your Backend

To add User Sign-in to your app you will create the backend resources in your Mobile Hub project, and then update the configuration file in your app.

Add a NoSQL Database to the AWS Mobile Hub Project

Before we work on the client-side code, we need to add a NoSQL database and table to the backend project:

  1. Right-click awsconfiguration.json in your Xcode Project Navigator, choose Delete, and then choose Move to trash.

  2. Open the AWS Mobile Hub console.

  3. Select your project.

  4. Scroll down to the Add More Backend Features section and then choose the NoSQL Database tile.

  5. Choose Enable NoSQL, choose Add Table, and then choose Example to start with an example schema.

  6. Choose Notes, which most closely matches the model we wish to use.

  7. Choose Add attribute, then fill in the details of the new attribute:

    • Attribute name: updatedDate

    • Type: number

  8. Choose Add index then fill in the details of the new index:

    • Index name: LastUpdated

    • Partition key: userId

    • Sort key: updatedDate

  9. Choose Create table

  10. Choose Create table in the modal dialog.

  11. Choose your project name in the upper left and then choose Integrate on your iOS app card.

  12. Choose Download Cloud Config to get an updated awsconfiguration.json file.

Connect to Your Backend

To update the linkage between your app and your AWS services:

  1. Drag awsconfiguration.json from your download location into the Xcode project folder containing Info.plist. Select Copy items if needed and Create groups in the options dialog. Choose Finish.

Your system may have modified the filename to avoid conflicts. Make sure the file you add to your Xcode project is named awsconfiguration.json.

Download the Models

To aid in implementing a provider for the table you created, Mobile Hub generated a data model descriptor file. To add the data model to your project:

  1. Choose your project name in the upper left and then choose Integrate on the iOS app card.

  2. Choose Swift Models under Download Models.

  3. Unpack the downloaded ZIP file.

  4. Find Notes.swift, and then drag and drop it into the folder in Xcode that contains file:Info.plist. Select Copy items if needed and Create groups in the options dialog. Choose Finish.

Add NoSQL Data Dependencies

  1. Add the following NoSQL Data dependencies in your project's Podfile

    platform :ios, '9.0' target :'MyNotes' do use_frameworks! # Analytics dependency pod 'AWSPinpoint', '~> 2.6.5' # Auth dependencies pod 'AWSUserPoolsSignIn', '~> 2.6.5' pod 'AWSAuthUI', '~> 2.6.5' pod 'AWSMobileClient', '~> 2.6.5' # NoSQL Data dependencies pod 'AWSDynamoDB', '~> 2.6.5' # other pods end

    Then, in a terminal run:

    pod install --repo-update

    If you encounter an error message that begins "[!] Failed to connect to GitHub to update the CocoaPods/Specs . . .", and your internet connectivity is working, you may need to update openssl and Ruby

Implement Mutation Methods

NotesContentProvider is the basic interface the app uses to communicate with Core data and your NoSQL table in Amazon DynamoDB. Mutation events handle the CRUD operations when you call its insertNoteDDB, updateNoteDDB, and deleteNoteDDB methods.

To add these mutation methods to the NotesContentProvider class, add the following import statement to the file.

import AWSDynamoDB import AWSAuthCore

Then add CRUD functions (insert, update, and delete) to the NotesContentProvider to the class as follows.

public class NotesContentProvider { // . . . //Insert a note using Amazon DynamoDB func insertNoteDDB(noteId: String, noteTitle: String, noteContent: String) -> String { let dynamoDbObjectMapper = AWSDynamoDBObjectMapper.default() // Create a Note object using data model you downloaded from Mobile Hub let noteItem: Notes = Notes() noteItem._userId = AWSIdentityManager.default().identityId noteItem._noteId = noteId noteItem._title = emptyTitle noteItem._content = emptyContent noteItem._creationDate = NSDate().timeIntervalSince1970 as NSNumber //Save a new item dynamoDbObjectMapper.save(noteItem, completionHandler: { (error: Error?) -> Void in if let error = error { print("Amazon DynamoDB Save Error on new note: \(error)") return } print("New note was saved to DDB.") }) return noteItem._noteId! } //Insert a note using Amazon DynamoDB func updateNoteDDB(noteId: String, noteTitle: String, noteContent: String) { let dynamoDbObjectMapper = AWSDynamoDBObjectMapper.default() let noteItem: Notes = Notes() noteItem._userId = AWSIdentityManager.default().identityId noteItem._noteId = noteId if (!noteTitle.isEmpty){ noteItem._title = noteTitle } else { noteItem._title = emptyTitle } if (!noteContent.isEmpty){ noteItem._content = noteContent } else { noteItem._content = emptyContent } noteItem._updatedDate = NSDate().timeIntervalSince1970 as NSNumber let updateMapperConfig = AWSDynamoDBObjectMapperConfiguration() updateMapperConfig.saveBehavior = .updateSkipNullAttributes //ignore any null value attributes and does not remove in database dynamoDbObjectMapper.save(noteItem, configuration: updateMapperConfig, completionHandler: {(error: Error?) -> Void in if let error = error { print(" Amazon DynamoDB Save Error on note update: \(error)") return } print("Existing note updated in DDB.") }) } //Delete a note using Amazon DynamoDB func deleteNoteDDB(noteId: String) { let dynamoDbObjectMapper = AWSDynamoDBObjectMapper.default() let itemToDelete = Notes() itemToDelete?._userId = AWSIdentityManager.default().identityId itemToDelete?._noteId = noteId dynamoDbObjectMapper.remove(itemToDelete!, completionHandler: {(error: Error?) -> Void in if let error = error { print(" Amazon DynamoDB Save Error: \(error)") return } print("An note was deleted in DDB.") }) } }

Implement Query Methods

This application always asks for the entire data set that the user is entitled to see, so there is no need to implement complex query management. This simplifies the query() method considerably. The query() method returns a Cursor (which is a standard mechanism for iterating over data sets returned from databases).

Add the following query function to the NotesContentProvider class:

func getNotesFromDDB() { // 1) Configure the query looking for all the notes created by this user (userId => Cognito identityId) let queryExpression = AWSDynamoDBQueryExpression() queryExpression.keyConditionExpression = "#userId = :userId" queryExpression.expressionAttributeNames = [ "#userId": "userId", ] queryExpression.expressionAttributeValues = [ ":userId": AWSIdentityManager.default().identityId ] // 2) Make the query let dynamoDbObjectMapper = AWSDynamoDBObjectMapper.default() dynamoDbObjectMapper.query(Notes.self, expression: queryExpression) { (output: AWSDynamoDBPaginatedOutput?, error: Error?) in if error != nil { print("DynamoDB query request failed. Error: \(String(describing: error))") } if output != nil { print("Found [\(output!.items.count)] notes") for notes in output!.items { let noteItem = notes as? Notes print("\nNoteId: \(noteItem!._noteId!)\nTitle: \(noteItem!._title!)\nContent: \(noteItem!._content!)") } } } }

Add Data Access Calls

Calls to insert, update, delete, and query data stored in Amazon DynamoDB are made in MasterViewController and DetailsViewController.

  1. To create a note in Amazon DynamoDB , add the following call to noteContentProvider?.insertNoteDDB() to the insert portion of the autoSave() function of DetailViewController.

    // . . . // If this is a NEW note, set the Note Id if (DetailViewController.noteId == nil) // Insert { let id = noteContentProvider?.insert(noteTitle: "", noteContent: "") noteContentProvider?.insertNoteDDB(noteId: id!, noteTitle: "", noteContent: "") DetailViewController.noteId = id } else // Update { let noteId = DetailViewController.noteId let noteTitle = self.noteTitle.text let noteContent = self.noteContent.text noteContentProvider?.update(noteId: noteId!, noteTitle: noteTitle!, noteContent: noteContent!) noteContentProvider?.update(noteId: noteId!, noteTitle: noteTitle!, noteContent: noteContent!) } // . . .
  2. To update a note from Amazon DynamoDB , add the following line in to the update portion of the autoSave() function of DetailViewController.

    noteContentProvider?.updateNoteDDB(noteId: noteId!, noteTitle: noteTitle!, noteContent: noteContent!)
  3. To delete a note from Amazon DynamoDB, update the following function in the MasterViewController with a call to deleteNoteDDB().

    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { let context = fetchedResultsController.managedObjectContext let noteObj = fetchedResultsController.object(at: indexPath) let noteId = fetchedResultsController.object(at: indexPath).noteId //Delete Note Locally _noteContentProvider?.delete(managedObjectContext: context, managedObj: noteObj, noteId: noteObj.noteId) //Core Data Delete //Delete Note in DynamoDB _noteContentProvider?.deleteNoteDDB(noteId: noteId!) } }
  4. To query for all notes from Amazon DynamoDB, add the following line to the bottom of the viewDidLoad() function in the MasterViewController:

    _noteContentProvider?.getNotesFromDDB()

Note

Differences from a real implementation

We've taken a simplified approach for this content provider to demonstrate the CRUD implementation. A real implementation would need to deal with online state and handle caching of the data, plus handle appropriate query capabilities as required by the application.

Run the App and Validate Results

You must be online in order to run this application. Run the application in the emulator. Note that the initial startup after logging in is slightly longer (due to reading the data from the remote database).

Data is available immediately in the mobile backend. Create a few notes, then view the records within the AWS Console:

  1. Open the Mobile Hub console.

  2. Choose your project.

  3. Choose Resources on the upper right.

  4. Choose the link for your Amazon DynamoDB table.

  5. Choose the Items tab.

When you insert, edit or delete notes in the app, you should be able to see the data on the server reflect your actions almost immediately.

Next Steps