Android: How to take a photo using Kotlin

For my first article in this platform I want to explain how you can take a photo in Android using the camera app and getting the resulting image in your application. All of this using Kotlin as the programming language.

First we need to create a new project in Android Studio. For this example I’m using Android Studio 2.3.3.

Once we have created the new project, we need to set the project so we can start working with Kotlin. You can do so by following the guide in the Kotlin site:

There are a few libraries I’m using in this project in order to simplify some steps, like view binding, displaying images and permission requests:

The next step is to specify in our Android Manifest the permissions that our app is going to require. In this case we are going to need the android.permission.WRITE_EXTERNAL_STORAGE.

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Let’s create the layout for our Main Activity

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.jc.capturephoto.MainActivity">


<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/imgv_photo"
android:layout_width="@dimen/photo_width"
android:layout_height="@dimen/photo_height"
android:layout_marginBottom="8dp"
android:layout_marginLeft="0dp"
android:layout_marginRight="0dp"
android:layout_marginTop="24dp"
app:failureImage="@drawable/ic_image_error"
app:failureImageScaleType="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:placeholderImage="@drawable/ic_image_placeholder"
app:placeholderImageScaleType="center" />

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_capture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
android:clickable="true"
app:backgroundTint="@android:color/white"
app:fabSize="normal"
app:layout_constraintRight_toRightOf="@+id/imgv_photo"
app:srcCompat="@drawable/ic_add_a_photo_black"
app:layout_constraintTop_toTopOf="@+id/imgv_photo"
android:layout_marginTop="172dp" />

</android.support.constraint.ConstraintLayout>

The screen will look like this:

Main Activity

In the MainActivity.kt file, we are going to set a click listener to the Floatting Action Button so we can launch the camera app when the user taps on it.

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
ButterKnife.bind(this)
fabCapturePhoto?.setOnClickListener { validatePermissions() }
}

The validatePermission() method takes care of verify and request the write permissions that the app requires. If the permission is granted it will launch the camera app via an Intent:

private fun validatePermissions() {
Dexter.withActivity(this)
.withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.withListener(object: PermissionListener {
override fun onPermissionGranted(
response: PermissionGrantedResponse?) {
launchCamera()
}

override fun onPermissionRationaleShouldBeShown(
permission: PermissionRequest?,
token: PermissionToken?) {
AlertDialog.Builder(this@MainActivity)
.setTitle(
R.string.storage_permission_rationale_title)
.setMessage(
R.string.storage_permition_rationale_message)
.setNegativeButton(
android.R.string.cancel,
{ dialog, _ ->
dialog.dismiss()
token?.cancelPermissionRequest()
})
.setPositiveButton(android.R.string.ok,
{ dialog, _ ->
dialog.dismiss()
token?.continuePermissionRequest()
})
.setOnDismissListener({
token?.cancelPermissionRequest() })
.show()
}

override fun onPermissionDenied(
response: PermissionDeniedResponse?) {
Snackbar.make(mainContainer!!,
R.string.storage_permission_denied_message,
Snackbar.LENGTH_LONG)
.show()
}
})
.check()
}

The launchCamera() method will create a path that we are going to pass to our intent and then call for the camera app. Also, we need to store the path that we created in a member variable for later use.

private fun launchCamera() {
val values = ContentValues(1)
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpg")
val fileUri = contentResolver
.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
values)
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
if(intent.resolveActivity(packageManager) != null) {
mCurrentPhotoPath = fileUri.toString()
intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
startActivityForResult(intent, TAKE_PHOTO_REQUEST)
}
}

Once we capture the photo, we need to implement onActivityResult() so we can process and display the photo we just captured:

override fun onActivityResult(requestCode: Int, resultCode: Int, 
data: Intent?) {
    if (resultCode == Activity.RESULT_OK 
&& requestCode == TAKE_PHOTO_REQUEST) {
processCapturedPhoto()
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}

Since the original photo is too big, it can lead to an Out Of Memory Error when we try to display it in our app. Fresco offers a simple way to resize the image before we display it.

private fun processCapturedPhoto() {
val cursor = contentResolver.query(Uri.parse(mCurrentPhotoPath),
Array(1) {android.provider.MediaStore.Images.ImageColumns.DATA},
null, null, null)
cursor.moveToFirst()
val photoPath = cursor.getString(0)
cursor.close()
val file = File(photoPath)
val uri = Uri.fromFile(file)

val height = resources.getDimensionPixelSize(R.dimen.photo_height)
val width = resources.getDimensionPixelSize(R.dimen.photo_width)

val request = ImageRequestBuilder.newBuilderWithSource(uri)
.setResizeOptions(ResizeOptions(width, height))
.build()
val controller = Fresco.newDraweeControllerBuilder()
.setOldController(imgvPhoto?.controller)
.setImageRequest(request)
.build()
imgvPhoto?.controller = controller
}

That’s it, now we can take photos and display them inside our app in a very simple way.

You can find the source code for the sample project here: https://github.com/bionicwan/capturephoto