Using GraphQL API
Note: Please review the documentation for API before you proceed with the rest of this section.
You can also upload and download Amazon S3 Objects using AWS AppSync, a GraphQL based solution to build data-driven apps with real-time and offline capabilities. Sometimes you might want to create logical objects that have more complex data, such as images or videos, as part of their structure. For example, you might create a Person type with a profile picture or a Post type that has an associated image. You can use AWS AppSync to model these as GraphQL types. If any of your mutations have a variable with bucket, key, region, mimeType, and localUri fields, the SDK uploads the file to Amazon S3 for you.
Attach the following policy to your IAM role to grant it programmatic read-write access to your bucket:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["s3:ListBucket"], "Resource": ["arn:aws:s3:::myBucket"] }, { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject" ], "Resource": ["arn:aws:s3:::myBucket/*"] } ]}
Schema Setup
If any mutations have an input type S3ObjectInput
with fields bucket
, key
, region
, mimeType
and localUri
fields, the SDK will upload the file to S3.
input S3ObjectInput { bucket: String! key: String! region: String! localUri: String mimeType: String }
For example, to add a photo field in the Post
type. Update Post
type, add the new S3ObjectInput
type and add a new mutation, putPostWithPhoto
.
type Mutation { ...other mutations here... putPostWithPhoto( id: ID!, author: String!, title: String, content: String, url: String, ups: Int, downs: Int, photo: S3ObjectInput version: Int! ): Post } type S3Object { bucket: String! key: String! region: String! } input S3ObjectInput { bucket: String! key: String! region: String! localUri: String mimeType: String } type Post { id: ID! author: String! title: String content: String url: String ups: Int downs: Int photo: S3Object version: Int! }
Next, update the putPostWithPhoto mutation resolver to use PutItemWithS3Object
template for request mapping
and Return single item
for response mapping from the AppSync console.
Next, update the response mapping template for the photo
field.
$util.toJson($util.dynamodb.fromS3ObjectJson($context.source.file))
Client Code
To use complex objects, you need AWS Identity and Access Management credentials for reading and writing to Amazon S3.
These can be separate from the other authentication credentials used in the AWS AppSync client.
Credentials for complex objects are set in the S3ObjectManagerImplementation
builder parameter, which you can use like the following:
public class ClientFactory { // ...other code... private static volatile AWSAppSyncClient client; private static volatile S3ObjectManagerImplementation s3ObjectManager; public static AWSAppSyncClient getInstance(Context context) { if (client == null) { client = AWSAppSyncClient.builder() .context(context) .awsConfiguration(new AWSConfiguration(context)) .s3ObjectManager(getS3ObjectManager(context)) // Here you initialize the s3 object manager. .build(); } return client; } // Copy the below two methods and add the .s3ObjectManager builder parameter // initialize and fetch the S3 Client public static final S3ObjectManagerImplementation getS3ObjectManager(final Context context) { if (s3ObjectManager == null) { AmazonS3Client s3Client = new AmazonS3Client(getCredentialsProvider(context)); s3Client.setRegion(Region.getRegion("us-east-1")); // you can set the region of bucket here s3ObjectManager = new S3ObjectManagerImplementation(s3Client); } return s3ObjectManager; } // initialize and fetch cognito credentials provider for S3 Object Manager public static final AWSCredentialsProvider getCredentialsProvider(final Context context){ final CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider( context, Constants.COGNITO_IDENTITY, // Identity pool ID Regions.fromName(Constants.COGNITO_REGION) // Region ); return credentialsProvider; }}
The SDK uploads the file found at the localUri
when the bucket, key, region, localUri, and mimeType are all provided.
Now, the SDK uploads any field which has S3ObjectInput
type in the mutation. The only requirement from a developer is to provide the correct bucket, key, region, localUri, and mimeType.
Example:
Add the following permissions to AndroidManifest.xml
:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Then, in your activity where you are adding a post, update the code as follows:
public class AddPostActivity extends AppCompatActivity { // ...other code... // Photo selector application code. private static int RESULT_LOAD_IMAGE = 1; private String photoPath; public void choosePhoto() { Intent i = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); startActivityForResult(i, RESULT_LOAD_IMAGE); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == RESULT_LOAD_IMAGE && resultCode == RESULT_OK && null != data) { Uri selectedImage = data.getData(); String[] filePathColumn = {MediaStore.Images.Media.DATA}; Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null); cursor.moveToFirst(); int columnIndex = cursor.getColumnIndex(filePathColumn[0]); String picturePath = cursor.getString(columnIndex); cursor.close(); // String picturePath contains the path of selected Image photoPath = picturePath; } } // Actual mutation code private void save() { final String title = ((EditText) findViewById(R.id.updateTitle)).getText().toString(); final String author = ((EditText) findViewById(R.id.updateAuthor)).getText().toString(); final String url = ((EditText) findViewById(R.id.updateUrl)).getText().toString(); final String content = ((EditText) findViewById(R.id.updateContent)).getText().toString(); S3ObjectInput s3ObjectInput = S3ObjectInput.builder() .bucket("YOUR_BUCKET_NAME") .key("public/"+ UUID.randomUUID().toString()) .region("us-east-1") .localUri(photoPath) .mimeType("image/jpg").build(); PutPostWithPhotoMutation addPostMutation = PutPostWithPhotoMutation.builder() .title(title) .author(author) .url(url) .content(content) .ups(0) .downs(0) .photo(s3ObjectInput) .expectedVersion(1) .build(); ClientFactory.getInstance(this).mutate(addPostMutation).enqueue(postsCallback); } // Mutation callback code private GraphQLCall.Callback<PutPostWithPhotoMutation.Data> postsCallback = new GraphQLCall.Callback<PutPostWithPhotoMutation.Data>() { @Override public void onResponse(@Nonnull final Response<PutPostWithPhotoMutation.Data> response) { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(AddPostActivity.this, "Added post", Toast.LENGTH_SHORT).show(); AddPostActivity.this.finish(); } }); } @Override public void onFailure(@Nonnull final ApolloException e) { runOnUiThread(new Runnable() { @Override public void run() { Log.e("", "Failed to perform AddPostMutation", e); Toast.makeText(AddPostActivity.this, "Failed to add post", Toast.LENGTH_SHORT).show(); AddPostActivity.this.finish(); } }); } };}