Activity
와 Fragment
간, 혹은 Fragment
와 Fragment 내의 다른 Fragment
간 데이터(혹은 이벤트)를 전달할때는 Activity 내에 Fragment가 포함되어 있는 형태이기 때문에 보통 리스너를 사용한다.
Activity
ㄴ FragmentA
ㄴ ㄴ FragmentB
/** in Activity */ class ActivityA : FragmentActivity() { ... val f = FragmentA.newInstance { // do something } supportFragmentManager.beginTransaction() .addToBackStack(backStack) .add(android.R.id.content, f, backStack) // or replace .commit() ... }
/** in FragmentA */ class FragmentA: Fragment() { companion object { fun newInstance(listener: (Result) -> Unit) = FragmentA().apply { arguments = bundleOf() onResultListener = listener } } ... // listener 호출 or FragmentB로 전달 onResultListener.invoke(result) // or val f = FragmentB.newInstance(onResultListener) parentFragmentManager.beginTransaction() .addToBackStack(backStack) .add(android.R.id.content, f, backStack) // or replace .commit() ... }
/** in FragmentB */ class FragmentB: Fragment() { companion object { fun newInstance(listener: (Result) -> Unit) = FragmentB().apply { arguments = bundleOf() onResultListener = listener } } ... // listener 호출 onResultListener.invoke(result) ... }
하지만 동등한 레벨에 있는 FragmentA
와 FragmentB
사이에 데이터를 전달할때는 바로 리스너를 등록할 수 없고 ViewModel
을 통해 데이터를 중계해줘야 한다는 문제가 있다.
Activity
ㄴ FragmentA
ㄴ FragmentB
/** in Activity */ class AcivityA: FragmentActivity() { val testViewModel by viewModels<TestViewModel>() ... }
/** in FragmentA */ class FragmentA: Fragment() { val testViewModel by activityViewModels<TestViewModel>() ... testViewModel.resultData.postValue(result) ... }
/** in FragmentB */ class FragmentB: Fragment() { val testViewModel by activityViewModels<TestViewModel>() ... testViewModel.resultData.observe(viewLifecycleOwner) { // do somethiing } ... }
/** in TestViewModel */ class TestViewModel: ViewModel() { val resultData by lazy { MutableLiveData<Result>() } ... }
따로 적진 않겠지만 뷰모델이 없던 시절에는 Activity를 통해 리스너를 중계하는 작업을 해야해서 더 복잡하고 어려운 방식을 사용했었다.
이를 처리하기 위해 androidx.fragment:fragment-ktx:1.3.0-alpha4
라이브러리에 FragmentResultListener
, setFragmentResult
함수가 추가됐다.
FragmentResult는 같은 FragmentManager
를 참조하는 Fragment
끼리 데이터를 주고 받을수 있으므로 Activity나 ViewModel을 통한 데이터 중계도 필요없게 된다.
/** in FragmentA */ class FragmentA: Fragment() { ... setFragmentResult("resultKey", bundleOf("result" to result)) ... }
/** in FragmentB */ class FragmentB: Fragment() { ... fragmentResultListener("resultKey") { resultKey: String, result: Bundle -> // do something } ... }
다만 단점이라면 하나의 FragmentResultListener
를 여러 곳에서 동시에 등록할 수 없다는 것이다.
fragment-ktx 라이브러리 코드를 보면 알겠지만 리스너를 등록할때 아래와 같이 등록한다.
/** in fragment-ktx library */
public fun Fragment.setFragmentResultListener(
requestKey: String,
listener: ((requestKey: String, bundle: Bundle) -> Unit)
) {
parentFragmentManager.setFragmentResultListener(requestKey, this, listener)
}
add 방식이 아닌 set 방식이라 FragmentC
에서 등록한(setFragmentResult
) 리스너를 FragmentA
와 FragmentB
에서 동시에 리스너로 등록하면(fragmentResultListener
) 가장 마지막에 등록된 리스너 한 곳에서만 동작한다.
이런 경우는 리스너 내부에서 새로운 리스너를 호출하던가 아니면 이전처럼 뷰모델의 MutableLiveData
를 통해 공유하는 방법을 사용해야 할듯 싶다.
fragmentResultListener 내부에서 새로운 리스너를 등록, 호출하는 방식은 아래처럼 하면 되지만 딱히 깔끔해 보이는 방법은 아니라서 마음에 들진 않는다.
/** FragmentA */ class FragmentA: Fragment() { ... setFragmentResultListener("keyA") { key, bundle -> // do something1 } ... }
/** FragmentB */ class FragmentB: Fragment() { ... setFragmentResultListener("keyB") { key, bundle -> setFragmentResult("keyA", bundle) // do something1 } ... }
/** FragmentC */ class FragmentC: Fragment() { ... setFragmentResult("keyB", bundleOf("result" to result)) ... }
참고
https://pluu.github.io/blog/android/2020/05/04/fragment-result/