Search code examples
androidkotlinandroid-jetpack-composeandroid-camera

Application gets killed during camera/file intent Xiaomi Android API 29


I'm having a little bit of an headache with several applications in Kotlin - Compose; I'll go straight to the point:

Context

The issue is consistently happening (9 times out of 10) with Xiaomi devices running API 29. The device that I currently have available (and that also presents that issue) is a Xiaomi Mi A2 Lite.

Emulator: Running the application in an emulator (API 29) works without any problem

Manifest: Manifest has the following permissions:

<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

Provider paths:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-cache-path
        name="my_files_cached"
        path="." />

    <files-path
        name="my_files"
        path="."/>
</paths>

Runtime permissions: Runtime permissions are given in both camera & file picker components when clicking a button (see code snippets in Code section).

Issue

  1. In the app start a launcher for either ActivityResultContracts.TakePicture() or ActivityResultContracts.GetContent()
  2. While outside of the app, either in the camera or in file picker, logcat writes out --- PROCESS ENDED (16504) for package ... ---- signaling that the app has been killed in the background
  3. Snap a photo or select a file and when returning to the app a white screen is shown for atleast 3-4 seconds and then it's clearly visible that the application has been recreated. Other than that, the content (file or photo) is not returned properly to the app (or in the camera case, not returned at all).

Code

Below a snippet of a @Composable body for handling the Camera photo snapping (I'm not posting the file picker code to avoid a text-wall; consider that it's similiar to the camera one):

val tmpFile = createTmpExtCachePhoto(photoFileName)

val context = LocalContext.current
var currentPhotoUri by remember { mutableStateOf(value = Uri.EMPTY) }
val uri = FileProvider.getUriForFile(
    Objects.requireNonNull(context),
    BuildConfig.APPLICATION_ID + ".provider", tmpFile
)

val cameraLauncher = rememberLauncherForActivityResult(
    contract = ActivityResultContracts.TakePicture(),
    onResult = { success ->
        if (success) currentPhotoUri = uri
    }
)

val launcher = rememberLauncherForActivityResult(
    ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
    if (isGranted) {
        // Permission Accepted: Do something
        cameraLauncher.launch(uri)
    } else {
        // Permission Denied: Do something
        onCameraPermissionDenied.invoke()
    }
}


if (currentPhotoUri.toString().isNotEmpty()) {
    onImagePicked.invoke(currentPhotoUri.toString())
}

Button(
    modifier = modifier,
    colors = buttonColors,
    onClick = {
        // Check permission
        when (PackageManager.PERMISSION_GRANTED) {
            ContextCompat.checkSelfPermission(
                context,
                Manifest.permission.CAMERA
            ) -> {
                // Permission already given, perform operation
                cameraLauncher.launch(uri)
            }
            else -> {
                // Asking for permission
                launcher.launch(Manifest.permission.CAMERA)
            }
        }
    }
) {
    Text(text = buttonLabel)
}

Situation

From what I got I think that there shouldn't be a particular issue within my codebase and this whole ordeal seems like it's coming from the system beneath that kills the app immediatelly after it goes out of focus. Now, if that happens to be the case, how should this situation be handled? Is there something that I can do to solve the problem? I'm willing to evaluate even workaround solutions that gets the app into a stable and reliable situation.

With that being said, thank you for your time!


Solution

  • Thanks to CommonsWare and Alexander Hoffmann comments I ended up solving the problem by making use of what the Android framework and Compose already gives us in terms of handling and persisting state throughout system-initiated process death:

    • rememberSaveable for persisting data in Composable components;
    • SavedStateHandle in ViewModel for persisting StateFlows; this I found particularly useful and ended up allowing me to completely solve the issue putting my application in a very stable and reliable state.

    This is a very useful page on developer.android that I heartily recommend. It describes the various possibilities that we have when talking about saving UI states.

    With that being said I hope that this will be useful and thank you all once again for your time!