I'm using Jetpack Compose, and when I call the method to take a picture with the camera, the result of ActivityResultContracts.TakePicture
is always false.
Sample code:
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun SomeScreen() {
val photoUri by remember { mutableStateOf(value = Uri.EMPTY) }
val cameraLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.TakePicture(),
onResult = { success ->
if (success) {
println("success")
println("photo uri: $photoUri")
} else println("result failed")
}
)
val cameraPermissionState = rememberPermissionState(
permission = Manifest.permission.CAMERA,
onPermissionResult = { granted ->
if (granted) cameraLauncher.launch(photoUri)
else print("camera permission is denied")
}
)
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Button(onClick = cameraPermissionState::launchPermissionRequest) {
Text(text = "Take a photo with Camera")
}
}
}
I used the accompanist-permissions
library to make it easier, the part of opening the camera app and taking the photo is apparently working normally, but the result from cameraLauncher
is always false...
Can anyone guide me to solve this problem?
The problem with your code is that you are passing an empty Uri
to launch
, and the Camera app can't save the image in that Uri
. If you open the TakePicture
class or place the mouse cursor over it, you will see the following information:
An ActivityResultContract to take a picture saving it into the provided content-Uri. Returns true if the image was saved into the given Uri.
In other words, the TakePicture
class will not automatically create a File
for you, you will have to create a File
yourself and provide the Uri
.
I'll assume a simple scenario where you want to take a photo for some temporary task within the app. To achieve this goal, you need to understand some steps that are missing in your code:
File
and expose it to the Camera app, you need to create rules with the File Provider and declare it in the Manifest
file.File
and exposes its Uri
with the FileProvider
.Lets start with file_paths.xml
(inside res/xml):
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path
name="cache_pictures"
path="/" />
</paths>
I'm using cache-path
here following the idea of keeping the files temporarily.
In the Manifest
file:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA" />
<application ...>
<activity .../>
<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>
</manifest>
Extension to create a File
and return a Uri
:
fun Context.createTempPictureUri(
provider: String = "${BuildConfig.APPLICATION_ID}.provider",
fileName: String = "picture_${System.currentTimeMillis()}",
fileExtension: String = ".png"
): Uri {
val tempFile = File.createTempFile(
fileName, fileExtension, cacheDir
).apply {
createNewFile()
}
return FileProvider.getUriForFile(applicationContext, provider, tempFile)
}
The cache folder is being used in this example with cacheDir
. If changing here to filesDir
, be sure to change in the cache-path
on file_paths.xml
to files-path
.
Now in Composable Screen
you can have something like this:
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun SomeScreen() {
val context = LocalContext.current
var currentPhotoUri by remember { mutableStateOf(value = Uri.EMPTY) }
var tempPhotoUri by remember { mutableStateOf(value = Uri.EMPTY) }
val cameraLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.TakePicture(),
onResult = { success ->
if (success) currentPhotoUri = tempPhotoUri
}
)
val cameraPermissionState = rememberPermissionState(
permission = Manifest.permission.CAMERA,
onPermissionResult = { granted ->
if (granted) {
tempPhotoUri = context.createTempPictureUri()
cameraLauncher.launch(tempPhotoUri)
} else print("camera permission is denied")
}
)
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
AnimatedVisibility(visible = currentPhotoUri.toString().isNotEmpty()) {
// from coil library
AsyncImage(
modifier = Modifier.size(size = 240.dp),
model = currentPhotoUri,
contentDescription = null
)
}
Button(onClick = cameraPermissionState::launchPermissionRequest) {
Text(text = "Take a photo with Camera")
}
}
}