Front-End Web & Mobile

Amazon DynamoDB on Mobile – Part 1: Loading Data

Version 2 of the AWS Mobile SDK

  • This article and sample apply to Version 1 of the AWS Mobile SDK. If you are building new apps, we recommend you use Version 2. For details, please visit the AWS Mobile SDK page.
  • This content is being maintained for historical reference.

Amazon DynamoDB is one of the fastest growing AWS services. It is a fast, fully managed NoSQL database service that makes it simple and cost-effective to store and retrieve any amount of data, and serve any level of request traffic. All data items are stored on Solid State Drives (SSDs), and are replicated across three Availability Zones for high availability and durability.

The AWS Mobile SDKs offers full access to DynamoDB for creating tables, as well as basic CRUD operations for items within the table.

A User Data Table

Let’s consider a table where we want to store information about the current user such as a name, location, and game high score. We can construct this table in the following way to store it in DynamoDB:

  • UserId – our Hash Key to uniquely identify the user, stored as a numeric value.
  • RecordId – our Range Key to identify a given record for the user, stored as a string.
  • Data – the data for the given record and user. As it is not being indexed, we can use any supported type by DynamoDB.

A slice of our table might look like the following:

UserId RecordId Data
1234 name Bob
1234 location Seattle, WA
1234 highscore 120000
6789 name Glenn
6789 location Seattle, WA
6789 highscore 150000

Load a Specific Record

If we know exactly which record we want to load (User 1234’s ‘name’), we can use the GetItem API operation to fetch the record by its composite primary key, the combination of the hash and range keys, and then pull our data from the result.

iOS

// Specify the table we are operating against
DynamoDBGetItemRequest *getItemRequest = [DynamoDBGetItemRequest new];
getItemRequest.tableName = @"UserTableExample";

// Need to specify the key of our item, which is an NSDictionary of our primary key attribute(s)
DynamoDBAttributeValue *userId = [[DynamoDBAttributeValue alloc] initWithN:@"1234"];
DynamoDBAttributeValue *recordId = [[DynamoDBAttributeValue alloc] initWithS:@"name"];
getItemRequest.key = [NSMutableDictionary dictionaryWithObjectsAndKeys:userId, @"UserId", recordId, @"RecordId", nil];

DynamoDBGetItemResponse *getItemResponse = [self.ddb getItem:getItemRequest];

// The item is an NSDictionary of DynamoDBAttributeValue keyed by the attribute name
DynamoDBAttributeValue  *name = [getItemResponse.item valueForKey:@"Data"];

// The name is a string, so its stored value will be in the s property as an NSString
NSLog(@"name = '%@'", name.s);

Android

// Need to specify the key of our item, which is a Map of our primary key attribute(s)
Map<String, AttributeValue> key = new HashMap<String, AttributeValue>();
key.put("UserId", 1234);
key.put("RecordId", "name");

GetItemRequest getItemRequest = new GetItemRequest("UserTableExample",key);
GetItemResult getItemResult = ddbClient.getItem(getItemRequest);

// name is a string, so its stored value will be in the S field
Log.i(LOG_TAG, "value1 = '" + getItemResult.getItem().get("name").getS() + "'");

Load All Records for a User

If we want to load all the records for a given user, we can use the Query API operation to fetch all the records by specifying just our hash key. We could also add conditions on our range key to restrict to a subset of records, even to the point of emulating GetItem by specifying the exact value of the range key we want. Because we don’t know how many records a given user will have and the Query response has an upper limit of 1MB, we may need to call Query multiple times to load all the records.

iOS

// Specify our key conditions ("UserId" == 1234)
DynamoDBCondition *condition = [DynamoDBCondition new];
condition.comparisonOperator = @"EQ";
DynamoDBAttributeValue *userId = [[DynamoDBAttributeValue alloc] initWithN:@"1234"];
[condition addAttributeValueList:userId];

NSMutableDictionary *queryStartKey = nil;
do {
    DynamoDBQueryRequest *queryRequest = [DynamoDBQueryRequest new];
    queryRequest.tableName = @"UserTableExample";
    queryRequest.exclusiveStartKey = queryStartKey;
    queryRequest.keyConditions = [NSMutableDictionary dictionaryWithObject:condition forKey:@"UserId"];

    DynamoDBQueryResponse *queryResponse = [[Constants ddb] query:queryRequest];

    // Each item in the result set is a NSDictionary of DynamoDBAttributeValue
    for (NSDictionary *item in queryResponse.items) {
        DynamoDBAttributeValue *recordId = [item objectForKey:@"RecordId"];
        NSLog(@"record id = '%@'", recordId.s);
    }
    
    // If the response lastEvaluatedKey has contents, that means there are more results
    queryStartKey = queryResponse.lastEvaluatedKey;

} while ([queryStartKey count] != 0);

Android

// Specify our key conditions ("UserId" == 1234)
Condition hashKeyCondition = new Condition()
    .withComparisonOperator(ComparisonOperator.EQ.toString())
    .withAttributeValueList(new AttributeValue().withN("1234"));
Map<String, Condition> keyConditions = new HashMap<String, Condition>();
keyConditions.put("UserId", hashKeyCondition);

Map<String, AttributeValue> lastEvaluatedKey = null;
do {
    QueryRequest queryRequest = new QueryRequest()
            .withTableName("UserTableExample")
            .withKeyConditions(keyConditions)
            .withExclusiveStartKey(lastEvaluatedKey);

    QueryResult queryResult = client.query(queryRequest);
    for (Map<String, AttributeValue> item : queryResult.getItems()) {
        // name is a string, so it's stored value will be in the S field
        Log.i(LOG_TAG, "record id = '" + item.get("RecordId").getS() + "'");
    }

    // If the response lastEvaluatedKey has contents, that means there are more results
    lastEvaluatedKey = queryResult.getLastEvaluatedKey();
} while (lastEvaluatedKey != null);

We hope this illustrates the basics of loading and using data from DynamoDB with the AWS Mobile SDKs. Stay tuned for future posts in which we discuss advanced control of our read requests and how we can update data in tables.

The code samples included in this post are for the DynamoDB API version 2012-08-10 and will work only with the AWSDynamoDB.framework on iOS and the com.amazonaws.services.dynamodbv2 package on Android. See our previous post on how to convert your iOS application to use the new service level frameworks. If you are interested in a higher-level interface, you may be interested in the AWS Persistance Framework for Core Data or DynamoDBMapper for Android.

Further Reading