ActivityResultContracts TakePicture it is always returning false as a result
Asked Answered
B

1

7

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?

Biblicist answered 8/2, 2023 at 14:37 Comment(0)
C
14

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:

  1. As you need to create a File and expose it to the Camera app, you need to create rules with the File Provider and declare it in the Manifest file.
  2. The function that creates a 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")
        }
    }
}
Carbolize answered 8/2, 2023 at 21:35 Comment(2)
You don't need camera permission for take pictureIlljudged
@Illjudged you are wrong, you will get the exception java.lang.SecurityException: Permission Denial: starting Intent { act=android.media.action.IMAGE_CAPTURE flg=0x3 cmp=com.android.camera2/com.android.camera.CaptureActivity clip={text/uri-list hasLabel(0) {U(content)}} (has extras) } from ProcessRecord{b1271ab 32567:package/u0a195} (pid=32567, uid=10195) with revoked permission android.permission.CAMERAHildie

© 2022 - 2024 — McMap. All rights reserved.