How to Make Your Own Android Camera App without Knowing How
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, and already have Android Studio setup you should be able to follow along.
I didn’t include a detailed description of the code included in this post in order to keep the 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 the device camera feature with minimum worries about compatibility.
If you are new to Android development, AndroidX (Jetpack)generally (in my own words) is a set of libraries that enable developers to use new features without worrying too much about support for older versions of Android OS. It was 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:
- Android Studio 3.6+
2. Beginner-level knowledge of Android Development
3. An 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.
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 of 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 your camera app, add the CameraX library to your 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
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 the project directory in /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, let's design the Graphical User Interface (GUI) for our Camera App. The GUI will contain a CameraX Preview window and a shoot button. It looks something like this:
So, let's write the code for the GUI. Open the activity_main.xml from your project’s layout folder 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 to it as that might be very important if you plan to show off your app to friends :) or you’re serious about making a kickass Camera app :D.
Coding the Camera Logic
In the last section, we wrote all the code required to add CameraX to our layout file. Now let's write the code that controls how the actual app works (the app logic).
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 get that the code above is a declaration for fields. We’ll use these fields throughout our Activity Class.
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 reads, 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 on a user’s device.
We‘ll need to add code to request/verify that the necessary permissions have been granted before calling startCamera(). To do that add 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 the 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 a physical Android device.
Conclusion
We’ve been able to walk through creating a new Android Studio project, added CameraX to the project, and 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 exact copy of the sample app in the CameraX codelab.
To make the app truly yours, feel free to customize 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.
Thank you for reading. If you found this post helpful, give me a clap. If you want to learn Android development from a developer, consider enrolling in my instructor-led training at https://buildbrothers.com/training/.
Need to hire an affordable Android App Development Agency? Send me an email at info@buildbrothers.com