Kotlin Android Amazon S3 – upload/download files (images)

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.

amazon-s3-storage-enable

Choose Store user data and click on Save button:

amazon-s3-storage-enable-save

3. Updated latest cloud configuration file

Return to the project details page, click on Integrate button:

amazon-s3-storage-integrate

Download new Cloud Config file, then override it in <project>/app/src/main/res/raw:

amazon-s3-storage-upadte-config-file

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.

springboot amazon s3 starter - choose user

Input User name, choose Programmatic access for Access type:

amazon s3 starter - add user info

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:

amazon s3 starter - add policies

Press Next: Review:

amazon s3 starter - review policies

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:

kotlin-android-amazon-s3-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

amazon-s3-storage-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="grokonez.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.

amazon-s3-storage-demo

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:

amazon-s3-storage-bucket-result-1

We will see jsaS3 folder:

amazon-s3-storage-bucket-result-2

And our files have been uploaded:

amazon-s3-storage-bucket-result-3

IV. Sourcecode

KotlinS3Amazon



By grokonez | January 28, 2018.

Last updated on April 4, 2021.



Related Posts


Got Something To Say:

Your email address will not be published. Required fields are marked *

*