My Jetpack Compose camera app is targeting API Level 32 and is being tested on an Android 11 phone. I'm generating a Uri
with FileProvider
to take a photo with the stock camera app. Logcat shows the Uri
every time I snap a picture, but the image is displayed in the UI only for the first time. Subsequent camera snapshots don't show the image although the Uri
is displayed in Logcat. And exiting the app with a back button click and then opening the app to take a snapshot, again, only works for the first time. How can I fix this issue?
Manifest File
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.testsoft.camtest">
<uses-feature android:name="android.hardware.camera" />
<uses-permission android:name="android.permission.CAMERA" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Camtest"
tools:targetApi="31">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:grantUriPermissions="true"
android:exported="false">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Camtest">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
File Paths XML File
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-files-path
name="my_images"
path="Android/data/com.testsoft.camtest/files/Pictures"/>
<external-files-path
name="my_debug_images"
path="/storage/emulated/0/Android/data/com.testsoft.camtest/files/Pictures/"/>
<external-files-path
name="my_root_images"
path="/"/>
</paths>
MainScreen Composable
@Composable
fun MainScreen() {
var hasImage by remember {
mutableStateOf(false)
}
var imageUri by remember {
mutableStateOf<Uri?>(null)
}
val context = LocalContext.current
var grantCameraState by remember {
mutableStateOf(
ContextCompat.checkSelfPermission(
context,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
)
}
val cameraPermissionlauncher: ManagedActivityResultLauncher<String, Boolean> =
rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestPermission()) {
grantCameraState = it
}
val cameraLauncher = rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) { success ->
Log.i("UriContent@Snapshot", imageUri.toString())
hasImage = success
}
Column {
Button(
modifier = Modifier.align(alignment = Alignment.CenterHorizontally),
onClick = {
if (grantCameraState) {
val uri = getCamImageUri(context)
imageUri = uri
cameraLauncher.launch(uri)
} else {
cameraPermissionlauncher.launch(Manifest.permission.CAMERA)
}
}) {
Text(text = "Take photo")
}
Spacer(Modifier.width(10.dp))
if(hasImage && imageUri != null){
Log.i("UriContent@Render", imageUri.toString())
AsyncImage(
imageUri,
contentDescription = null,
modifier = Modifier.fillMaxWidth()
)
/*
//Also tried this but had the same issue:
Image(
painter = rememberAsyncImagePainter(imageUri),
contentDescription = null,
modifier = Modifier.fillMaxWidth()
)
*/
}
}
}
fun getCamImageUri(context: Context): Uri? {
var uri: Uri? = null
val file = createImageFile(context)
try {
uri = FileProvider.getUriForFile(context, "com.testsoft.camtest.fileprovider", file)
} catch (e: Exception) {
Log.e(ContentValues.TAG, "Error: ${e.message}")
}
return uri
}
private fun createImageFile(context: Context) : File {
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
val imageDirectory = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
return File.createTempFile(
"Camtest_Image_${timestamp}",
".jpg",
imageDirectory
)
}
Logcat Output
I/UriContent@Snapshot: content://com.testsoft.camtest.fileprovider/my_root_images/Pictures/Camtest_Image_20220702_1002472475660413794636578.jpg
I/UriContent@Render: content://com.testsoft.camtest.fileprovider/my_root_images/Pictures/Camtest_Image_20220702_1002472475660413794636578.jpg
Logcat message with subsequent camera launch
D/skia: --- Failed to create image decoder with message 'unimplemented'
I found the issue. After debugging the app, I discovered that I needed to set the hasImage
state variable to false
in the Take photo
button's onclick
logic like below:
Button(
modifier = Modifier.align(alignment = Alignment.CenterHorizontally),
onClick = {
if (grantCameraState) {
val uri = getCamImageUri(context)
imageUri = uri
// Set it to false here
hasImage = false
cameraLauncher.launch(uri)
} else {
cameraPermissionlauncher.launch(Manifest.permission.CAMERA)
}
}) {
Text(text = "Take photo")
}