Building a Camera and Gallery Chooser in Android Using Kotlin and Jetpack Compose
Are you looking to implement a feature in your Android app that allows users to choose an image either from the camera or the gallery? In this tutorial, we’ll guide you through creating a Camera and Gallery Chooser using Kotlin and Jetpack Compose.
Prerequisites
Before we begin, ensure you have the following:
- Android Studio installed on your machine.
- Basic knowledge of Android development.
Setting Up Permissions and Dependencies
First, let’s set up the necessary permissions and dependencies in your AndroidManifest.xml
file.
AndroidManifest.xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<queries>
<intent>
<action android:name="android.media.action.IMAGE_CAPTURE" />
</intent>
<intent>
<action android:name="android.intent.action.PICK" />
<data android:mimeType="image/*" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
</intent>
</queries>
<application
android:requestLegacyExternalStorage="true"
...>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
file_paths.xml
Create a new XML file named file_paths.xml
in the res/xml
directory.
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="my_images" path="Pictures" />
</paths>
Implementing ImageUtils
Now, let’s create a utility class, ImageUtils
, to handle image-related operations.
ImageUtils.kt
class ImageUtils(private val context: Context) {
var currentPhotoPath: String? = null
private fun createImageFile(): File {
val timestamp = System.currentTimeMillis()
val storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File.createTempFile(
"JPEG_${timestamp}_",
".jpg",
storageDir
).apply {
currentPhotoPath = absolutePath
}
}
private fun getImageCaptureIntent() = Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
takePictureIntent.resolveActivity(context.packageManager)?.also {
val photoFile: File? = try {
createImageFile()
} catch (ex: Exception) {
null
}
photoFile?.also {
val photoURI = FileProvider.getUriForFile(
context,
"${context.packageName}.fileprovider",
it
)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
}
}
}
private fun getGalleryIntent() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && SdkExtensions.getExtensionVersion(Build.VERSION_CODES.R) >= 2) {
Intent(MediaStore.ACTION_PICK_IMAGES)
} else {
Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
}
fun getPathFromGalleryUri(uri: Uri): String? {
val projection = arrayOf(MediaStore.Images.Media.DATA)
val cursor = context.contentResolver.query(uri, projection, null, null, null)
cursor?.moveToFirst()
val columnIndex = cursor?.getColumnIndex(projection[0])
val path = columnIndex?.let { cursor.getString(it) }
cursor?.close()
return path
}
fun getIntent(): Intent {
val captureIntent = getImageCaptureIntent()
val galleryIntent = getGalleryIntent()
val chooserIntent = Intent(Intent.ACTION_CHOOSER).apply {
putExtra(Intent.EXTRA_INTENT, galleryIntent)
putExtra(Intent.EXTRA_TITLE, "Select from:")
putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(captureIntent))
}
return chooserIntent
}
}
Utilizing the Chooser in Jetpack Compose
Now, let’s integrate our ImageUtils
into a Jetpack Compose UI.
Your Composable
@Composable
fun CameraGalleryChooser() {
val context = LocalContext.current
val imageUtils = ImageUtils(context)
var currentPhoto by remember {
mutableStateOf<String?>(null)
}
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
val data = it.data?.data
currentPhoto = if (data == null) {
// Camera intent
imageUtils.currentPhotoPath
} else {
// Gallery Pick Intent
imageUtils.getPathFromGalleryUri(data)
}
}
}
Column {
Button(
onClick = { launcher.launch(imageUtils.getIntent()) }
) {
Text(text = "Choose or take image")
}
if (currentPhoto != null) {
// You can also use a normal Image by decoding the currentPhoto to a Bitmap using the BitmapFactory
GlideImage(
model = currentPhoto,
contentDescription = null,
modifier = Modifier
.size(100.dp)
.padding(16.dp)
)
}
}
}
@Composable
@Preview(showBackground = true)
fun CameraGalleryChooserPreview() {
CameraGalleryChooser()
}
Feel free to customize the UI and integrate the selected image into your application according to your specific requirements. Happy coding!