[안드로이드] Fragment - Activity 데이터 전달

hee09·2022년 1월 19일
2
post-thumbnail
post-custom-banner

프래그먼트와 통신

사용자 이벤트에 올바르게 반응하거나 상태 정보(데이터)를 공유하려면 액티비티와 프래그먼트 또는 프래그먼트 간에 통신 채널이 있어야 합니다. 즉, 이미 Fragment와 FragmentManager에서 언급하였지만 서로 독립적으로 유지해야 하는데, 이를 위해서 서로 간에 직접 통신해서는 안되고 통신 채널을 사용해 데이터를 공유하는 방식으로 통신해야 합니다.

프래그먼트 라이브러리는 ViewModel 또는 Fragment Result API라는 두 가지 통신 옵션을 제공합니다. 권장되는 옵션은 사용 사례에 따라 다릅니다. 영구적인 데이터를 사용한다면 ViewModel을 사용해야 하고, 일회성의 결과로 Bundle에 배치할 수 있는 데이터라면 Fragment Result API를 사용하는 것이 좋습니다. 이제 코드를 보며 방법을 알아보겠습니다.


ViewModel을 사용하여 데이터 전달

ViewModel 객체는 UI 데이터를 저장하고 관리합니다. 따라서 ViewModel을 사용해서 다수의 프래그먼트끼리 데이터를 공유하거나 호스트 액티비티와 프래그먼트간에 데이터를 공유할 수 있습니다.

호스트 액티비티와 데이터 전달

프래그먼트에서 호스트 액티비티의 ViewModel을 접근할 수 있기에 사용할 수 있는 방법입니다. 우선 ViewModel 클래스를 하나 작성하겠습니다.

ViewModel 클래스

class ItemViewModel : ViewModel() {
    private val mutableSelectedItem = MutableLiveData<Item>()
    val selectedItem: LiveData<Item> get() = mutableSelectedItem

    fun selectItem(item: Item) {
        mutableSelectedItem.value = item
    }
}

데이터는 MutableLiveData 타입인 mutableSelectedItem에 저장됩니다. 그리고 이 값은 selectedItem에 의해 접근할 수 있습니다. 이제 ViewModel을 액티비티와 프래그먼트에서 생성하고 프래그먼트에서는 데이터를 저장(공유), 액티비티에서는 그 데이터를 접근하여 사용해보겠습니다.

액티비티, 프래그먼트 코드

// 액티비티 코드
class MainActivity : AppCompatActivity() {
    // activity-ktx의 viewModels()  위임 사용
    // 액티비티의 scope안에서 ViewModel을 획득
    private val viewModel: ItemViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel.selectedItem.observe(this, Observer { item ->
            // 획득한 아이템에 대한 액션 지정
            Toast.makeText(this, "item : $item", Toast.LENGTH_SHORT).show()
        })
    }
}

// 프래그먼트 코드
class ListFragment : Fragment() {
    // fragment-ktx의 activityViewModels()를 사용하여 액티비티 scope의
    // ViewMoel 획득
    private val viewModel: ItemViewModel by activityViewModels()

    // 아이템이 클릭되면 호출
    fun onItemClicked(item: Item) {
        // ViewModel의 selectItem을 호출하여 MutableLiveData.setValue 호출
        viewModel.selectItem(item)
    }
}
  • 만약 동일한 액티비티에 있는 프래그먼트끼리 데이터를 공유한다면 위와 똑같이 액티비티의 ViewModel을 사용하면 되고, 상위 프래그먼트 - 하위 프래그먼트의 관계에서 데이터를 공유한다면 가진다면 상위 프래그먼트를 ViewModel의 scope로 사용하면 됩니다.

Fragment Result API를 사용하기

Fragment Result API는 프래그먼트 간에 또는 호스트 액티비티와 프래그먼트간에 일회성 값을 전달하는 경우 사용하면 됩니다. 예를 들어 QR 코드를 읽고 이전 프래그먼트로 데이터를 다시 전달하는 프래그먼트가 있을 수 있을 수 있습니다. 이 API는 Fragment 1.3.0-alpha04부터 추가된 기능으로 FragmentManager는 FragmentResultOwner 인터페이스를 구현하고 있습니다.

FragmentResultOwner는 프래그먼트 사이에 전달하는 데이터를 관리하는 클래스입니다. 이 인터페이스에는 위와 같은 메소드들이 있는데, 이 메소드를 사용하여 리스너를 등록하고 데이터를 받아오면 됩니다. 따라서 이를 구현하는 FragmentManager는 프래그먼트 결과(데이터)의 중앙 저장소 역할을 수행합니다.


의존성 추가

우선 Android KTX의 Fragment KTX를 추가합니다. Android KTX는 코틀린의 확장 기능을 안드로이드 Jetpack과 안드로이드 라이브러리에 적용한 것입니다.

dependencies {
    implementation "androidx.fragment:fragment-ktx:1.4.0"
}

프래그먼트 간 데이터 전달

구조는 위와 같습니다.

  • 프래그먼트 B에서 프래그먼트 A로 데이터를 전달한다고 가정하겠습니다.

  • 데이터를 받는 프래그먼트 A는 FragmentManager에 setFragmentResultListener("requestKey") 메소드를 사용하여 주어진 requestKey로 리스너를 설정합니다.

  • 데이터를 전송하는 프래그먼트 B는 setFragmentResult("requestKey", Bundle) 메소드를 사용하여 주어진 requestKey로 데이터를 저장합니다. 그러면 데이터가 FragmentManager를 통해서 동일한 requestKey로 리스너를 등록한 프래그먼트 A로 전달됩니다.


이제 코드를 확인해보겠습니다.

우선, 결과를 수신하는 프래그먼트인 프래그먼트 A에서 결과 리스너를 설정합니다. 아래의 예제와 같이 프래그먼트 A의 FragmentManager에서 setFragmentResultListener()를 호출하면 됩니다.

데이터를 받는 프래그먼트

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // fragment-ktx의 코틀린 확장을 사용
    // 주어진 requestKey를 사용하여 FragmentResultListener를 설정
    setFragmentResultListener("requestKey") { requestKey, bundle ->
        // 데이터를 수신하고 생명주기 STARTED 상태가 되면 호출되는 콜백 메소드
        val result = bundle.getString("bundleKey")
        
        // Do something with the result
    }
}

데이터를 전송하는 프래그먼트인 프래그먼트 B는 requestKey를 사용하여 동일한 FragmentManager에 데이터를 설정합니다. setFragmentResult()를 사용하여 데이터를 설정할 수 있습니다.

데이터를 전달하는 프래그먼트

button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact
    // fragment-ktx의 코틀린 확장을 사용
    // setFragmentResult 메소드에 key와 Bundle을 사용해 데이터 전송
    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}

상위 프래그먼트 - 하위 프래그먼트 간 데이터 전달

하위 프래그먼트의 데이터를 상위 프래그먼트로 전달하려면 단지 상위 프래그먼트에서 setFragmentResultListener()를 호출할 때 getParentFragmentManager() 대신 getChildFragmentManager()를 사용하면 됩니다.

데이터를 전달받는 상위 프래그먼트

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // child Fragment Manager에 리스너 등록
    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))
}

호스트 액티비티에서 데이터 수신

호스트 액티비티에서 자신에게 속한 프래그먼트의 데이터를 수신받으려면 getSupportFragmentManager()를 사용하여 Fragment Manager에 결과 리스너를 설정합니다.

데이터를 전달받는 호스트 액티비티

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
        }
    }
}

참조
안드로이드 developer - Communication with fragments
Fragment간 데이터 전달 방법들
안드로이드 - ViewModel
안드로이드 - LiveData와 Observer
안드로이드 - Fragment와 FragmentManager
안드로이드 - Fragment Transaction

틀린 부분은 댓글로 남겨주시면 수정하겠습니다..!!

profile
되새기기 위해 기록
post-custom-banner

0개의 댓글