본문 바로가기
android

startActivityForResult 너 누군데. ActivityResultLauncher를 써야 하는 이유

by liz_devel 2024. 9. 25.

오늘 app to app 통신을 하는 간단한 프로젝트를 만들 일이 있어서 ActivityResultLauncher를 쓰게 되었는데 전에는 생각없이 사용했던 것을 오늘 다시 한번 사용하게 되면서 이참에 정리를 해두려고 한다.

 

 


ActivityResultLauncher를 쓰기 전에 우리는 ActivityForResult를 사용했다.

현재 ActivityForResult는 deprecated되었다.

ActivityForResult가 deprecated 된 이유는 다음과 같다.

 

결과를 받기 위해 여러 가지 단계를 거쳐야 했고, onActivityResult 메서드를 오버라이드해야 했다.

이로 인해 코드가 복잡해지고 유지 보수가 어려웠다.

 

  • 여러 단계(1. Intent 생성 및 startActivityForResult(), 2. onActivityResult() 오버라이드, 3. 결과 Intent 생성 및 setResult())를 거쳐야 하고,
  • 각 단계에서 잘못된 처리를 할 경우, 특히 requestCode나 resultCode가 일치하지 않거나, 생명주기 변화로 인해 결과 처리가 어려울 수 있다.
  • 프래그먼트에서도 이 메서드를 사용할 경우, 부모 액티비티에 결과가 전달되고 프래그먼트에서 다시 처리하는 번거로움이 있다.

 

구체적으로 여러가지 단계가 어떤 단계인데?

 

1. Request Code 설정

val intent = Intent(this, SecondActivity::class.java)
startActivityForResult(intent, REQUEST_CODE)

 

2. onActivityResult() 메서드 오버라이드

결과를 받기 위해 onActivityResult() 메서드를 오버라이드한다. 이 메서드에서 requestCode와 resultCode를 사용해, 어떤 요청에 대한 응답인지, 그리고 결과가 성공적인지(RESULT_OK, RESULT_CANCELED)를 체크한 후 적절한 처리를 해야 한다.

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        // 결과 처리 로직
        val resultData = data?.getStringExtra("result_key")
    }
}

3. 결과 Intent에 데이터 넣기

결과를 반환하는 쪽에서도 결과를 담은 Intent를 준비하고, 결과를 설정한 후, setResult() 메서드로 결과를 넘겨야 한다.

val resultIntent = Intent()
resultIntent.putExtra("result_key", "some_result")
setResult(Activity.RESULT_OK, resultIntent)
finish()

 

4. 생명 주기 문제

onActivityResult() 메서드는 액티비티의 생명 주기와 관련이 있어, 액티비티가 재생성되거나(예: 화면 회전), 백그라운드에서 복귀할 때 결과 처리가 누락될 위험이 있다. 이러한 경우, 추가로 생명 주기 관련 처리를 해줘야 한다.

 

 

 

 

위에서 사용 과정을 하나하나 보는데도 벌써부터 위험 요소가 다분하다. 심지어 Request Code가 여러 개 생겼을 때 응답 처리할 것을 생각하면 벌써부터 끔찍하다. 현재 내가 맡고 있는 프로젝트에는 App to App 통신을 12개 하고 있다. 처음 12개의 코드를ActivityForResult로 처리하여 조건문으로 길게 늘어진 코드를 보았을 때란.... 코드를 보기 굉장히 힘들었을 뿐더러 생명주기에도 신경을 바짝 기울여야 했던 코드였다. 이 코드를 ActivityResultLauncher로 바꾸어주었다.

 

당시에는 ActivityForResult deprecated됐대! 하고 바로 적용시켰다. 

왜?라는 것에 대해 생각하지 않은 채, 그리고 또다시 ActivityResultLauncher를 마주한 지금...

이제 왜? 바꿔야 하는지에 대해 설명해본다.

 

 


 

그래서 왜 ActivityResultLauncher를 써야 하냐고!!!

 

1. 간결한 코드

startActivityForResult는 onActivityResult() 메서드를 오버라이드해서 결과를 처리해야 하는 반면, ActivityResultLauncher는 간단한 콜백 방식을 사용하여 결과를 처리할 수 있다. 코드가 훨씬 간결하고 가독성이 좋다.

 

val resultLauncher = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()
) { result ->
    if (result.resultCode == Activity.RESULT_OK) {
        val data = result.data
        // 결과 처리
    }
}

val intent = Intent(this, SecondActivity::class.java)
resultLauncher.launch(intent)

 

ActivityResultLauncher를 사용하면 따로 onActivityResult() 메서드를 오버라이드할 필요 없이 바로 콜백에서 결과를 처리할 수 있다. 이로 인해 코드가 더 직관적이고 간결해졌다.

 

 

 

2. 생명 주기와의 일관성

startActivityForResult는 액티비티나 프래그먼트의 생명 주기와 관련된 문제가 있었다. 예를 들어, 화면 회전이나 시스템에 의해 액티비티가 종료되고 다시 시작되는 경우, onActivityResult() 콜백이 제대로 호출되지 않거나 누락될 수 있다.

ActivityResultLauncher는 액티비티나 프래그먼트의 생명 주기에 맞춰 자동으로 관리되며, 재생성 상황에서도 안전하게 결과를 받을 수 있도록 보장한다.

 

 

3. 프래그먼트에서의 일관된 사용

기존의 startActivityForResult를 프래그먼트에서 사용할 때는, 부모 액티비티로 결과가 전달된 후 다시 프래그먼트로 넘겨줘야 하는 번거로움이 있었다. 하지만 ActivityResultLauncher는 액티비티와 프래그먼트 모두에서 동일한 방식으로 동작하며, 프래그먼트 내에서 결과를 직접 처리할 수 있다.

  • 기존 방식 (프래그먼트에서 사용)
// 프래그먼트에서 startActivityForResult 호출
val intent = Intent(context, SecondActivity::class.java)
startActivityForResult(intent, REQUEST_CODE)

// 부모 액티비티에서 onActivityResult 오버라이드 필요
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    val fragment = supportFragmentManager.findFragmentById(R.id.fragment_container)
    fragment?.onActivityResult(requestCode, resultCode, data)
}

 

  • 새로운 방식 (프래그먼트에서 사용)
val resultLauncher = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()
) { result ->
    if (result.resultCode == Activity.RESULT_OK) {
        // 결과 처리
    }
}

// 프래그먼트에서 바로 실행
val intent = Intent(context, SecondActivity::class.java)
resultLauncher.launch(intent)

프래그먼트에서도 액티비티와 동일한 방식으로 결과 처리를 할 수 있어 불필요한 복잡성이 제거된다.

 

 

 

4. 미리 등록된 계약(Contract) 사용 가능

ActivityResultContracts 클래스는 여러 **미리 정의된 결과 처리 계약(Contract)**을 제공한다. 예를 들어, 사진을 촬영하거나, 파일을 선택하거나, 권한을 요청하는 등의 작업에서 별도의 추가 처리 없이 간편하게 사용할 수 있다.

 

예: 권한 요청 (ActivityResultLauncher 사용)

val requestPermissionLauncher = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
    if (isGranted) {
        // 권한이 허용된 경우
    } else {
        // 권한이 거부된 경우
    }
}

requestPermissionLauncher.launch(Manifest.permission.CAMERA)

이처럼 **ActivityResultContracts**를 사용하면 권한 요청, 이미지 선택, 파일 열기 등 자주 사용되는 액티비티 요청을 매우 간편하게 처리할 수 있다.

 

5. 더 나은 에러 처리와 예외 처리

ActivityResultLauncher는 결과를 받을 때 더 안전하고 예외 처리에 유리하다. 특히, 생명 주기와 관련된 문제나 결과 처리 누락 가능성을 줄여준다.

 

 

 

 

 

결론

ActivityResultLauncher를 사용하는 이유

  • 간결한 코드 작성 가능
  • 생명 주기 문제 해결로 안정성 향상
  • 프래그먼트에서도 일관되게 동작
  • **미리 정의된 계약(Contract)**을 사용하여 반복적인 코드를 줄임
  • 결과 처리 누락 문제 감소

이러한 이유로 ActivityResultLauncher는 기존의 startActivityForResult보다 더 현대적이고 안전한 대안으로 권장된다.

반응형