프래그먼트를 재사용하려면 자체 레이아웃과 동작을 정의하는 완전히 독립된 구성요소로 빌드합니다. 이러한 재사용 가능한 프래그먼트를 정의한 후에는 활동 및 애플리케이션 로직과 연결하여 전반적인 복합 UI를 실현할 수 있습니다.
사용자 이벤트에 올바르게 반응하고 상태 정보를 공유하려면 활동과 활동의 프래그먼트 간 또는 두 개 이상의 프래그먼트 간에 통신 채널이 있어야 할 때가 많습니다. 프래그먼트를 독립적으로 유지하려면 프래그먼트가 다른 프래그먼트 또는 호스트 활동과 직접 통신하지 않도록 합니다.
Fragment
라이브러리는 공유 ViewModel
및 Fragment Result API라는 두 가지 통신 옵션을 제공합니다. 권장되는 옵션은 사용 사례에 따라 다릅니다. 영구 API를 맞춤 API와 공유하려면 ViewModel
을 사용하세요. Bundle
에 배치할 수 있는 데이터가 포함된 일회성 결과의 경우 Fragment Result API를 사용합니다.
다음 섹션에서는 ViewModel
및 Fragment Result API를 사용하여 프래그먼트와 활동 간에 통신하는 방법을 보여 줍니다.
ViewModel을 사용하여 데이터 공유
ViewModel
은 여러 프래그먼트 간에 또는 프래그먼트와 호스트 활동 간에 데이터를 공유해야 할 때 적합합니다.
ViewModel
객체는 UI 데이터를 저장하고 관리합니다. ViewModel
에 관한 자세한 내용은 ViewModel 개요를 참고하세요.
호스트 활동과 데이터 공유
때에 따라 프래그먼트와 호스트 활동 간에 데이터를 공유해야 할 수 있습니다. 예를 들어 프래그먼트 내의 상호작용에 기반하여 전역 UI 구성요소를 전환해야 할 수 있습니다.
다음 ItemViewModel
을 고려하세요.
Kotlin
class ItemViewModel : ViewModel() { private val mutableSelectedItem = MutableLiveData<Item>() val selectedItem: LiveData<Item> get() = mutableSelectedItem fun selectItem(item: Item) { mutableSelectedItem.value = item } }
Java
public class ItemViewModel extends ViewModel { private final MutableLiveData<Item> selectedItem = new MutableLiveData<Item>(); public void selectItem(Item item) { selectedItem.setValue(item); } public LiveData<Item> getSelectedItem() { return selectedItem; } }
이 예에서 저장된 데이터는 MutableLiveData
클래스로 래핑됩니다.
LiveData
는 수명 주기를 인식하는 관찰 가능한 데이터 홀더 클래스입니다. MutableLiveData
를 사용하면 값을 변경할 수 있습니다. LiveData
에 관한 자세한 내용은 LiveData 개요를 참고하세요.
프래그먼트와 호스트 활동은 모두 활동을 ViewModelProvider
생성자에 전달하여 활동 범위가 있는 ViewModel
의 공유 인스턴스를 검색할 수 있습니다. ViewModelProvider
는 ViewModel
을 인스턴스화하거나 이미 존재하는 경우 ViewModel을 검색합니다. 두 구성요소 모두 이 데이터를 관찰하고 수정할 수 있습니다.
Kotlin
class MainActivity : AppCompatActivity() { // Using the viewModels() Kotlin property delegate from the activity-ktx // artifact to retrieve the ViewModel in the activity scope. private val viewModel: ItemViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel.selectedItem.observe(this, Observer { item -> // Perform an action with the latest item data. }) } } class ListFragment : Fragment() { // Using the activityViewModels() Kotlin property delegate from the // fragment-ktx artifact to retrieve the ViewModel in the activity scope. private val viewModel: ItemViewModel by activityViewModels() // Called when the item is clicked. fun onItemClicked(item: Item) { // Set a new item. viewModel.selectItem(item) } }
Java
public class MainActivity extends AppCompatActivity { private ItemViewModel viewModel; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(ItemViewModel.class); viewModel.getSelectedItem().observe(this, item -> { // Perform an action with the latest item data. }); } } public class ListFragment extends Fragment { private ItemViewModel viewModel; @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); viewModel = new ViewModelProvider(requireActivity()).get(ItemViewModel.class); ... items.setOnClickListener(item -> { // Set a new item. viewModel.select(item); }); } }
프래그먼트 간 데이터 공유
동일한 활동에 있는 두 개 이상의 프래그먼트는 서로 통신해야 할 때가 많습니다. 예를 들어 목록을 표시하는 프래그먼트 하나와 사용자가 목록에 다양한 필터를 적용할 수 있는 프래그먼트 하나를 생각해 보세요. 이 사례를 구현하는 방법은 프래그먼트가 직접 통신하면 간단하지만 그러면 더 이상 독립적이지 않습니다. 또한 두 프래그먼트는 다른 프래그먼트가 아직 만들어지지 않았거나 표시되지 않은 시나리오를 처리해야 합니다.
이러한 프래그먼트는 이 통신을 처리하기 위해 활동 범위를 사용하여 ViewModel
을 공유할 수 있습니다. 이러한 방식으로 ViewModel
을 공유하면 프래그먼트는 서로에 관해 알 필요가 없으며 활동은 통신을 용이하게 하기 위해 어떤 작업도 할 필요가 없습니다.
다음 예는 두 프래그먼트가 공유 ViewModel
을 사용하여 통신하는 방법을 보여 줍니다.
Kotlin
class ListViewModel : ViewModel() { val filters = MutableLiveData<Set<Filter>>() private val originalList: LiveData<List<Item>>() = ... val filteredList: LiveData<List<Item>> = ... fun addFilter(filter: Filter) { ... } fun removeFilter(filter: Filter) { ... } } class ListFragment : Fragment() { // Using the activityViewModels() Kotlin property delegate from the // fragment-ktx artifact to retrieve the ViewModel in the activity scope. private val viewModel: ListViewModel by activityViewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { viewModel.filteredList.observe(viewLifecycleOwner, Observer { list -> // Update the list UI. } } } class FilterFragment : Fragment() { private val viewModel: ListViewModel by activityViewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { viewModel.filters.observe(viewLifecycleOwner, Observer { set -> // Update the selected filters UI. } } fun onFilterSelected(filter: Filter) = viewModel.addFilter(filter) fun onFilterDeselected(filter: Filter) = viewModel.removeFilter(filter) }
Java
public class ListViewModel extends ViewModel { private final MutableLiveData<Set<Filter>> filters = new MutableLiveData<>(); private final LiveData<List<Item>> originalList = ...; private final LiveData<List<Item>> filteredList = ...; public LiveData<List<Item>> getFilteredList() { return filteredList; } public LiveData<Set<Filter>> getFilters() { return filters; } public void addFilter(Filter filter) { ... } public void removeFilter(Filter filter) { ... } } public class ListFragment extends Fragment { private ListViewModel viewModel; @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); viewModel = new ViewModelProvider(requireActivity()).get(ListViewModel.class); viewModel.getFilteredList().observe(getViewLifecycleOwner(), list -> { // Update the list UI. }); } } public class FilterFragment extends Fragment { private ListViewModel viewModel; @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { viewModel = new ViewModelProvider(requireActivity()).get(ListViewModel.class); viewModel.getFilters().observe(getViewLifecycleOwner(), set -> { // Update the selected filters UI. }); } public void onFilterSelected(Filter filter) { viewModel.addFilter(filter); } public void onFilterDeselected(Filter filter) { viewModel.removeFilter(filter); } }
두 프래그먼트는 모두 호스트 활동을 ViewModelProvider
의 범위로 사용합니다. 프래그먼트가 동일한 범위를 사용하므로 동일한 ViewModel
인스턴스를 수신하여 서로 통신할 수 있습니다.
상위 프래그먼트와 하위 프래그먼트 간 데이터 공유
하위 프래그먼트로 작업할 때 상위 프래그먼트와 그 하위 프래그먼트는 서로 데이터를 공유해야 할 수도 있습니다. 이러한 프래그먼트 간에 데이터를 공유하려면 다음 예와 같이 상위 프래그먼트를 ViewModel
범위로 사용하세요.
Kotlin
class ListFragment: Fragment() { // Using the viewModels() Kotlin property delegate from the fragment-ktx // artifact to retrieve the ViewModel. private val viewModel: ListViewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { viewModel.filteredList.observe(viewLifecycleOwner, Observer { list -> // Update the list UI. } } } class ChildFragment: Fragment() { // Using the viewModels() Kotlin property delegate from the fragment-ktx // artifact to retrieve the ViewModel using the parent fragment's scope private val viewModel: ListViewModel by viewModels({requireParentFragment()}) ... }
Java
public class ListFragment extends Fragment { private ListViewModel viewModel; @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { viewModel = new ViewModelProvider(this).get(ListViewModel.class); viewModel.getFilteredList().observe(getViewLifecycleOwner(), list -> { // Update the list UI. } } } public class ChildFragment extends Fragment { private ListViewModel viewModel; @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { viewModel = new ViewModelProvider(requireParentFragment()).get(ListViewModel.class); ... } }
탐색 그래프로 ViewModel의 범위 지정
Navigation 라이브러리를 사용하고 있다면 ViewModel
의 범위를 대상 NavBackStackEntry
의 수명 주기로 지정할 수도 있습니다. 예를 들어 ViewModel
의 범위를 ListFragment
의 NavBackStackEntry
로 지정할 수 있습니다.
Kotlin
class ListFragment: Fragment() { // Using the navGraphViewModels() Kotlin property delegate from the fragment-ktx // artifact to retrieve the ViewModel using the NavBackStackEntry scope. // R.id.list_fragment == the destination id of the ListFragment destination private val viewModel: ListViewModel by navGraphViewModels(R.id.list_fragment) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { viewModel.filteredList.observe(viewLifecycleOwner, Observer { item -> // Update the list UI. } } }
Java
public class ListFragment extends Fragment { private ListViewModel viewModel; @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { NavController navController = NavHostFragment.findNavController(this); NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.list_fragment) viewModel = new ViewModelProvider(backStackEntry).get(ListViewModel.class); viewModel.getFilteredList().observe(getViewLifecycleOwner(), list -> { // Update the list UI. } } }
ViewModel
의 범위를 NavBackStackEntry
로 지정하는 방법에 관한 자세한 내용은 Navigation 구성요소와 프로그래매틱 방식으로 상호작용을 참고하세요.
Fragment Result API를 사용하여 결과 가져오기
경우에 따라 두 프래그먼트 간에 또는 프래그먼트와 호스트 활동 간에 일회성 값을 전달해야 할 수 있습니다. 예를 들어 QR 코드를 읽고 이전 프래그먼트로 데이터를 다시 전달하는 프래그먼트가 있을 수 있습니다.
Fragment 버전 1.3.0 이상에서 각 FragmentManager
는 FragmentResultOwner
를 구현합니다.
즉, FragmentManager
는 프래그먼트 결과의 중앙 저장소 역할을 할 수 있습니다. 이번 변경으로 구성요소가 서로를 직접 참조하지 않아도 프래그먼트 결과를 설정하고 이러한 결과를 수신 대기하여 구성요소가 서로 통신할 수 있습니다.
프래그먼트 간 결과 전달
프래그먼트 B에서 프래그먼트 A로 데이터를 다시 전달하려면 우선, 결과를 수신하는 프래그먼트인 프래그먼트 A에서 결과 리스너를 설정합니다. 다음 예와 같이 프래그먼트 A의 FragmentManager
에서 setFragmentResultListener()
를 호출합니다.
Kotlin
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. } }
Java
@Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); getParentFragmentManager().setFragmentResultListener("requestKey", this, new FragmentResultListener() { @Override public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle bundle) { // We use a String here, but any type that can be put in a Bundle is supported. String result = bundle.getString("bundleKey"); // Do something with the result. } }); }
결과를 생성하는 프래그먼트인 프래그먼트 B에서 동일한 requestKey
를 사용하여 동일한 FragmentManager
에 결과를 설정합니다. 이 작업은 setFragmentResult()
API를 사용하여 실행할 수 있습니다.
Kotlin
button.setOnClickListener { val result = "result" // Use the Kotlin extension in the fragment-ktx artifact. setFragmentResult("requestKey", bundleOf("bundleKey" to result)) }
Java
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Bundle result = new Bundle(); result.putString("bundleKey", "result"); getParentFragmentManager().setFragmentResult("requestKey", result); } });
그러면 프래그먼트 A가 결과를 수신하고 STARTED
상태가 되면 리스너 콜백을 실행합니다.
주어진 키에는 단일 리스너와 결과만 있을 수 있습니다. 동일한 키에 setFragmentResult()
를 두 번 이상 호출하는 경우이자 리스너가 STARTED
상태가 아닌 경우 시스템은 대기 중인 결과를 업데이트된 결과로 바꿉니다.
결과를 수신할 관련 리스너 없이 결과를 설정하면 결과는 동일한 키로 리스너를 설정할 때까지 FragmentManager
에 저장됩니다. 리스너가 결과를 수신하고 onFragmentResult()
콜백을 실행하면 결과는 삭제됩니다. 이 동작에는 다음 두 가지 의미가 있습니다.
- 백 스택의 프래그먼트는 표시되어
STARTED
상태가 될 때까지 결과를 수신하지 않습니다. - 결과를 수신 대기하는 프래그먼트가
STARTED
상태인 경우 결과가 설정되면 리스너의 콜백이 즉시 실행됩니다.
프래그먼트 결과 테스트
FragmentScenario
를 사용하여 setFragmentResult()
및 setFragmentResultListener()
호출을 테스트합니다.
launchFragmentInContainer
또는 launchFragment
를 사용하여 테스트 중인 프래그먼트의 시나리오를 만들고 테스트 중이 아닌 메서드를 수동으로 호출합니다.
setFragmentResultListener()
를 테스트하려면 setFragmentResultListener()
를 호출하는 프래그먼트로 시나리오를 만듭니다. 그런 다음 setFragmentResult()
를 직접 호출하여 다음과 같이 결과를 확인합니다.
@Test
fun testFragmentResultListener() {
val scenario = launchFragmentInContainer<ResultListenerFragment>()
scenario.onFragment { fragment ->
val expectedResult = "result"
fragment.parentFragmentManager.setFragmentResult("requestKey", bundleOf("bundleKey" to expectedResult))
assertThat(fragment.result).isEqualTo(expectedResult)
}
}
class ResultListenerFragment : Fragment() {
var result : String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Use the Kotlin extension in the fragment-ktx artifact.
setFragmentResultListener("requestKey") { requestKey, bundle ->
result = bundle.getString("bundleKey")
}
}
}
setFragmentResult()
를 테스트하려면 setFragmentResult()
를 호출하는 프래그먼트로 시나리오를 만듭니다. 그런 다음 setFragmentResultListener()
를 직접 호출하여 다음과 같이 결과를 확인합니다.
@Test
fun testFragmentResult() {
val scenario = launchFragmentInContainer<ResultFragment>()
lateinit var actualResult: String?
scenario.onFragment { fragment ->
fragment.parentFragmentManager
.setFragmentResultListener("requestKey") { requestKey, bundle ->
actualResult = bundle.getString("bundleKey")
}
}
onView(withId(R.id.result_button)).perform(click())
assertThat(actualResult).isEqualTo("result")
}
class ResultFragment : Fragment(R.layout.fragment_result) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
view.findViewById(R.id.result_button).setOnClickListener {
val result = "result"
// Use the Kotlin extension in the fragment-ktx artifact.
setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}
}
}
상위 및 하위 프래그먼트 간 결과 전달
하위 프래그먼트의 결과를 상위 요소에 전달하려면 setFragmentResultListener()
를 호출할 때 getParentFragmentManager()
대신 상위 프래그먼트의 getChildFragmentManager()
를 사용합니다.
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Set the listener on the child fragmentManager. childFragmentManager.setFragmentResultListener("requestKey") { key, bundle -> val result = bundle.getString("bundleKey") // Do something with the result. } }
Java
@Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Set the listener on the child fragmentManager. getChildFragmentManager() .setFragmentResultListener("requestKey", this, new FragmentResultListener() { @Override public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle bundle) { String result = bundle.getString("bundleKey"); // Do something with the result. } }); }
하위 프래그먼트는 FragmentManager
에 결과를 설정합니다. 그러면 다음과 같이 프래그먼트가 STARTED
상태가 되면 상위 프래그먼트에서 결과를 수신합니다.
Kotlin
button.setOnClickListener { val result = "result" // Use the Kotlin extension in the fragment-ktx artifact. setFragmentResult("requestKey", bundleOf("bundleKey" to result)) }
Java
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Bundle result = new Bundle(); result.putString("bundleKey", "result"); // The child fragment needs to still set the result on its parent fragment manager. getParentFragmentManager().setFragmentResult("requestKey", result); } });
호스트 활동에서 결과 수신
호스트 활동에서 프래그먼트 결과를 수신하려면 getSupportFragmentManager()
를 사용하여 프래그먼트 관리자에서 결과 리스너를 설정합니다.
Kotlin
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) supportFragmentManager .setFragmentResultListener("requestKey", this) { 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. } } }
Java
class MainActivity extends AppCompatActivity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); getSupportFragmentManager().setFragmentResultListener("requestKey", this, new FragmentResultListener() { @Override public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle bundle) { // We use a String here, but any type that can be put in a Bundle is supported. String result = bundle.getString("bundleKey"); // Do something with the result. } }); } }