Access Camera and Gallery in a Jetpack Compose Android App
Hello Buddy’s
In this blog, we’ll explore how to access images from the gallery and use the system camera to take pictures in an Android application using Kotlin. This tutorial builds upon a previous example where we demonstrated creating a bottom navigation with a FAB (Floating Action Button). We’ll integrate camera and gallery access into this application.
Adding Permissions
First, we need to add the necessary permissions in the AndroidManifest.xml
file. These permissions allow the app to access the camera and the gallery. and we’ll add the content provider authorities and file path to store the image in the content provider.
<manifest ...>
<application ...>
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
</manifest>
Create a file_paths.xml
file in the res/xml
directory:
<paths>
<external-files-path name="external_files" path="." />
</paths>
Creating the Bottom Sheet
Let’s create a new Kotlin file named BottomSheets.kt
where we'll define our bottom sheet for selecting camera or gallery options.
package com.example.app
import android.content.Context
import android.net.Uri
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ModalBottomSheetLayout
import androidx.compose.runtime.*
import androidx.compose.ui.res.painterResource
import androidx.core.content.FileProvider
import java.io.File
@Composable
fun CameraBottomSheet(
context: Context,
sheetState: ModalBottomSheetLayout,
onDismiss: () -> Unit,
onAction: (String) -> Unit
) {
var imageUri by remember { mutableStateOf<Uri?>(null) }
val cameraPermission = remember { mutableStateOf(false) }
Column {
Row {
IconButton(onClick = {
if (cameraPermission.value) {
imageUri = createImageFile(context)
onAction(imageUri.toString())
}
}) {
Icon(painter = painterResource(R.drawable.ic_camera), contentDescription = "Camera")
}
IconButton(onClick = {
// Launch gallery picker
}) {
Icon(painter = painterResource(R.drawable.ic_gallery), contentDescription = "Gallery")
}
}
}
}
private fun createImageFile(context: Context): Uri {
val storageDir: File? = context.getExternalFilesDir(null)
val imageFile = File.createTempFile("JPEG_${System.currentTimeMillis()}_", ".jpg", storageDir)
return FileProvider.getUriForFile(context, "${context.packageName}.provider", imageFile)
}
Implementing Camera and Gallery Launchers
In the same BottomSheets.kt
file, we'll add functionality to launch the camera and gallery.
import android.content.Intent
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
@Composable
fun CameraBottomSheet(
context: Context,
sheetState: ModalBottomSheetLayout,
onDismiss: () -> Unit,
onAction: (String) -> Unit
) {
var imageUri by remember { mutableStateOf<Uri?>(null) }
val cameraPermission = remember { mutableStateOf(false) }
val galleryLauncher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
uri?.let { onAction(it.toString()) }
}
val cameraLauncher = rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) { success ->
if (success) {
imageUri?.let { onAction(it.toString()) }
}
}
Column {
Row {
IconButton(onClick = {
if (cameraPermission.value) {
imageUri = createImageFile(context)
cameraLauncher.launch(imageUri)
}
}) {
Icon(painter = painterResource(R.drawable.ic_camera), contentDescription = "Camera")
}
IconButton(onClick = {
galleryLauncher.launch("image/*")
}) {
Icon(painter = painterResource(R.drawable.ic_gallery), contentDescription = "Gallery")
}
}
}
}
Adding the Bottom Sheet to the Main Activity
Now, let’s integrate the bottom sheet into the main activity.
package com.example.app
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.ModalBottomSheetLayout
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val context = LocalContext.current
var openBottomSheet by remember { mutableStateOf(false) }
val sheetState = rememberModalBottomSheetState()
if (openBottomSheet) {
ModalBottomSheetLayout(
sheetState = sheetState,
onDismissRequest = { openBottomSheet = false }
) {
CameraBottomSheet(
context = context,
sheetState = sheetState,
onDismiss = { openBottomSheet = false },
onAction = { imagePath ->
// Handle the captured image path
}
)
}
}
}
}
}
Handling Permissions
To handle permissions gracefully, add the following dependencies to your build.gradle
file:
dependencies {
implementation "com.google.accompanist:accompanist-permissions:0.24.7-alpha"
}
Then, request camera permissions in your composable function.
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberPermissionState
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun CameraBottomSheet(
context: Context,
sheetState: ModalBottomSheetLayout,
onDismiss: () -> Unit,
onAction: (String) -> Unit
) {
var imageUri by remember { mutableStateOf<Uri?>(null) }
val cameraPermission = rememberPermissionState(android.Manifest.permission.CAMERA)
if (!cameraPermission.status.isGranted) {
LaunchedEffect(cameraPermission) {
cameraPermission.launchPermissionRequest()
}
}
// Rest of the composable function
}
Conclusion
In this blog, we’ve implemented a bottom sheet in a Kotlin Android app that allows users to access the camera and gallery. We’ve also handled necessary permissions and file storage using a content provider. This approach ensures that the app remains secure and functional across different Android versions.
Feel free to customize the UI and add additional functionality as needed. Happy coding!