- Today
- Total
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- GIT
- suspend
- 학습지
- 책추천
- Android
- CustomTab
- 코틀린
- 진짜학습지
- 책리뷰
- ai
- webflux
- KotlinInAction
- 안드로이드
- jlpt
- n3문법
- 인공지능
- PR
- 일본어문법
- Kotlin
- github
- 진짜일본어
- blog
- 일본어기초
- errorhandling
- posting
- rxjava
- coroutine
- androidstudio
- 진짜학습지후기
- pullrequest
코딩하는 개굴이
[안드로이드] 액티비티에서 결과를 받아오자, registerForActivityResult 본문
기존에 액티비티에서 결과를 받아올 때, 어떤 방식을 사용했는지 기억하는가?
우리는 지금까지 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))
}
}