Upload Files to Amazon S3 from Android
File storage is a common feature in most modern apps whether the file be an image, video, audio file, or some other custom file.
This article will cover how to upload a photo from the photo gallery to Amazon S3 using AWS Amplify Storage, then download that image and display it in an Android app using Jetpack Compose and Coil.
Configuring Amplify
Start by running the following Amplify CLI terminal command at the root directory of your project:
$ amplify init
Answer the command prompts to finish initializing your Amplify project locally. They should look like this:
? Enter a name for the project uploadtos3android
The following configuration will be applied:
Project information
| Name: uploadtos3android
| Environment: dev
| Default editor: Visual Studio Code
| App type: android
| Res directory: app/src/main/res
? Initialize the project with the above configuration? Yes
Using default provider awscloudformation
? Select the authentication method you want to use: AWS profile
? Please choose the profile you want to use default
Add the Amplify Storage category:
$ amplify add storage
You can select the default answer to most questions by hitting Enter. Here are the answers I selected for the following prompts:
? Select from one of the below mentioned services: Content (Images, audio, video
, etc.)
✔ You need to add auth (Amazon Cognito) to your project in order to add storage for user files. Do you want to add auth now? (Y/n) · yes
Using service: Cognito, provided by: awscloudformation
Do you want to use the default authentication and security configuration? Default configuration
How do you want users to be able to sign in? Username
Do you want to configure advanced settings? No, I am done.
✔ Provide a friendly name for your resource that will be used to label this category in the project: · s32147b60f
✔ Provide bucket name: · uploadtos3android106dd97fb37542bea66e39dfeff2a7
✔ Who should have access: · Auth and guest users
✔ What kind of access do you want for Authenticated users? · create/update, read, delete
✔ What kind of access do you want for Guest users? · create/update, read, delete
✔ Do you want to add a Lambda Trigger for your S3 Bucket? (y/N) · no
Push the Amplify project configuration to the backend.
$ amplify push -y
Once the Storage resources have been successfully created, add the Amplify framework as a dependency for your Android project in the app build.gradle
file:
// Amplify
def amplify_version = "1.31.3"
implementation "com.amplifyframework:aws-storage-s3:$amplify_version"
implementation "com.amplifyframework:aws-auth-cognito:$amplify_version"
Create a function to configure both the Auth and Storage categories in MainActivity
:
private fun configureAmplify() {
try {
Amplify.addPlugin(AWSCognitoAuthPlugin())
Amplify.addPlugin(AWSS3StoragePlugin())
Amplify.configure(applicationContext)
Log.i("kilo", "Initialized Amplify")
} catch (error: AmplifyException) {
Log.e("kilo", "Could not initialize Amplify", error)
}
}
Amplify must be configured before attempting to use any of the categories. Call configureAmplify
in the onCreate
method of MainActivity
.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
configureAmplify()
setContent {
Text(text = "Like and subscribe")
}
}
If you build and run now, you should see that Amplify has been successfully configured in the console.
Selecting a Photo
Create a sealed class that will be responsible for handling the different states that will determine what is rendered to the user's screen:
sealed class ImageState {
object Initial: ImageState()
class ImageSelected(val imageUri: Uri): ImageState()
object ImageUploaded: ImageState()
class ImageDownloaded(val downloadedImageFile: File): ImageState()
}
In MainActivity
add the following properties:
private var imageState = mutableStateOf<ImageState>(ImageState.Initial)
private val getImageLauncher = registerForActivityResult(GetContent()) { uri ->
uri?.let { imageState.value = ImageState.ImageSelected(it) }
}
imageState
will keep track of the current ImageState and getImageLauncher
will be responsible for handling the Uri
once a photo is selected.
In setContent
of MainActivity.onCreate
, add the following composable views:
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
when (val state = imageState.value) {
// Show Open Gallery Button
is ImageState.Initial -> {
Button(onClick = { getImageLauncher.launch("image/*") }) {
Text(text = "Open Gallery")
}
}
}
}
imageState
will have a default value of ImageState.Initial
, so the Open Gallery button will be shown when the app is launched. Clicking the button will trigger the image launcher and show the user's photo gallery.
If you build and run the app, you will now be able to select a photo from your gallery.
Uploading a Photo
Amazon S3 uses keys to index files. Create a constant value that can be used to name and retrieve the uploaded photo.
companion object {
const val PHOTO_KEY = "my-photo.jpg"
}
Use Amplify.Storage
to upload your photo using an InputStream:
private fun uploadPhoto(imageUri: Uri) {
val stream = contentResolver.openInputStream(imageUri)!!
Amplify.Storage.uploadInputStream(
PHOTO_KEY,
stream,
{ imageState.value = ImageState.ImageUploaded },
{ error -> Log.e("kilo", "Failed upload", error) }
)
}
This method is using callbacks to update imageState
or log any errors with the upload. To see more ways to upload files, see Upload files.
Add the following snippet to the when
statement in setContent
:
// Show Upload Button
is ImageState.ImageSelected -> {
Button(onClick = { uploadPhoto(state.imageUri) }) {
Text(text = "Upload Photo")
}
}
If you build and run now, you will be able to upload a selected photo from your photo gallery to S3.
Downloading a Photo
Create a function that will be responsible for downloading the file/photo from S3:
private fun downloadPhoto() {
val localFile = File("${applicationContext.filesDir}/downloaded-image.jpg")
Amplify.Storage.downloadFile(
PHOTO_KEY,
localFile,
{ imageState.value = ImageState.ImageDownloaded(localFile) },
{ Log.e("kilo", "Failed download", it) }
)
}
Once you provide a key and file destination, you can use Storage.downloadFile
to write the file locally. Upon success, imageState
is updated to ImageState.ImageDownloaded
and provides the destination of the downloaded file.
Again in the setContent
block, add the following snippet to the when
statement:
// Show Download Button
is ImageState.ImageUploaded -> {
Button(onClick = ::downloadPhoto) {
Text(text = "Download Photo")
}
}
You will now be able to download the photo to a local file.
Showing the Photo
Add Coil as a dependency of your project in the app build.gradle
file. It will be used to show the downloaded image on the screen.
// Coil
def coil_version = "1.4.0"
implementation "io.coil-kt:coil-compose:$coil_version"
Add the final when
statement to setContent
:
// Show downloaded image
is ImageState.ImageDownloaded -> {
Image(
painter = rememberImagePainter(state.downloadedImageFile),
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
}
When the image has been successfully downloaded and a file destination is provided, the image will now be rendered to the screen 🎉