Fragment간 데이터 전달시 Listener를 사용하는 것은 쉬운 방법 중 하나입니다. 혹은 Shared ViewModel을 이용해서 데이터 전달하는 방법도 있습니다. 이 섹션에서는 기본적인 방법인 Listener를 사용합니다. 예시에서 전달할 데이터 타입은 String 입니다.
class FragmentB : Fragment() {
interface OnResultListener {
fun onResult(value : String)
}
private var listener : OnResultListener? = null
....
fun setListener(listener : OnResultListener) {
this.listener = listener
}
private fun clickDone() {
listener?.onResult(//write result)
parentFragmentManager.popBackStack()
}
}
FragmentA보다 FragmentB를 먼저 살펴봅니다. 데이터를 전달하는 주체는 FragmnetB이므로 Listener 정의(OnResultListener)를 합니다. FragmentB를 사용하는 쪽에서는 setListener(OnResultListener) 함수를 사용해서 데이터를 전달받습니다.
class FragmentA : Fragment(), FragmentB.OnResultListener {
...
override fun onViewCreated(view: View, savedInstanceState:Bundle?) {
...
val restoreValue = arguments?.getString(keyRestore)
if (restoreValue != null) {
...
}
}
private fun showFragmenB() {
parentFragmentManager.commit {
replace(R.id.container, FragmentB().apply{
setListener(this@FragmentA)
})
addToBackStack(null)
}
}
override fun onResult(value : String) {
if(isVisible) {
...
} else {
arguments = (arguments ?: Bundle()).also{
it.putString(keyRestore, value)
}
}
}
companion object {
private const val keyRestore = "resultRestore"
}
}
위 예제는 FragmentA에서 FragmetnB 호출하는 예제입니다. 단순하게 Fragment의 OnResultListener를 호출하면 FragmentA는 FragmentTransaction#replace(Int, Fragment) 사용으로 FragmentManager에 추가되어 있지않아서 Fragment#isVisible()이 false가 됩니다. 그래서 Fragment View가 생성된 후, FragmentB로부터 전달된 데이터를 노출하기 위해서 Fragment의 arguments에 데이터를 임시 저장합니다.
두 Fragment 간에 또는 Fragment와 호스트 활동 간에 일회성 값을 전달해야 할 수 있습니다.
Fragment 1.3.0-alpha04 이후부터 FragmentManager는 FragmentResultOwner를 구현합니다.(상속을 통해)
FragmentManager가 Fragment 결과의 central store로 동작할 수 있다는 것을 의미합니다. 이제 component간 직접 참조하지 않고 Fragment의 result를 설정하고 결과를 수신 및 수신 대기해서 서로 데이터를 전달할 수 있게 됐습니다.
Fragment B에서 Fragment A로 데이터를 다시 전달하려면 우선 결과를 수신하는 FragmentA에서 결과 리스너를 설정합니다. 다음 예와 같이 Fragment A의 FragmentManager에서 setFragmentResultListener()를 호출합니다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Use the Kotlin extension in the fragment-ktx artifact
setFragmentResultListener("requestKey") { requestKey, bundle ->
// We use a String here, but any type that can be put in a Bundle is supported
val result = bundle.getString("bundleKey")
// Do something with the result
}
}
결과를 생성하는 Fragment인 Fragment B에서 동일한 requestKey를 사용하여 동일한 FragmentManager에 결과를 설정해야합니다. 이 작업은 setFragmentResult() API를 사용하여 실행할 수 있습니다.
button.setOnClickListener {
val result = "result"
//User the Kotlin extension in the fragment-ktx artifact
setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}
Fragment A가 결과를 수신하고 STARTED 상태가 되면 리스터 콜백을 실행합니다. 주어진 키에는 단일 리스너와 결과만 있을 수 있습니다. 동일한 키에 setFragmentResult()를 두 번이상 호출하는경우 그리고 리스너가 STARTED 상태가 아닌 경우 시스템은 대기 중인 결과를 업데이트된 결과로 바꿉니다.
결과를 수신할 관련 리스너 없이 결과를 설정하면 동일한 키로 리스너를 설정할 때까지 FragmentManager에 저장됩니다. 리스너가 결과를 수신하고 onFragmentResult()콜백을 실행하면 결과를 삭제됩니다. 이 동작이 의미하는 것은
- 백 스택의 프래그먼트는 표시되어 STARTED 상태가 될 때까지 결과를 수신하지 않습니다.
- 결과를 수신 대기하는 프래그먼트가 STARTED 상태인 경우 결과가 설정되면 리스너의 콜백이 즉시 실행됩니다.