FragmentResult API 스터디

김토끼·2022년 5월 18일
0

ActivityFragment간, 혹은 FragmentFragment 내의 다른 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)
  ...
}

하지만 동등한 레벨에 있는 FragmentAFragmentB 사이에 데이터를 전달할때는 바로 리스너를 등록할 수 없고 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) 리스너를 FragmentAFragmentB에서 동시에 리스너로 등록하면(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/

profile
방구석 김토끼🐰

0개의 댓글