Front-End Web & Mobile
Creating Mobile Apps with Dynamic Content Stored in Amazon S3
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.
Native mobile apps allow image files and other content assets to be bundled with the app code, which typically means that updates to those assets entails deploying a new version of the app to your end users. This blog post demonstrates how to store your content in Amazon S3 and access it in app code from a local store on the device. We’ll show how your app code can quickly check with Amazon S3 if the content needs to be updated, so that all you need to do to change the content is upload new files to Amazon S3.
Example Code Overview
This blog post includes example code for an app that displays a dynamic image. First, the app determines if it has the appropriate image. Then, it either retrieves the latest version from Amazon S3, or loads the image from the local store.
Checking and Displaying an Image
The following function displays the image in the app. This main function checks if a newer image is available from Amazon S3. If a newer image is available, the app retrieves it, stores it locally, and then displays the image.
iOS
-(void)displayImage:(AmazonS3Client*)s3 withName:(NSString*)imageName fromBucket:(NSString*)bucketName { if ( [self isNewImageAvailable:s3 withName:imageName fromBucket:bucketName] ) { [self getRemoteImage:s3 withName:imageName fromBucket:bucketName]; } NSData *imageData = [self getLocalImage:imageName]; UIImage *image = [UIImage imageWithData:imageData]; UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; [self.view addSubview:imageView]; }
Android
private void displayImage( ImageView view, AmazonS3Client s3, String imageName, String bucketName ) { if ( this.isNewImageAvailable( s3, imageName, bucketName ) ) { this.getRemoteImage( s3, imageName, bucketName ); } InputStream stream = this.getLocalImage( imageName ); view.setImageDrawable( Drawable.createFromStream( stream, "src" ) ); }
Checking If a Newer Image is Available
To see if the remote image has changed, we use Amazon S3’s getObjectMetadata
operation. This operation allows us to check the local file date versus the last modified date of the image object in Amazon S3. If the remote image file’s date is later, then we need to update our local copy.
iOS
-(BOOL)isNewImageAvailable:(AmazonS3Client*)s3 withName:(NSString*)imageName fromBucket:(NSString*)bucketName { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSArray *pathComponents = [NSArray arrayWithObjects:documentsDirectory, imageName, nil ]; NSString *filePath = [NSString pathWithComponents:pathComponents]; NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; if ( attributes == nil ) { return YES; } else { NSDate *date = [attributes fileModificationDate]; S3GetObjectMetadataRequest *request = nil; S3GetObjectMetadataResponse *response = nil; request = [[S3GetObjectMetadataRequest alloc] initWithKey:imageName withBucket:bucketName]; response = [s3 getObjectMetadata:request]; if ( [response.lastModified compare:date] == NSOrderedDescending ) { return YES; } else { return NO; } } }
Android
private boolean isNewImageAvailable( AmazonS3Client s3, String imageName, String bucketName ) { File file = new File( this.getApplicationContext().getFilesDir(), imageName ); if ( !file.exists() ) { return true; } ObjectMetadata metadata = s3.getObjectMetadata( bucketName, imageName ); long remoteLastModified = metadata.getLastModified().getTime(); if ( file.lastModified() < remoteLastModified ) { return true; } else { return false; } }
Getting an Image from Amazon S3
To retrieve an image from Amazon S3, we use the GetObject
operation as shown in the following code. The code also stores the image data locally.
iOS
-(void)getRemoteImage:(AmazonS3Client*)s3 withName:(NSString*)imageName fromBucket:(NSString*)bucketName { S3GetObjectRequest *request = [[S3GetObjectRequest alloc] initWithKey:imageName withBucket:bucketName]; S3GetObjectResponse *response = [s3 getObject:request]; [self storeImageLocally:response.body withName:imageName]; }
Android
private void getRemoteImage( AmazonS3Client s3, String imageName, String bucketName ) { S3Object object = s3.getObject( bucketName, imageName ); this.storeImageLocally( object.getObjectContent(), imageName ); }
Storing the Image Locally
As stated previously, once we have the image, we want to store it in the app’s local file store. This local copy of the image allows us to display an image without network connectivity and helps us determine when we need to refresh the image from Amazon S3.
iOS
-(void)storeImageLocally:(NSData*)imageData withName:(NSString*)imageName { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSArray *pathComponents = [NSArray arrayWithObjects:documentsDirectory, imageName, nil ]; NSString *filePath = [NSString pathWithComponents:pathComponents]; [imageData writeToFile:filePath atomically:YES]; }
Make sure to mark the file as not backed up to iCloud by following these instructions.
Android
private void storeImageLocally( InputStream stream, String imageName ) { FileOutputStream outputStream; try { outputStream = openFileOutput( imageName, Context.MODE_PRIVATE); int length = 0; byte[] buffer = new byte[1024]; while ( ( length = stream.read( buffer ) ) > 0 ) { outputStream.write( buffer, 0, length ); } outputStream.close(); } catch ( Exception e ) { Log.d( "Store Image", "Can't store image : " + e ); } }
Loading the Local Image
When our local version of the image is current, we load the image from the local file store. The following code shows how to load the image.
Tip: You should include the image in the app’s assets. This allows you to load an image in the app, even if the network is not available the first time the app is launched.
iOS
-(NSData*)getLocalImage:(NSString*)imageName { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSArray *pathComponents = [NSArray arrayWithObjects:documentsDirectory, imageName, nil ]; NSString *filePath = [NSString pathWithComponents:pathComponents]; return [NSData dataWithContentsOfFile:filePath]; }
Android
private InputStream getLocalImage( String imageName ) { try { return openFileInput( imageName ); } catch ( FileNotFoundException exception ) { return null; } }
Update Frequency
How frequently you intend to change the image dictates how often you should check for a new version in your app. Checking for an updated image as few times as possible will minimize your Amazon S3 costs. If you deploy an app with this functionality, all you need to do is update the image in your Amazon S3 bucket (using the AWS Management Console for example) to give your app a new image.
Retrieving Content Asynchronously
In the name of brevity, the example code in this blog post performs synchronous network requests for the image. In a real-world app, synchronous network calls should not be made from the main UI thread. You can reference the following posts to learn how to make the example code asynchronous:
iOS
- Using the AWS SDK for iOS Asynchronously – Part I: Sync vs. Async
- Using the AWS SDK for iOS Asynchronously – Part II: AmazonRequestDelegate Best Practices
- Using the AWS SDK for iOS Asynchronously – Part III: Using Grand Central Dispatch (GCD)
- Using the AWS SDK for iOS Asynchronously – Part IV: Grand Central Dispatch (GCD) Best Practices
Android
Summary
This blog post showed how to create a mobile app that displays a dynamic image stored in Amazon S3. This strategy applies to more than just images. Any content or media you want to be dynamic in your app could benefit from this approach. Please let us know what you think by leaving a comment below.