Create an Android Camera App using Kotlin

Create an Android Camera App using Kotlin

The Android Framework provides us with support for taking images and videos using the built-in camera app. But what if you want to develop your own without writing to much boilerplate code?

Today we are going to do exactly that using the Fotoapparat library which provides an easy way to interact with the Android camera while still maintaining a powerful collection of parameter customization options.

The project we will develop

We will create a simple camera app which lets the user take photos, switch from the back to the front camera and vice versa and control the flash. It will also provide the user with a preview of the camera using the CameraView which is included in the Fotoapparat library.

So, without wasting any further time, let’s get started.

Importing the dependencies

The first thing we need to do is importing the needed dependencies for our app. We can do so by adding a few lines of code to our build.gradle (Module:app) file.

dependencies {
    //Fotoapparat
    implementation 'io.fotoapparat:fotoapparat:2.6.1'
    
    //Material Design
    implementation 'com.google.android.material:material:1.0.0-rc01'
}

Creating the UI

Now, we need to build the user interface of our camera app. It consists of a simple layout with 3 FloatingActionButtons which will be used to take an image, switch cameras and control the flash. We will also include a CameraView which will display a preview of the camera.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"
        tools:context=".MainActivity"
        android:background="@android:color/black">

    <io.fotoapparat.view.CameraView
            android:id="@+id/camera_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_above="@id/fab_camera"/>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab_camera"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:fabSize="normal"
        android:src="@drawable/ic_camera"
        android:layout_alignParentBottom="true"
        android:layout_margin="32dp"
        android:layout_centerHorizontal="true"
        app:backgroundTint="@android:color/white"/>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fab_flash"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:fabSize="normal"
            android:src="@drawable/ic_flash"
            android:layout_alignParentBottom="true"
            android:layout_margin="32dp"
            app:backgroundTint="@android:color/white"/>

    <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fab_switch_camera"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:fabSize="normal"
            android:src="@drawable/ic_switch_camera"
            android:layout_alignParentBottom="true"
            android:layout_margin="32dp"
            android:layout_alignParentRight="true"
            app:backgroundTint="@android:color/white"/>

</RelativeLayout>

Requesting the required permissions

After creating the UI we can almost start using the Fotoapparat to build our app. But first, we need to request the required permissions to use the camera and access the local storage. We can do that with some simple lines of code in our AndroidManifest.xml file.

<uses-feature android:name="android.hardware.camera" android:required="true" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

We also need to check if the user has really enabled the permissions and request them at runtime if he hasn’t. We can do so in our MainActivity.kt file.

val permissions = arrayOf(android.Manifest.permission.CAMERA, android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.Manifest.permission.READ_EXTERNAL_STORAGE) 

private fun hasNoPermissions(): Boolean{
    return ContextCompat.checkSelfPermission(this,
        Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this,
        Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this,
        Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
}

fun requestPermission(){
    ActivityCompat.requestPermissions(this, permissions,0)
}

Taking an image

Adding OnClickListeners

First, we need to add OnClickListeners to the buttons to make sure they react to user events.

fab_camera.setOnClickListener {
    takePhoto()
}

fab_switch_camera.setOnClickListener {
    switchCamera()
}

fab_flash.setOnClickListener {
    changeFlashState()
}

Configuring the Fotoapparat instance

Next, we need to configure our instance of the Fotoapparat class.

var fotoapparat: Fotoapparat? = null

private fun createFotoapparat(){
    val cameraView = findViewById<CameraView>(R.id.camera_view)

    fotoapparat = Fotoapparat(
        context = this,
        view = cameraView,
        scaleType = ScaleType.CenterCrop,
        lensPosition = back(),
        logger = loggers(
            logcat()
        ),
        cameraErrorCallback = { error ->
            println("Recorder errors: $error")
        }
    )
}

override fun onStop() {
    super.onStop()
    fotoapparat?.stop()
}

override fun onStart() {
    super.onStart()
    if (hasNoPermissions()) {
        requestPermission()
    }else{
        fotoapparat?.start()
    }
}

Here we initialize the Fotoapparat instance and define basic parameters like which camera and scale-type to use. We also define the view in which the camera preview will be displayed. After that we start the preview in the onStart() method and stop it in the OnStop().

Taking an image

Next, we need to define a path and filename for our output and start implementing the image taking functionality.

val filename = "test.png"
val sd = Environment.getExternalStorageDirectory()
val dest = File(sd, filename)

private fun takePhoto() {
   if (hasNoPermissions()) {
       requestPermission()
   }else{
       fotoapparat
           ?.takePicture()
           ?.saveToFile(dest)
   }
}

Here we check if we have all the required permission. If not we request the remaining ones. After we have all the right permission we take an image and save it in the local storage path we defined above.

Switching cameras

Managing States

Now we can start managing the states of our Fotoapparat to make sure we have all the information needed for performing camera events like switching from the back to the front camera.

Note: The state of the Fotoapparat is only used to make sure that the activity is reloaded when we start the app and request the permissions for the first time so the user sees the preview.

var fotoapparatState : FotoapparatState? = null
var cameraStatus : CameraState? = null
var flashState: FlashState? = null


override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    createFotoapparat()

    cameraStatus = CameraState.BACK
    flashState = FlashState.OFF
    fotoapparatState = FotoapparatState.OFF

}

override fun onStart() {
    super.onStart()
    if (hasNoPermissions()) {
        requestPermission()
    }else{
        fotoapparat?.start()
        fotoapparatState = FotoapparatState.ON
    }
}

override fun onStop() {
    super.onStop()
    fotoapparat?.stop()
    FotoapparatState.OFF
}

override fun onResume() {
    super.onResume()
    if(!hasNoPermissions() && fotoapparatState == FotoapparatState.OFF){
        val intent = Intent(baseContext, MainActivity::class.java)
        startActivity(intent)
        finish()
    }
}

enum class CameraState{
    FRONT, BACK
}

enum class FlashState{
    TORCH, OFF
}

enum class FotoapparatState{
    ON, OFF
}

Here we use enum classes to manage our states to make sure we know exactly what we should do when the switchCamera or the changeFlashState methods are called.

Switching Cameras

After checking the state we can continue working on our camera features.

private fun switchCamera() {
    fotoapparat?.switchTo(
        lensPosition =  if (cameraStatus == CameraState.BACK) front() else back(),
        cameraConfiguration = CameraConfiguration()
    )

    if(cameraStatus == CameraState.BACK) cameraStatus = CameraState.FRONT
    else cameraStatus = CameraState.BACK
}

Here we implemented the switch camera functionality using the state we defined above and the switchTo function provided by the Fotoapparat library. To switch the camera, we need to pass the camera we want and our camera configuration (We pass an empty instance for the standard configuration).

Turning the flash on

The changeFlashState method is very similar to the switchCamera one. We are working with the flashState we defined above and just invert it when the changeFlashState method is called.

private fun changeFlashState() {
    fotoapparat?.updateConfiguration(
        CameraConfiguration(
            flashMode = if(flashState == FlashState.TORCH) off() else torch()
        )
    )

    if(flashState == FlashState.TORCH) flashState = FlashState.OFF
    else flashState = FlashState.TORCH
}

After that, we change the flashState to match the current flash condition.

Complete Source Code for the MainActivity.kt

Here you can get the complete source code for our basic camera app:

package com.example.videorecorder


import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Environment
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import io.fotoapparat.Fotoapparat
import io.fotoapparat.configuration.CameraConfiguration
import io.fotoapparat.log.logcat
import io.fotoapparat.log.loggers
import io.fotoapparat.parameter.ScaleType
import io.fotoapparat.selector.*
import io.fotoapparat.view.CameraView
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File



class MainActivity : AppCompatActivity() {

    var fotoapparat: Fotoapparat? = null
    val filename = "test.png"
    val sd = Environment.getExternalStorageDirectory()
    val dest = File(sd, filename)
    var fotoapparatState : FotoapparatState? = null
    var cameraStatus : CameraState? = null
    var flashState: FlashState? = null

    val permissions = arrayOf(android.Manifest.permission.CAMERA, android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.Manifest.permission.READ_EXTERNAL_STORAGE)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        createFotoapparat()

        cameraStatus = CameraState.BACK
        flashState = FlashState.OFF
        fotoapparatState = FotoapparatState.OFF

        fab_camera.setOnClickListener {
            takePhoto()
        }

        fab_switch_camera.setOnClickListener {
            switchCamera()
        }

        fab_flash.setOnClickListener {
            changeFlashState()
        }
    }

    private fun createFotoapparat(){
        val cameraView = findViewById<CameraView>(R.id.camera_view)

        fotoapparat = Fotoapparat(
            context = this,
            view = cameraView,
            scaleType = ScaleType.CenterCrop,
            lensPosition = back(),
            logger = loggers(
                logcat()
            ),
            cameraErrorCallback = { error ->
                println("Recorder errors: $error")
            }
        )
    }

    private fun changeFlashState() {
        fotoapparat?.updateConfiguration(
            CameraConfiguration(
                flashMode = if(flashState == FlashState.TORCH) off() else torch()
            )
        )

        if(flashState == FlashState.TORCH) flashState = FlashState.OFF
        else flashState = FlashState.TORCH
    }

    private fun switchCamera() {
        fotoapparat?.switchTo(
            lensPosition =  if (cameraStatus == CameraState.BACK) front() else back(),
            cameraConfiguration = CameraConfiguration()
        )

        if(cameraStatus == CameraState.BACK) cameraStatus = CameraState.FRONT
        else cameraStatus = CameraState.BACK
    }

    private fun takePhoto() {
       if (hasNoPermissions()) {
           requestPermission()
       }else{
           fotoapparat
               ?.takePicture()
               ?.saveToFile(dest)
       }
    }

    override fun onStart() {
        super.onStart()
        if (hasNoPermissions()) {
            requestPermission()
        }else{
            fotoapparat?.start()
            fotoapparatState = FotoapparatState.ON
        }
    }

    private fun hasNoPermissions(): Boolean{
        return ContextCompat.checkSelfPermission(this,
            Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this,
            Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this,
            Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
    }

    fun requestPermission(){
        ActivityCompat.requestPermissions(this, permissions,0)
    }

    override fun onStop() {
        super.onStop()
        fotoapparat?.stop()
        FotoapparatState.OFF
    }

    override fun onResume() {
        super.onResume()
        if(!hasNoPermissions() && fotoapparatState == FotoapparatState.OFF){
            val intent = Intent(baseContext, MainActivity::class.java)
            startActivity(intent)
            finish()
        }
    }

}

enum class CameraState{
    FRONT, BACK
}

enum class FlashState{
    TORCH, OFF
}

enum class FotoapparatState{
    ON, OFF
}

Looking up the image

After taking an image you need to go into the local storage of your Android device to look at it. I’m just including it for the people who aren’t confident navigating in the local Android storage.

  1. Open the files app on your Android device
  2. Go to the local register
  3. Search for test.png

Closing Notes

So, by now you should have a pretty good idea how you can develop your own camera app using the Fotoapparat library. You have also got a good idea of how to request permission in the runtime.

The full source code of this project can be found on GitHub.

If you have found this useful, please consider recommending and sharing it with other fellow developers.

Read these next: