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:
- 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.
- 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")
}
}
}