Improve code readability when taking pictures in android development
Published: 2023-07-19 15:52:31

Here is an extension function that wraps the code of taking pictures into shorter and better readable code:

```kotlin
fun ComponentActivity.registerForTakePicture(
    callback: (Result<Unit>) -> Unit
): ActivityResultLauncher<Uri> {
    return registerForActivityResult(
        ActivityResultContracts.TakePicture()
    ) { success ->
        if (success) {
            callback(Result.success(Unit))
        } else {
            callback(
                Result.failure(
                    Throwable(
                        "Didn't save a picture. " +
                                "ACTION_IMAGE_CAPTURE-Activity cancel?"
                    )
                )
            )
        }
    }
}
```
=> Watch out: A failure will be also returned if the user presses the abort-button in the Take Image Screen intentionally. Unfortunately we can't differ if the user aborts intentionally or if there was an error recording the image.

But there is another bad code design in the default android api. The trigger point of launching the take-photo activity and the result handler are very separate although they logically belong to each other in my opinion:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/meyy47u758e4j028nbgx.png)

See this article: https://medium.com/codex/how-to-use-the-android-activity-result-api-for-selecting-and-taking-images-5dbcc3e6324b

This forces the developer to create a global variable (either as an activity member field or in the ViewModel/State) that has to be nullable or lateinit. 

What if we can create a better api?

## Singleton that solves the problem

```kotlin
class TakePictureContract {

    private val logger by provideLogger(this::class.java.simpleName)

    private var activityResultLauncher: ActivityResultLauncher<Uri>? = null
    private var targetUri: Uri? = null
    private var continuation: Continuation<Boolean>? = null

    fun registerForActivity(activity: ComponentActivity) {
        activityResultLauncher = activity.registerForTakePicture { result ->
            result
                .onSuccess {
                    val uri = targetUri
                        ?: throw IllegalStateException(
                            "targetUri is null"
                        )

                    continuation?.resume(true)

                    this.continuation = null
                    this.targetUri = null
                }
                .onFailure {
                    logger.d(it.message.toString())
                    continuation?.resume(false)

                    this.continuation = null
                    this.targetUri = null
                }
        }

    }

    suspend fun run(targetUri: Uri) = suspendCoroutine {
        if (this.continuation != null) {
            throw IllegalStateException(
                "TakePictureContract is already running"
            )
        }
        this.targetUri = targetUri
        this.continuation = it
        activityResultLauncher?.launch(targetUri)
    }

}
```

## Usage from ViewModel

```kotlin
fun newImage(fileUri: Uri) {
    viewModelScope.launchIO {
        try {
            _state.update { it.copy(isLoading = true) }

            val result = takePictureContract.run(fileUri)
            if (result) {
                addTakenImage(fileUri)
            }
        } finally {
            _state.update { it.copy(isLoading = false) }
        }
    }
}
```

## Register Activity in onCreate

```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    takePictureContract.registerForActivity(this)
}
```

## Some additional notes

* The Instance of `TakePictureContract` has to be the same in Activity and ViewModel. I solved this via Koin Dependency Injection. An easy alternative to DI would be to use kotlin `object TakePictureContract` instead of class.
* DI for `TakePictureContract` also allows mocking the wanted behaviour for unit tests
* It is okay to use a singleton in my opinion because there can only be one camera activity active at a single time.

Let me know that you think about the class in the comments!

Regards 
Max