
안드로이드에서는 화면 간 데이터 교환을 위해 기존에 startActivityForResult를 사용했지만, activityResultLauncher API가 등장하면서 더 직관적이고 안전하게 데이터를 주고받을 수 있게 되었습니다.
이번 글에서는 카메라 액티비티에서 촬영한 파일을 서버에 업로드하고, 그 결과를 원래 화면에 전달하여 토스트로 표시하는 예시를 통해 activityResultLauncher의 사용법을 알아보겠습니다.
1. activityResultLauncher를 onCreate에서 정의해야 하는 이유
activityResultLauncher는 액티비티의 생명주기를 따르는 안전한 비동기 콜백입니다. onCreate에서 한 번만 정의하여야 안정적으로 동작하며, 다른 메서드에서 정의할 경우 여러 개의 activityResultLauncher 인스턴스가 불필요하게 생성되어 예상치 못한 동작이 발생할 수 있습니다. onCreate에서 정의하여 액티비티 생명주기와 함께 안전하게 관리하는 것이 좋습니다.
2. activityResultLauncher를 활용한 파일 업로드 결과 처리 예시
이번 예시에서는 신고하기 액티비티에서 카메라 액티비티를 실행하고, 촬영한 파일을 서버에 업로드한 후 성공 여부를 원래 액티비티로 전달하여 토스트로 알림을 표시하는 과정입니다. activityResultLauncher를 통해 서버 업로드 결과 데이터를 주고받아 비동기적으로 관리할 수 있습니다.
신고하기 액티비티
신고하기 액티비티는 activityResultLauncher를 onCreate에서 정의하여, 카메라 액티비티의 실행 결과를 비동기적으로 수신합니다. sendEmergencyCreate() 메서드가 비상 신고 API를 호출해 서버로부터 이벤트 ID를 받고, 이 ID를 tryMediaCapture()로 전달하여 카메라 액티비티를 실행합니다.
class ReportActivity : AppCompatActivity() {
private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// activityResultLauncher 정의 및 등록
activityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
Toast.makeText(this, "사진이 등록되었습니다.", Toast.LENGTH_SHORT).show()
} else if (it.resultCode == RESULT_CANCELED) {
val errorMessage = it.data?.getStringExtra("onFailure")
Toast.makeText(this, errorMessage ?: "사진 등록 실패", Toast.LENGTH_SHORT).show()
}
}
}
// 비상 신고 API 호출
private fun sendEmergencyCreate() {
ApiSender.reportEmergency(this) { eventId ->
Logger.d("Received event ID in ReportActivity: $eventId")
tryMediaCapture(eventId) // 응답받은 eventId로 카메라 액티비티 실행
}
}
private fun tryMediaCapture(eventId: Long) {
val captureFormat = UserSettingsManager.getEmergencyFormat(this)
if (captureFormat != UserSettingsManager.EmergencyFormatType.NONE) {
val intent = Intent(this, MediaCaptureActivity::class.java).apply {
putExtra("eventId", eventId)
putExtra("mediaFormat", captureFormat.value)
}
activityResultLauncher.launch(intent) // activityResultLauncher를 통해 카메라 액티비티 시작
}
(application as GaboApplication).isEmergency = true
uiEmergency()
}
}
카메라 액티비티
카메라 액티비티는 takePhoto()와 captureVideo() 메서드를 통해 촬영한 파일을 서버에 업로드합니다. 업로드 성공 시 RESULT_OK와 성공 메시지를 반환하고, 실패 시 RESULT_CANCELED와 실패 메시지를 반환하여 신고하기 액티비티가 이를 받아서 토스트로 알림을 표시할 수 있게 합니다.
class MediaCaptureActivity : AppCompatActivity() {
private suspend fun uploadFiles(eventId: Long, videoFile: File?, imageFiles: List<File>?) {
fileUploadClient.uploadFiles(
context = this,
eventId = eventId,
videoFile = videoFile,
imageFiles = imageFiles,
onSuccess = {
Logger.d("파일 업로드 성공 $eventId")
val intent = Intent().apply {
putExtra("onSuccess", "파일 업로드 성공 $eventId")
}
setResult(RESULT_OK, intent)
if (!isFinishing) finish()
},
onFailure = { errorMessage ->
Logger.d("파일 업로드 실패: $errorMessage, $eventId")
val intent = Intent().apply {
putExtra("onFailure", "파일 업로드 실패: $errorMessage")
}
setResult(RESULT_CANCELED, intent)
if (!isFinishing) finish()
})
}
private fun takePhoto() {
val photoFile = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "photo.jpg")
imageCapture.takePicture(
ImageCapture.OutputFileOptions.Builder(photoFile).build(),
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
CoroutineScope(Dispatchers.IO).launch {
uploadFiles(eventId = mediaEventId, imageFiles = listOf(photoFile), videoFile = null)
}
}
override fun onError(exception: ImageCaptureException) {
Logger.e("Photo capture failed", exception)
}
}
)
}
private fun captureVideo() {
val videoFile = File(getExternalFilesDir(Environment.DIRECTORY_MOVIES), "video.mp4")
val outputOptions = FileOutputOptions.Builder(videoFile).build()
videoCapture.output.prepareRecording(this, outputOptions).apply {
if (ActivityCompat.checkSelfPermission(this@MediaCaptureActivity, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
withAudioEnabled()
}
}.start(ContextCompat.getMainExecutor(this)) { recordEvent ->
if (recordEvent is VideoRecordEvent.Finalize && !recordEvent.hasError()) {
CoroutineScope(Dispatchers.IO).launch {
uploadFiles(eventId = mediaEventId, videoFile = videoFile, imageFiles = null)
}
}
}
}
}
위 예시에서 takePhoto()와 captureVideo() 메서드는 촬영 후 uploadFiles() 메서드를 호출하여 서버에 파일을 업로드합니다. uploadFiles()는 서버 응답에 따라 성공 시 RESULT_OK와 성공 메시지를 반환하고, 실패 시 RESULT_CANCELED와 실패 메시지를 반환하여 신고하기 액티비티가 이를 통해 알림을 표시할 수 있게 합니다.
'Android > Android Core' 카테고리의 다른 글
[Android/Kotlin] EncryptedSharedPreferences 사용하기: 보안 강화된 데이터 저장 방법 (0) | 2024.11.08 |
---|---|
[Android/Kotlin] CameraX에서 발생한 'Use case binding failed' 오류를 조건부 바인딩으로 해결하기 (1) | 2024.11.01 |
[Android/Kotlin] 기본 카메라 기능을 사용한 사진 및 비디오 자동 촬영 시도 기록 (0) | 2024.10.25 |
[Android/Kotlin] CameraX로 자동 사진 및 비디오 촬영 기능 구현하기 (1) | 2024.10.25 |
[Android/Kotlin] Bluetooth Notify 기능 구현: CCCD 설정과 알림 활성화 방법 (0) | 2024.10.25 |