[Android] ActivityResultContracts 사용해보기

평소에 이미지를 촬영하거나 사진을 선택할 때 외부 라이브러리를 가져다 쓰는데
간단한 기능을 사용하는데도 필요한가 싶어서 찾아봤는데 당연하게도 있었다.

AndroidX에 ActivityResultsAPI 에 추가된 기능이다.

보통 다른 Activity에서 결과를 받을 때 startActivityForResult()onActivityResult()를 이용한다.
AndroidResultAPI를 이용하여 동일한 방법으로 처리하고, 다양한 기능을 이용할 수 있다. 이제 외부 라이브러리를 가져다 쓰지 않아도 된다는 말이다.

ActivityResultContracts

ActivityResultContracts 에서 제공하는 기능은 다양한데. 사진 촬영부터 시작해서 문서 생성, 폴더 접근 등 다양하게 지원한다. 직접 Activity를 호출할 경우도 ActivityResultContract<I,O> 추상 클래스를 이용하여 직접 구현할 수 있다.
이번에는 간단하게 어떤식으로 호출이되고, 데이터를 받는지 확인만 해보도록 하려고 한다.

아래 두가지를 이용해서 테스트를 진행했다.

  • ActivityResultContracts.TakePicture()
  • ActivityResultContracts.GetContent()

TakePicture()를 이용하여 사진을 찍고, GetContent()를 이용하여 사진을 가져온다.

프로젝트

Github Sample Project

개발

사용하기 위해서 registerForActivityResult 메서드를 통해 다음과 같이 액티비티 결과 콜백을 등록한다.

private lateinit var takePicture: ActivityResultLauncher<Uri>
// ---
takePicture =
    registerForActivityResult(
        ActivityResultContracts.TakePicture()) { isTakePicture ->
                if (isTakePicture) {
                    Toast.makeText(this, "사진을 찍었습니다", Toast.LENGTH_SHORT).show()
                } else {
                    Toast.makeText(this, "사진을 찍어주세요", Toast.LENGTH_SHORT).show()
                }
            }

registerForActivityResult로 등록 후 얻은 ActivityResultLauncher 인스턴스로 액티비티를 실행할 수 있다.
ActivityResultLauncherlaunch메서드를 호출하여 결과를 받을 Activity를 생성한다.

TakePicture()를 이용하기 위해서는 이미지가 저장되기 위한 Uri가 필요하다. 임시 파일생성 후 FileProvider를 이용하여 이미지가 저장될 Uri를 launch에 파라미터로 넘겨준다.

val imageFile = createFileInFiles(applicationContext, "/images")
currentImageUri = FileProvider.getUriForFile(this, applicationContext.packageName + ".provider", imageFile)

if (currentImageUri != null)
    takePicture.launch(currentImageUri)
private fun loadBitmapFromUri(uri: Uri) = runCatching {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        ImageDecoder.decodeBitmap(
            ImageDecoder.createSource(
                contentResolver,
                uri
            )
        )
    } else {
        MediaStore.Images.Media.getBitmap(contentResolver, uri)
    }
}.getOrElse {
    val `is` = applicationContext.contentResolver.openInputStream(uri)
    BitmapFactory.decodeStream(`is`)
}
private fun createFileInFiles(context: Context, path: String, prefix: String? = null): File {
    val timeStamp: String = System.currentTimeMillis().toString()
    val fileName =
        "${path}/${if (prefix != null && prefix.isBlank()) prefix else ""}_${timeStamp}.jpeg"
    val file = File(getAppFilesDir(context), fileName)
    if (file.parentFile?.exists() == false) {
        file.parentFile?.mkdirs()
    }
    if (!file.exists()) {
        file.createNewFile()
    }
    return file
}
private fun getAppFilesDir(context: Context): File? {
    val file = context.filesDir
    if (file != null && !file.exists()) {
        file.mkdirs()
    }
    return file
}