Not a really compose question, but I think, `Activ...
# compose
h
Not a really compose question, but I think,
ActivityResultContracts.TakePicture()
should return the file to match Compose declarative style. Currently, you have to create the file first, and keep a reference to it to use it after the camera intent finished successfully. Creating a
TakePicturePlus()
returning
Uri?
instead does not work, because the
TakePicture
result intent is almost empty.
Copy code
val context = LocalContext.current
val id = remember { ImageDTO.ID(UUID()) }
val uri = remember(id) {
    FileProvider.getUriForFile(
    context,
    "sample.provider",
     id.file(context)
      )
}
val cameraLauncher = rememberLauncherForActivityResult(TakePicture()) { success ->
    if (success) {
        action(uri) // Needs a reference to the always created uri...
    }
}
Button(onClick = {
            cameraLauncher.launch(uri) // I want to create the uri here!
}) {
    Text(stringResource(id = R.string.takeAPicture))
}
g
I don't see how it is against the compose declarative style, it's just how this API works on system level, it doesn't add any additional logic on top of it. What kind of name of file, where it should be saved etc If you need such a contract create your own, it's just a few lines of code
h
How? the returning
Intent
does not return the given Input? Or do you mean by simple storing the reference in the custom ActivityContract?... This sounds too easy, I simple forgot it
g
Yes keep reference, it can be even a top level object contract to keep reference, though I wouldn't recommend introducing a global mutable state
It can even be a @Composable function with internal remember if you want to keep it only in composition (essentially wrap your code above)
h
Thanks, I also extract it in a separate compose function, but I don't like creating a file during composition. This should be done in the on click handler, right before launching the camera. So I use this class:
Copy code
class TakePicturePlus(private val fileAuthority: String) : ActivityResultContract<String, Uri?>() {
    private lateinit var uri: Uri
    private lateinit var file: File

    override fun createIntent(context: Context, input: String): Intent {
        file = input.file(context)
        uri = FileProvider.getUriForFile(context, fileAuthority, file)
        return Intent(MediaStore.ACTION_IMAGE_CAPTURE)
            .putExtra(MediaStore.EXTRA_OUTPUT, uri)
    }

    override fun parseResult(resultCode: Int, intent: Intent?) =
        if (resultCode == Activity.RESULT_OK) {
            uri
        } else {
            file.delete()
            null
        }

    private fun String.file(context: Context): File {
        val folder = File(context.filesDir, "images")
        if (!folder.exists()) {
            folder.mkdir()
        }

        val file = File(folder, "$this.jpg")
        file.createNewFile()
        return file
    }
}
i
Keep in mind that your whole app can (and will, on many device) be completely destroyed (and then restored from saved instance state) while the camera is open - you'll want to make sure that
File
is saved (i.e., via
rememberSaveable
, etc.) so that it is actually available when the result returns. That code looks like it'll fail completely in those cases (not to mention cases like doing a configuration change if your activity doesn't handle all config changes)
Oh and keep in mind that unless you
remember
that Contract, you'll get a new instance every time that Composable recomposes - you should really make all of your Contracts stateless if at all possible
☝️ 1
h
Thanks, I will keep this in mind! I already noticed the destruction (and crashes) on real devices, but on the emulator...
s
I wonder Philip, have you gotten something like this work for you afterall? I was trying to also make some contract which encapsulates more of this flow instead of having to do too much on each call site, but trying to avoid any pitfalls like the ones mentioned by Ian. Everything I’ve tried just fails to work when doing the workflow of Start activity for result -> while in the photo picking, process kill my app -> pick the picture -> go back to the app Which is exactly what Ian explains here. So what I’ve done so far is use the plain TakePicture contract, and make sure to properly save the URI that I pass into it through process death, and use that inside the result lambda.