코딩하는 개굴이

[안드로이드] 액티비티에서 결과를 받아오자, registerForActivityResult 본문

안드로이드

[안드로이드] 액티비티에서 결과를 받아오자, registerForActivityResult

개굴이모자 2023. 4. 9. 22:13
반응형

기존에 액티비티에서 결과를 받아올 때, 어떤 방식을 사용했는지 기억하는가?

 

우리는 지금까지 StartActivityForResult 로 액티비티를 호출하고, onActivityResult를 사용해 결과를 받아오곤했다.

그러나, 이 방법은 Deprecated 되었다.

Activity 클래스에서 여전히 사용할 수는 있지만, Android는 AndroidX Activity 와 Fragment 에 도입된 새로운 방식을 통한 사용으로 변경될 것을 권장하고 있다. 

 

그것이 바로 registerForActivityResult 이다.

우선, 우리는 가장 심플한 케이스인 MainActivity ← → SecondActivity 로 상호 데이터를 전달하는 케이스를 기반으로 알아보도록하자.

 

MainActivity: Activity Result 에 Callback 을 등록한다

registerForActivityResult() 는 ActivityResultLauncher 를 반환하는데, 이 런처는 ActivityResultContract, ActivityResultCallback 을 가져와 다른 활동을 이어가는데 사용한다.

  • ActivityResultContract: 결과를 생성하는데 필요한 기본적인 입력과 결과 출력 유형을 정의하고, 기본 인텐트 작업의 contract 를 제공하며, 이는 커스텀 또한 가능하다.
  • ActivityResultCallback: ActivityResultContract 에 정의된 출력 유형의 객체를 가져오는 onActivityResult 메서드가 포함된 단일 메서드 인터페이스이다.
//1. registerForActivityResult 로 callback 등록하기
val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
}

동시에 다른 contract 또한 사용할 경우

만일 또다른 activity 를 유사하게 사용해야할 필요성이 존재하여, 별개의 콜백을 원하는 여러 activityForResult 호출이 있다면, registerForActivityResult() 를 여러번 호출해 여러 ActivityResultLauncher 를 등록할 수 있다.

 

💡참고💡 명심해야할 점은 registerForActivityResult() 는 fragment, activity 의 created 시점 전에 호출 되어야만 한다는 것이다. 그러나, ActivityResultLauncher 는 fragment, activity의 lifecycle 의 created 시점 전에는 launch 할 수 없으므로 주의해야한다.

 

따라서, 일반적인 케이스에서는 registerForActivityResult()는 fragment나 acitivity 를 생성하기 전에 호출되는 것이 안전하므로, 반환되는 ActivityResultLauncher 인스턴스의 멤버변수를 선언 시점에 직접 사용할 수도 있다.

 

MainActivity: Activity for result 로 launch 하기

위의 과정으로 registerForActivityResult 가 콜백을 등록하였다면, 이것은 다른 액티비티를 launch 하고 result 를 반환해주는 역할까지 하는 것은 아니다.

아래 코드를 보자.

//1. registerForActivityResult 로 callback 등록하기
val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
    // Handle the returned Uri
		// 3. activity 갔다가 돌아온다면, 해당 callback 이 실행된다.
}

override fun onCreate(savedInstanceState: Bundle?) {
    // ...

    val selectButton = findViewById<Button>(R.id.select_button)

    selectButton.setOnClickListener {
        // Pass in the mime type you want to let the user select
        // as the input
        getContent.launch("image/*") //2. launch 를 contract 즉 상위에 등록한 uri 를 넘기는 약속에 맞게 넘긴다.
    }
}

반환된 ActivityResultLauncher 가 해당 역할을 대신해주는데, 만일 input 이 존재한다면 Launcher 는 이러한 Contract 에 맞는 타입으로 input 을 받는다. 아래와 같이 launch 를 호출하면, result 를 생산하는 과정을 거친다. 사용자가 activity 를 종료하고 리턴된다면, ActivityResultCallback이 실행된다.

💡 위와 같은 과정에서 질문이 하나 생길 수 있다. 만일 모종의 이유로 프로세스와 액티비티가 launch 는 했지만 onActivityResult() 콜백이 트리거 되기 전에 destroy 된 경우는 어찌하는가? 이런 경우 처리하는데 필요한 추가적인 상태들은 별도로 저장하고 복원해야한다.

 

 

Another Class: Activity Result 수신하기

별도의 클래스에서 ActivityResult 를 수신하는 것 또한 가능하다.

예를들어, launcher 실행과 함께 constract 의 등록을 처리하는 LifecycleObserver 를 구현하는 것이다.

이때는 ActivityResultRegistry 를 사용한다.

Google 은 이를 사용할 때, LifecycleOwner 와 함께 사용할 것을 강력하게 권장한다. LifecycleOwner 는 자동적으로 등록된 launcher를 Lifecycle 이 Destory 되는 시점에서 제거해주기 때문이다. 그러나, 만일 LifecycleOwner 가 존재하지 않는 불가피한 상황항에서라면, 각 ActivityResultLauncher 클래스는 수동적으로 unregister() 를 해제할 수 있도록 대안을 두고 있다.

//1. LifecycleObserver 를 생성. registry 를 받도록 하여, 이곳에서 Callback 을 등록할 수 있도록 한다.
class MyLifecycleObserver(private val registry : ActivityResultRegistry)
        : DefaultLifecycleObserver {
    lateinit var getContent : ActivityResultLauncher<String>

    override fun onCreate(owner: LifecycleOwner) {
        getContent = registry.register("key", owner, GetContent()) { uri ->
            // Handle the returned Uri
        }
    }
		// 2. launch 또한 이곳에서 수행하도록 한다.
    fun selectImage() {
        getContent.launch("image/*")
    }
}

class MyFragment : Fragment() {
    lateinit var observer : MyLifecycleObserver

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
				// 3. fragment 에서는 observer 를 생성하고, lifecycle 에 등록시킨다. 이로써, activityResult 콜백은 생명주기와 함께한다.
        observer = MyLifecycleObserver(requireActivity().activityResultRegistry)
        lifecycle.addObserver(observer)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val selectButton = view.findViewById<Button>(R.id.select_button)

        selectButton.setOnClickListener {
            // Open the activity to select an image
						// 4. fragment 에서 launch 가 필요한 시점에 observer 의 메서드를 호출한다.
            observer.selectImage()
        }
    }
}

 

Custom Contract 생성하기

ActivityResultContracts 는 prebuilt 된 ActivityResultcontract 클래스들을 여럿 제공하고 있지만 정확한 타입을 사용하기 위해서 custom 하게 생성할 수 있다.

각 ActivityResultContract 는 input 과 output class 의 정의를 필요로하는데, 만일 사용하지 않는다면 Void? 혹은 Unit 을 둘 수도 있다.

각 contract 는 createIntent 메서드를 implement 해야하고, Context 와 contract 에 필요한 input 들을 받아야한다.

각 contract 는 또한 parseResult 를 implement 해야하고 reultCode 를 리턴해야한다. OK 혹은 CANCLED 같은 것 말이다.

class PickRingtone : ActivityResultContract<Int, Uri?>() {
    override fun createIntent(context: Context, ringtoneType: Int) =
        Intent(RingtoneManager.ACTION_RINGTONE_PICKER).apply {
            putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringtoneType)
        }

    override fun parseResult(resultCode: Int, result: Intent?) : Uri? {
        if (resultCode != Activity.RESULT_OK) {
            return null
        }
        return result?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
    }
}

만일 custom contract 가 필요하지 안다면 StartActivityForResult contract 를 사용하면된다. 이는 generic 한 contract 로 어느 Intent 를 입력으로 받을 수 있고, ResultCode 와 Intent를 callback 의 결과로 받을 수 있도록 ActivityResult 를 리턴한다.

val startForResult = registerForActivityResult(StartActivityForResult()) { result: ActivityResult ->
    if (result.resultCode == Activity.RESULT_OK) {
        val intent = result.data
        // Handle the Intent
    }
}

override fun onCreate(savedInstanceState: Bundle) {
    // ...

    val startButton = findViewById(R.id.start_button)

    startButton.setOnClickListener {
        // Use the Kotlin extension in activity-ktx
        // passing it the Intent you want to start
        startForResult.launch(Intent(this, ResultProducingActivity::class.java))
    }
}
반응형
Comments