Fragment간 데이터를 전달하는 방법은 여러가지가 있다.
- bundle과 FragmentManager로 전달
- Fragment Result API를 사용하여 Data 전달
- Fragment간 공통의 ViewModel(ex. HostActivity의 ViewModel)로 전달
- Jetpack의 Navigation에서 제공하는 safe-args로 전달
이러한 방법들이 있다.
1번 방법은 구글에 'Fragment간 Data전달'을 검색하면 옛날 방식의 예시 코드들이 많이 존재한다.
그리고 구글 공식 문서에서는 위 세 가지 방법(2~4)을 확인할 수 있다.
Google Docs1 (Communicating with fragments) 2, 3번 방법
Google Docs2 (Pass data between destinations) 4번 방법
위 방법들에 대해 직접 프로젝트를 만들어서 구현해보고 소개한다.
전송하려는 Fragment class
//PassBundleFragment는 본인이 전달하고자 하는 Fragment class
val bundle = Bundle()
bundle.putString("key", "value")
val passBundleBFragment = PassBundleBFragment()
passBundleBFragment.arguments = bundle parentFragmentManager.beginTransaction()
.replace(R.id.fragment_container_bundle, PassBundleFragment())
.commit()
받을 Fragment class
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
var result = arguments?.getString("key")
return inflater.inflate(R.layout.fragment_pass_b, container, false)
}
로 data를 전달할 수 있다.
작성일 기준 androidx.fragment:fragment-ktx:1.3.0 안정화버전이 나온 상태. 의존성 추가 후 사용할 수 있다.
//(AppModule)
dependencies {
...
implementation "androidx.fragment:fragment-ktx:1.3.0"
}
전송하려는 Fragment class
//PassBundleFragment는 본인이 전달하고자 하는 Fragment class
//fragment_container_bundle은 본인의 Fragment가 담겨있는 Container
button.setOnClickListener {
val result = "result"
setFragmentResult("requestKey", bundleOf("bundleKey" to result))
parentFragmentManager.beginTransaction()
.replace(R.id.fragment_container_bundle, PassBundleFragment())
.commit()
}
받을 Fragment class
setFragmentResultListener("requestKey") { requestKey, bundle ->
val result = bundle.getString("bundleKey")
// Do something with the result
}
"requestKey"는 사용하는 Fragment에서 어떤 listener에게 데이터를 전달할 지 결정하기 위한 식별자로 사용된다.
다른 fragment에서 같은 "requestKey"로 setFragmentResultListener를 적용할 경우 마지막에 적용한 listener가 호출된다.
하나의 Activity에서 container로 여러 Fragment를 이동하는 경우 사용할 수 있다.
Fragment가 Activity 하위에 위치하기 때문에 사용할 수 있는 아이디어!
Activity를 공유하는 여러 Fragment들은 Activity의 메모리를 공유할 수 있고 AAC의 ViewModel은 Activity의 lifecycle보다 오래 살아있는 것이 보장되기 때문에 안전하게 공통의 Activity의 ViewModel을 사용하여 데이터 전달이 가능하다.
구조를 그려보면 더 직관적인 이해가 가능하다. 공통의 메모리 데이터를 이용하여 서로의 데이터를 전달하는 방법.Code
class ViewModelPassActivity : AppCompatActivity() {
val viewModel : PassingViewModel by viewModels()
}
class PassAFragment : Fragment() {
val viewModel : PassingViewModel by activityViewModels()
}
class PassBFragment : Fragment() {
val viewModel: PassingViewModel by activityViewModels()
}
Activity에 Fragment들이 같은 데이터를 바라볼 수 있게 ViewModel 객체를 만들고 그 객체에 데이터를 전달하여 간단하게 Fragment 통신을 달성할 수 있다.
IPC기법 중 Shared Memory와 비슷한 아이디어라는 생각에 도달
실제 프로젝트에서는 당연히 구글에서 권장하는 방법을 사용하겠지만 IPC기법까지 생각이 도달하면 더 많은 종류의 Data전달 방법이 떠오를 수 있다. (IPC의 여러방법들)
IPC 기법들 :
- File 사용
- Message Queue
- Shared Memory (ViewModel을 이용한 데이터 전달 방법을 보고 비슷하다고 느낌)
- Pipe
- ...
이 방법은 Jetpack의 Navigation을 사용하는 프로젝트에서 적용하기 좋은 방법이다.
장점으로 navigation.xml에서 화면전환에 필요한 액션과 data를 정의할 수 있고, defaultValue도 정의가 가능하다. 또한, 전달하는 데이터 타입이 받는 곳의 데이터 타입과 다를 경우 compile 에러를 일으켜 의도하지 않은 상황이 발생할 확률을 줄여줬다.
간결하고 안정적인 처리의 장점이 있지만 다른 Data전달 방식보다 환경세팅하는 것이 많고 알아야 할 기능들이 많다. 단지 Fragment Data전달을 위해 Navigation을 쓰는 것은 비효율적이지만 전체 프로젝트에 Navigation을 적용했을 때 얻을 수 있는 이점이 많으니 참고하고 적용할 지 결정해야겠다.
여기서는 세팅을 제외하고 어떤 식으로 전달하는지 설명한다.
//Navigation.xml에서 이동할 화면에 대한 정의(action)와 전달할 데이터(argument)를 정의한다.
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/passArgsAFragment">
<fragment
android:id="@+id/passArgsAFragment"
android:name="com.gunt.fragmentdatapassexample.pass.passargs.PassArgsAFragment"
android:label="PassArgsAFragment" >
<action
android:id="@+id/passAToB"
app:destination="@id/passArgsBFragment"
app:launchSingleTop="true" />
<argument
android:name="argsString"
app:argType="string"
android:defaultValue=""/>
</fragment>
<fragment
android:id="@+id/passArgsBFragment"
android:name="com.gunt.fragmentdatapassexample.pass.passargs.PassArgsBFragment"
android:label="PassArgsBFragment" >
<action
android:id="@+id/passBToA"
app:destination="@id/passArgsAFragment"
app:launchSingleTop="true"
app:popUpTo="@id/nav_graph" />
<argument
android:name="argsString"
app:argType="string"
android:defaultValue=""/>
</fragment>
</navigation>
전송하려는 Fragment class
//액션에 본인이 보내려는 데이터를 담아 액션을 수행한다.
binding.btnSend.setOnClickListener {
val action = PassArgsAFragmentDirections.passAToB(binding.etText.text.toString())
findNavController().navigate(action)
}
받을 Fragment class
val args: PassArgsAFragmentArgs by navArgs()
binding.textView.text = args.argsString
위 방식으로 Fragment간 데이터 전달이 가능하다.
전체 예제 소스 : https://github.com/sysout-achieve/FragmentDataPassExample
.replace(R.id.fragment_container_bundle, PassBundleFragment())
.commit()
1 에서 프래그먼트 객체 생성과 동시에 번들을 넘긴뒤 2 번에서 프래그먼트 새로운 객체를 생성하면 데이터가 없는게 아닌지 궁금합니다 :)