Kotlin Android Amazon S3 – upload/download files (images)
Amazon Simple Storage Service (Amazon S3) is object storage built to store and retrieve any amount of data from web or mobile. Amazon S3 is designed to make web-scale computing easier for developers. In this tutorial, we’re gonna create an Android App that can upload/download files (images) to/from Amazon S3 with Kotlin language.
Related Post: How to integrate AWS Mobile SDK into Android App
I. Technology
– Android Studio 2.x
– AWS Mobile SDK Client 2.6.7
II. Data Storage with Amazon S3
1. Integrate AWS Mobile SDK into Android App
Please visit this article for details.
2. Enable User Data Storage
Open your project in Mobile Hub and choose the User Data Storage tile to enable the feature.
Choose Store user data and click on Save button:
3. Updated latest cloud configuration file
Return to the project details page, click on Integrate button:
Download new Cloud Config file, then override it in <project>/app/src/main/res/raw:
4. Create an IAM user
We need to provide access permission mobile bucket. So follow these step to create an IAM user and get Access key ID and Secret access key:
Go to https://console.aws.amazon.com/iam/
In the navigation pane, choose Users and then choose Add user.
Input User name, choose Programmatic access for Access type:
Press Next: Permissions button -> go to Set permissions for jsa-user screen.
Now, choose Attach existing policies directly -> filter policy type s3, then check AmazonS3FullAccess:
Press Next: Review:
Press Create user:
Press Download .csv for {Access key ID, Secret access key}.
5. Connect to Amazon S3
5.1 Add dependencies
Open app/build.gradle, add:
dependencies { ... implementation ('com.amazonaws:aws-android-sdk-mobile-client:2.6.7@aar') { transitive = true } implementation 'com.amazonaws:aws-android-sdk-s3:2.6.+' implementation 'com.amazonaws:aws-android-sdk-cognito:2.6.+' }
5.2 Add TransferService
Open AndroidManifest.xml, add service
and set permission for read/write file:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.javasampleapproach.s3amazon"> <application ...> ... <activity android:name=".MainActivity"> ... </activity> <service android:name="com.amazonaws.mobileconnectors.s3.transferutility.TransferService" android:enabled="true" /> </application> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> </manifest>
5.3 Establish connection with AWS Mobile
AWSMobileClient
is a singleton that will be an interface for AWS services. In onCreate()
method, initialize it:
import com.amazonaws.services.s3.AmazonS3Client class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) AWSMobileClient.getInstance().initialize(this).execute() } }
5.4 Upload a File
// KEY and SECRET are gotten when we create an IAM user above credentials = BasicAWSCredentials(KEY, SECRET) s3Client = AmazonS3Client(credentials) val transferUtility = TransferUtility.builder() .context(applicationContext) .awsConfiguration(AWSMobileClient.getInstance().configuration) .s3Client(s3Client) .build() // "jsaS3" will be the folder that contains the file val uploadObserver = transferUtility.upload("jsaS3/" + fileName, file) uploadObserver.setTransferListener(object : TransferListener { override fun onStateChanged(id: Int, state: TransferState) { if (TransferState.COMPLETED == state) { // Handle a completed upload. } } override fun onProgressChanged(id: Int, bytesCurrent: Long, bytesTotal: Long) { val percentDonef = bytesCurrent.toFloat() / bytesTotal.toFloat() * 100 val percentDone = percentDonef.toInt() } override fun onError(id: Int, ex: Exception) { // Handle errors } }) // If your upload does not trigger the onStateChanged method inside your // TransferListener, you can directly check the transfer state as shown here. if (TransferState.COMPLETED == uploadObserver.state) { // Handle a completed upload. }
5.5 Download a File
// KEY and SECRET are gotten when we create an IAM user above credentials = BasicAWSCredentials(KEY, SECRET) s3Client = AmazonS3Client(credentials) val transferUtility = TransferUtility.builder() .context(applicationContext) .awsConfiguration(AWSMobileClient.getInstance().configuration) .s3Client(s3Client) .build() val downloadObserver = transferUtility.download("jsaS3/" + fileName, localFile) downloadObserver.setTransferListener(object : TransferListener { override fun onStateChanged(id: Int, state: TransferState) { if (TransferState.COMPLETED == state) { // Handle a completed download. } } override fun onProgressChanged(id: Int, bytesCurrent: Long, bytesTotal: Long) { val percentDonef = bytesCurrent.toFloat() / bytesTotal.toFloat() * 100 val percentDone = percentDonef.toInt() } override fun onError(id: Int, ex: Exception) { // Handle errors } }
III. Practice
1. Set up Project
Follow instruction above to:
– Integrate AWS Mobile SDK into Android App
– Enable User Data Storage
– Updated latest cloud configuration file
– Create an IAM user and get {Access key ID, Secret access key}
Project Structure:
2. Dependencies
Open app/build.gradle, add:
dependencies { ... implementation ('com.amazonaws:aws-android-sdk-mobile-client:2.6.7@aar') { transitive = true } implementation 'com.amazonaws:aws-android-sdk-s3:2.6.+' implementation 'com.amazonaws:aws-android-sdk-cognito:2.6.+' }
3. AndroidManifest.xml
Open AndroidManifest.xml, add service
and set permission for network, read/write file:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.javasampleapproach.s3amazon"> <application ...> ... <activity android:name=".MainActivity"> ... </activity> <service android:name="com.amazonaws.mobileconnectors.s3.transferutility.TransferService" android:enabled="true" /> </application> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> </manifest>
4. Layout
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingLeft="16dp" android:paddingRight="16dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="ozenero.com" android:textSize="20sp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:weightSum="3"> <Button android:id="@+id/btn_choose_file" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Choose File" /> <EditText android:id="@+id/edt_file_name" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" android:hint="File Name" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:weightSum="2"> <Button android:id="@+id/btn_upload" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Upload" /> <Button android:id="@+id/btn_download" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Download" /> </LinearLayout> <TextView android:id="@+id/tv_file_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_margin="5dp" android:text="File Name" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:weightSum="5"> <ImageView android:id="@+id/img_file" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="4.7" /> </LinearLayout> </LinearLayout>
5. Main Code
MainActivity.java
package com.javasampleapproach.kotlin.s3amazon import android.app.Activity import android.content.Context import android.content.Intent import android.graphics.Bitmap import android.graphics.BitmapFactory import android.net.Uri import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.os.Environment import android.provider.MediaStore import android.text.TextUtils import android.view.View import android.webkit.MimeTypeMap import android.widget.Toast import com.amazonaws.auth.BasicAWSCredentials import com.amazonaws.mobile.client.AWSMobileClient import com.amazonaws.mobileconnectors.s3.transferutility.TransferListener import com.amazonaws.mobileconnectors.s3.transferutility.TransferState import com.amazonaws.mobileconnectors.s3.transferutility.TransferUtility import com.amazonaws.services.s3.AmazonS3Client import com.amazonaws.util.IOUtils import kotlinx.android.synthetic.main.activity_main.* import java.io.File import java.io.FileOutputStream import java.io.IOException class MainActivity : AppCompatActivity(), View.OnClickListener { private val KEY = "xxx" private val SECRET = "xxxxxx" private var s3Client: AmazonS3Client? = null private var credentials: BasicAWSCredentials? = null //track Choosing Image Intent private val CHOOSING_IMAGE_REQUEST = 1234 private var fileUri: Uri? = null private var bitmap: Bitmap? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) tv_file_name.text = "" btn_choose_file.setOnClickListener(this) btn_upload.setOnClickListener(this) btn_download.setOnClickListener(this) AWSMobileClient.getInstance().initialize(this).execute() credentials = BasicAWSCredentials(KEY, SECRET) s3Client = AmazonS3Client(credentials) } private fun uploadFile() { if (fileUri != null) { val fileName = edt_file_name.text.toString() if (!validateInputFileName(fileName)) { return } val file = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "/" + fileName) createFile(applicationContext, fileUri!!, file) val transferUtility = TransferUtility.builder() .context(applicationContext) .awsConfiguration(AWSMobileClient.getInstance().configuration) .s3Client(s3Client) .build() val uploadObserver = transferUtility.upload("jsaS3/" + fileName + "." + getFileExtension(fileUri), file) uploadObserver.setTransferListener(object : TransferListener { override fun onStateChanged(id: Int, state: TransferState) { if (TransferState.COMPLETED == state) { Toast.makeText(applicationContext, "Upload Completed!", Toast.LENGTH_SHORT).show() file.delete() } else if (TransferState.FAILED == state) { file.delete() } } override fun onProgressChanged(id: Int, bytesCurrent: Long, bytesTotal: Long) { val percentDonef = bytesCurrent.toFloat() / bytesTotal.toFloat() * 100 val percentDone = percentDonef.toInt() tv_file_name.text = "ID:$id|bytesCurrent: $bytesCurrent|bytesTotal: $bytesTotal|$percentDone%" } override fun onError(id: Int, ex: Exception) { ex.printStackTrace() } }) } } private fun downloadFile() { if (fileUri != null) { val fileName = edt_file_name.text.toString() if (!validateInputFileName(fileName)) { return } try { val localFile = File.createTempFile("images", getFileExtension(fileUri)) val transferUtility = TransferUtility.builder() .context(applicationContext) .awsConfiguration(AWSMobileClient.getInstance().configuration) .s3Client(s3Client) .build() val downloadObserver = transferUtility.download("jsaS3/" + fileName + "." + getFileExtension(fileUri), localFile) downloadObserver.setTransferListener(object : TransferListener { override fun onStateChanged(id: Int, state: TransferState) { if (TransferState.COMPLETED == state) { Toast.makeText(applicationContext, "Download Completed!", Toast.LENGTH_SHORT).show() tv_file_name.text = fileName + "." + getFileExtension(fileUri) val bmp = BitmapFactory.decodeFile(localFile.absolutePath) img_file.setImageBitmap(bmp) } } override fun onProgressChanged(id: Int, bytesCurrent: Long, bytesTotal: Long) { val percentDonef = bytesCurrent.toFloat() / bytesTotal.toFloat() * 100 val percentDone = percentDonef.toInt() tv_file_name.text = "ID:$id|bytesCurrent: $bytesCurrent|bytesTotal: $bytesTotal|$percentDone%" } override fun onError(id: Int, ex: Exception) { ex.printStackTrace() } }) } catch (e: IOException) { e.printStackTrace() } } else { Toast.makeText(this, "Upload file before downloading", Toast.LENGTH_LONG).show() } } override fun onClick(view: View) { val i = view.id if (i == R.id.btn_choose_file) { showChoosingFile() } else if (i == R.id.btn_upload) { uploadFile() } else if (i == R.id.btn_download) { downloadFile() } } private fun showChoosingFile() { val intent = Intent() intent.type = "image/*" intent.action = Intent.ACTION_GET_CONTENT startActivityForResult(Intent.createChooser(intent, "Select Image"), CHOOSING_IMAGE_REQUEST) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) bitmap?.recycle() if (requestCode == CHOOSING_IMAGE_REQUEST && resultCode == Activity.RESULT_OK && data != null && data.data != null) { fileUri = data.data try { bitmap = MediaStore.Images.Media.getBitmap(contentResolver, fileUri) } catch (e: IOException) { e.printStackTrace() } } } private fun getFileExtension(uri: Uri?): String { val contentResolver = contentResolver val mime = MimeTypeMap.getSingleton() return mime.getExtensionFromMimeType(contentResolver.getType(uri)) } private fun validateInputFileName(fileName: String): Boolean { if (TextUtils.isEmpty(fileName)) { Toast.makeText(this, "Enter file name!", Toast.LENGTH_SHORT).show() return false } return true } private fun createFile(context: Context, srcUri: Uri?, dstFile: File) { try { val inputStream = context.contentResolver.openInputStream(srcUri) ?: return val outputStream = FileOutputStream(dstFile) IOUtils.copy(inputStream, outputStream) inputStream.close() outputStream.close() } catch (e: IOException) { e.printStackTrace() } } }
6. Check Results
Run the Android App, click on CHOOSE FILE button, select image file and set file name in the blank. Then check UPLOAD and DOWNLOAD.
Go to https://console.aws.amazon.com/s3/buckets/
, click on xxx-userfiles-mobilehub-xxx bucket (which is automatically generated when we enable User Data Storage:
We will see jsaS3 folder:
And our files have been uploaded: