[Android] Fragment 통신(데이터 전달)

neoneoneo·2024년 3월 28일
0

android

목록 보기
7/16

프래그먼트 통신 왜 해?

프래그먼트를 재사용하면 보다 효율적으로 리소스를 관리할 수 있게 된다.

이 프래그먼트를 사용하여 앱을 개발할때, 사용자 이벤트에 적절하게 반응하고 그 상태를 공유하기 위해서는 프래그먼트와 액티비티 간 통신 채널이 필요하다.

안드로이드 공식 문서에서 소개하는 프래그먼트 통신에는 2가지 방법이 있다.

1. ViewModel 사용

추천 케이스 | ViewModel이 여러 프래그먼트 간 또는 프래그먼트와 액티비티 간에 데이터를 공유해야하는 경우

참고로 ViewModel 객체에는 UI 데이터가 저장되어 있다.

host activity와 데이터 공유하기

프래그먼트 내의 상호작용에따라 전역 UI 구성요소를 전환해야 할 때와 같이 프래그먼트-액티비티 간 데이터 공유가 필요한 순간이 있다.

아래 ItemViewModel 클래스는 ViewModel 클래스를 확장하여 데이터를 관리하는 뷰모델을 정의한다.

class ItemViewModel : ViewModel() {
    private val mutableSelectedItem = MutableLiveData<Item>()
    val selectedItem: LiveData<Item> get() = mutableSelectedItem
    fun selectItem(item: Item) {
        mutableSelectedItem.value = item
    }
}
  • mutableSelectedItem : 선택된 아이템을 저장하는 변수
    • 변경될 수 있는 LiveData 타입
      • LiveData : 아키텍처 컴포넌트에서 사용되는 데이터 홀더
        • 데이터가 변경될 때마다 UI에 알려주는 역할
  • selectedItem : mutableSelectedItem의 값을 읽기 전용 형태로 변환한 변수
    • 외부에서 값을 변경할 수 없다.
    • UI에서 선택된 아이템을 관찰하기 위한 용도
  • selectItem(item: Item) : 선택된 아이템을 설정하는 함수
    • 뷰모델에 저장되어 있는 선택된 아이템을 갱신하는 용도

아래의 코드에서는 MainActivity에서 이를 어떻게 활용할 수 있는지 보여준다.

class MainActivity : AppCompatActivity() {
    private val viewModel: ItemViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel.selectedItem.observe(this, Observer { item ->
        })
    }
}
class ListFragment : Fragment() {
    private val viewModel: ItemViewModel by activityViewModels()
    fun onItemClicked(item: Item) {
        viewModel.selectItem(item)
    }
}
  • MainActivity 클래스
    • 앱의 시작점
    • ItemViewModel 인스턴스를 가져온다.
    • onCreate()에서 selectedItem를 관찰하여 데이터가 변경될 때 UI를 업데이트 할 수 있다.
  • ListFragment 클래스
    • 화면을 나타내는 프래그먼트
    • ItemViewModel 인스턴스를 가져온다.
    • onItemClicked()에서 사용자가 선택한 항목을 ViewModel에 설정한다.

위 사례처럼, 프래그먼트와 액티비티 간의 연결을 할 때에는 ViewModel을 사용하면된다. 이때 두 항목은 같은 ViewModel을 공유하므로써 데이터를 쉽게 전달하고 관리할 수 있게 된다.

프래그먼트 간 데이터 공유

프래그먼트 간 통신에 대한 Activity 범위를 사용하여 viewModel을 공유할 수 있다.

아래 코드는 앱에서 필터링된 목록을 표시하고 관리하는 ViewModel에 대한 예시이다.

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() {
    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)
}
  • ListViewModel 클래스
    • 필터링된 목록을 관리하는 ViewModel
    • filters : 필터의 집합을 저장하는 MutableLiveData
    • originalList와 filteredList : 원본 목록과 필터링된 목록을 나타내는 LiveData
    • addFilter()와 removeFilter() : 필터를 추가, 제거하는 용도
  • ListFragment 클래스
    • viewModel에 ListViewModel 인스턴스를 저장한다.
    • filteredList로 viewModel을 관찰하여 목록이 변경될 때마다 UI를 업데이트 한다.
  • FilterFragment 클래스
    • viewModel에 ListViewModel 인스턴스를 저장한다.
    • filters로 viewModel을 관찰하여 선택된 필터가 변경될 때마다 UI를 업데이트한다.
    • onFilterSelected()와 onFilterDeselected() : 필터를 선택하거나 취소하는 용도

이렇게 목록-필터 프래그먼트 간의 통신이 이루어질 수 있으며, 통신으로부터 UI 업데이트를 해준다거나 데이터를 갱신한다거나 할 수 있다.

상위 프래그먼트 - 하위 프래그먼트 간 데이터 공유는?

class ListFragment: Fragment() {
    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 parent fragment's scope
    private val viewModel: ListViewModel by viewModels({requireParentFragment()}) //이 부분!
    ...
}

2. Fragment Result API 사용

만약 통신이 일회성으로 필요하게 되면 어떻게 할까?

예를들어, QR 코드를 읽어 이전 프래그먼트 A로 데이터를 전달해야 하는 프래그먼트 B가 있다고 상상해보자. QR 코드가 사라진 뒤에는 계속해서 통신 채널을 유지할 필요가 없으므로 한 번만 데이터를 넘겨주면 된다.

프래그먼트 간 결과 전달

먼저 데이터를 수신하는 프래그먼트에 결과 리스너(setFragmentResultListener)를 설정해야한다.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setFragmentResultListener("requestKey") { requestKey, bundle -> //이 부분
        val result = bundle.getString("bundleKey")
        // Do something with the result.
    }
}
button.setOnClickListener {
    val result = "result"
    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}

  • B에서는 setFragmentResult 함수를 사용하여 FragmentManager로 값을 넘기고, A는 리스너로 요청하여 해당 값을 받아온다.
    • 이 과정은 FragmentManager가 중재한다.

상위 및 하위 프래그먼트 간 결과 전달은?

하위 프래그먼트 -> 상위 프래그먼트로의 전달은 childFragmentManager을 호출하여 처리한다.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    childFragmentManager.setFragmentResultListener("requestKey") { key, bundle ->
        val result = bundle.getString("bundleKey")
        // Do something with the result.
    }
}
button.setOnClickListener {
    val result = "result"
    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}

  • 여기서도 마찬가지로 데이터를 넘기는 쪽(하위 프래그먼트)에서는 setFragmentResult 함수를 사용하여 데이터를 넘긴다.
  • 상위 프래그먼트도 setFragmentResultListener 함수를 통해 결과를 요청하여 받아온다.
  • 이 과정은 ChildFragmentManager가 중재한다.

어.. 근데 activity에서 프래그먼트로 결과를 받아오려면?? 일회성으로 뭔가를 받아오고 싶을 수도 있잖아..!

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportFragmentManager
                .setFragmentResultListener("requestKey", this) { requestKey, bundle ->
            val result = bundle.getString("bundleKey")
            // Do something with the result.
        }
    }
}
  • supportFragmentManager 함수를 사용하면 프래그먼트 매니저에게 결과 리스너를 요청하여 받아올 수 있다.

마치며,,,

이렇게 프래그먼트의 두 가지 통신 방법에 대해 알아보았다.

개발자가 사용 목적에 따라 취사선택하면 되지만, 공식문서에서는

  • 영구 API를 맞춤 API와 공유하려면 ViewModel
  • Bundle에 배치할 수 있는 데이터가 포함된 일회성 result를 다뤄야 하는 경우에는 Fragment Result API 사용을 권장하고 있다.

reference

공식 문서


[TIL-240328]

0개의 댓글