How to Make Your Own Android Camera App without Knowing How

Pius Aboyi
7 min readNov 27, 2020

--

Weird title? Yeah, you won’t be learning how-to do x if you already know how.

This post is inspired by a recent attempt I made to build an app for scanning texts (characters) in images. I am kind of an advocate of learning by doing so I decided to put up a tutorial for any Android beginners looking to build a simple app that does some very common tasks.

If you are learning Android development, already have Android Studio setup you should be able to follow along.

I didn’t include detailed description of the code included in this post in other to keep to post short. Feel free to check the codelab linked in the post to learn more about the code.

Getting Started

CameraX is a member of the Jetpack library family that helps android developers use device camera feature with minimum worries about compatibility. If you are new to Android development.

AndroidX generally (in my own words) is a set of libraries that enable developer use new features without worrying too much about support for older versions of Android OS. Previously known as Support Library.

In this post, you will learn how to build a simple Camera App using Jetpack’s CameraX library.

Pre-requisites

To follow along, you will need the following:

  1. Android Studio 3.6+

2. Beginner level knowledge of Android Development

3. Android Phone or Emulator

Now let's dive into creating the Camera App.

Creating a New Project

Let's create a new project in Android studio. To do this, open Android Studio then click file > new > new project.

Alternatively, you can start a new project from Android Studio’s welcome screen.

Next, follow the new project wizard to complete the process.

new project template

Tip: To follow along better, please select “empty activity” in the project template window.

Hit next when you are done selecting a template to move to the new project configuration window. You can specify the name for your new Camera app, the package name and the location on your computer where the project will be saved.

Tip: I recommend you keep Language, Minimum SDK and the Use legacy android.support.libraries default (do not change the default values).

Next, click Finish and wait for Android Studio to finish setting up your project.

Add CameraX to Project

At this point, we have successfully set up our new project. One last step before we start coding the app, let’s add the Camerax library to our project.

To do that, open the app-level build.gradle file and add the following code to the dependency block:

// CameraX core library using camera2 implementation
implementation "androidx.camera:camera-camera2:$camerax_version"
// CameraX Lifecycle Library
implementation "androidx.camera:camera-lifecycle:$camerax_version"
// CameraX View class
implementation "androidx.camera:camera-view:1.0.0-alpha14"

Still in the app-level build.gradle file, add the following code to the android block just after buildTypes section:

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

Sync Gradle by clicking Sync Now on the top yellow bar.

The rest of this tutorial will include code found on the official codelab for CameraX.

Add Required Permission

In order for your app to use the device camera (hardware), you’ll need to specify in your app’s manifest file that it uses the camera. Add the following code to AndroidManifest.xml (located in project directory/app/src/main):

<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />

The code should be pasted just above the application tag.

Coding the Layout

Now that we have CameraX in our project and the required permission configuration is done, lets code the Graphical User Interface (GUI) for our Camera App. The GUI will contain a CameraX Perview window and a shoot button. It looks something like this:

So, lets write the code. Open the activity_main.xml and paste the following code inside the root constraint layout:

<Button
android:id="@+id/camera_capture_button"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginBottom="50dp"
android:scaleType="fitCenter"
android:text="Take Photo"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:elevation="2dp"
/>

<androidx.camera.view.PreviewView
android:id="@+id/viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>

With that, we are done with our GUI. Feel free to add some more creativity it as that might be very important if you plan to show off your app to friends :).

Coding the Camera Logic

In the last section, we wrote all the code required to add CameraX to our layout file. Now lets write the code that controls how the actual app works.

We’ll write all our code in MainActivity.kt. Open the file and add the following code:

Paste the following code just above the onCreate method:

private var imageCapture: ImageCapture? = null

private lateinit var
outputDirectory: File
private lateinit var cameraExecutor: ExecutorService

If you are familiar with OOP, you might be able to get that the code above is declaration for fields.

Next, move just below the onCreate method and paste the following:

private fun takePhoto() {
// Get a stable reference of the modifiable image capture use case
val imageCapture = imageCapture ?: return

// Create time-stamped output file to hold the image
val photoFile = File(
outputDirectory,
SimpleDateFormat(FILENAME_FORMAT, Locale.US
).format(System.currentTimeMillis()) + ".jpg")

// Create output options object which contains file + metadata
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()

// Set up image capture listener, which is triggered after photo has
// been taken
imageCapture.takePicture(
outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
}

override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val savedUri = Uri.fromFile(photoFile)
val msg = "Photo capture succeeded: $savedUri"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
Log.d(TAG, msg)
}
})
}

The code above adds a takePhoto() method. Just as the name of the method, calling the method will take a photo.

Now paste the following code below the takePhoto() method:

private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

cameraProviderFuture.addListener(Runnable {
// Used to bind the lifecycle of cameras to the lifecycle owner
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

// Preview
val preview = Preview.Builder()
.build()
.also {
it
.setSurfaceProvider(viewFinder.createSurfaceProvider())
}

imageCapture
= ImageCapture.Builder()
.build()

// Select back camera as a default
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

try {
// Unbind use cases before rebinding
cameraProvider.unbindAll()

// Bind use cases to camera
cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageCapture)

} catch(exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}

}, ContextCompat.getMainExecutor(this))
}

The code adds a startCamera method which starts the camera.

We will need to add code to request/verify that the necessary permissions have been granted. Let’s do that by adding the following code below the startCamera() method.

private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(
baseContext, it) == PackageManager.PERMISSION_GRANTED
}

@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private fun getOutputDirectory(): File {
val mediaDir = externalMediaDirs.firstOrNull()?.let {
File(it, resources.getString(R.string.app_name)).apply { mkdirs() } }
return if
(mediaDir != null && mediaDir.exists())
mediaDir else filesDir
}

override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults:
IntArray) {
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
startCamera()
} else {
Toast.makeText(this,
"Permissions not granted by the user.",
Toast.LENGTH_SHORT).show()
finish()
}
}
}

The code above also includes logic for getting storage output directory.

Finally, paste the following code at the end of the last code:

override fun onDestroy() {
super.onDestroy()
cameraExecutor.shutdown()
}

companion object {
private const val TAG = "CameraXBasic"
private const val FILENAME_FORMAT
= "yyyy-MM-dd-HH-mm-ss-SSS"
private const val REQUEST_CODE_PERMISSIONS
= 10
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
}

In onDestroy, we do some cleanup by closing the camera. the full content of MainActivity.kt should look like this:

Running the App

If you got to this point without any errors, Congrats!!!! Hit the green play button in Android Studio to run your app on an emulator or physical android device.

Conclusion

We’ve been able to walk through creating a new Android Studio project, add CameraX to the project and we used CameraX to build a small camera app.

The app doesn’t do much and as it is at the end of this post it’s the exaclt copy of the sample app in the CameraX codelab. To make the app truely yours, feel free to customise the GUI and add some more features. Add features you will love to see in your ideal camera app. That is all from me for now. You can find the official CameraX codelab here

--

--

Pius Aboyi

Web/Mobile Developer(Android), I know PHP, SEO+Digital marketing. Currently growing the tech Eco-system in Benue, Nigeria, via BenueTechForum and GDG Makurdi.