[프래그먼트 간 데이터 전달]
프래그먼트 간 데이터 전달
- 각 FragmentManager는 FragmentResultOwner를 구현한다.
- 즉, FragmentManger는 프래그먼트 결과의 중앙 저장소 역할을 수행한다.
- 이로 인해 프래그먼트가 서로 직접 참조할 필요 없이 프래그먼트 결과를 설정하고 결과를 수신하여 개별 프래그먼트 간에 통신이 가능하다.
프래그먼트 B에서 프래그먼트 A로 데이터를 다시 전달하려면 우선 결과를 수신하는 프래그먼트인 프래그먼트 A에서 결과 리스너( setResultListener( ) )를 설정한다.
1.프래그먼트 A
다음 예와 같이 프래그먼트 A의 FragmentManager에서 setFragmentResultListener() API를 호출한다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//kotlin extension "fragment-ktx artifact" 를 사용해라
setResultListener("requestKey") {key, bundle ->
val result = bundle.getString("bundleKey")
}
}
2.프래그먼트 B
그 다음 프래그먼트 B에서 동일한 requestKey를 사용하여 동일한 FragmentManger에 결과를 설정해야 한다. 이 작업은 setFragmentResult() API를 사용하여 실행할 수 있다.
button.setOnClickListener{
val result = "result"
//kotlin extension "fragment-ktx artifact" 를 사용해라
setResult("requestKey", bundleOf("bundleKey" to result)) //bundle.putExtra("bundleKey", result)
}
그러면 프래그먼트 A는 결과를 수신하고 '시작됨' 상태가 되면 리스너 콜백을 실행한다.
주어진 키에는 단일 리스너와 결과만 있을 수 있다. 동일한 키에 setResult()를 두 번 이상 호출하면 프래그먼트 B가 백 스택에서 사라지기 전에 시스템에서 가장 최근 결과를 프래그먼트 A에 전송한다. 결과를 수신할 관련 리스너 없이 결과를 설정하면 결과는 동일한 키로 리스너를 설정할 때까지 FragmentManger에 저장된다
리스너의 프래그먼트는 '시작됨' 상태여야 프래그먼트가 결과를 수신할 수 있다. 리스너가 결과를 수신하고 onFragmentResult() 콜백을 실행하면 결과는 삭제된다. 이 동작에는 다음 두 가지 의미가 있다.
- 백 스택의 프래그먼트는 표시되어 STARTED 상태가 될 때까지 결과를 수신하지 않는다.
- 결과를 수신하는 프래그먼트가 STARTED 상태인 경우 결과가 설정 되면 리스너의 콜백이 즉시 실행된다.
참고 : 프래그먼트가 DESTROYED 상태가 되면 이 프래그먼트에 리스너를 더 이상 설정할 수 없다.
실제 코드
1_1.MainActivity
//MainActivity
package com.example.fragmentdatatransaction
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.fragmentdatatransaction.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
var mBinding : ActivityMainBinding? = null
val binding get() = mBinding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val fragmentManager = supportFragmentManager
val fragTransaction = fragmentManager.beginTransaction()
fragTransaction
.add(R.id.sendFrag, SendFragment())
.add(R.id.receiveFrag, ReceiveFragment())
.commit()
}
}
1_2.activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/sendFrag"
android:layout_width="0dp"
android:layout_height="342dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0">
</FrameLayout>
<FrameLayout
android:id="@+id/receiveFrag"
android:layout_width="0dp"
android:layout_height="371dp"
android:layout_marginTop="1dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sendFrag"
app:layout_constraintVertical_bias="1.0">
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
2_1.Fragment1 (SendFragment)
package com.example.fragmentdatatransaction
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import androidx.core.os.bundleOf
import androidx.fragment.app.setFragmentResult
import com.example.fragmentdatatransaction.databinding.Fragment1Binding
class SendFragment : Fragment() {
private var _binding: Fragment1Binding? = null
private val binding get() = _binding!!
private lateinit var send_editText : EditText
private lateinit var send_button: Button
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = Fragment1Binding.inflate(inflater,container,false)
val view = binding.root
send_editText = binding.sendFragEtInput
send_button = binding.sendFragBtnSend
// Inflate the layout for this fragment
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
send_button.setOnClickListener {
var msgFunction : (String?) -> String= {
msg ->
var tmp = ""
if(msg?.length!! <= 0 || msg == null) tmp = "아무런 메세지를 수신하지 못했습니다."
else tmp = msg
tmp
}
var msg = msgFunction(send_editText.text.toString())
setFragmentResult("SendFragment", bundleOf("MessageKey" to msg))
}
}
}
2_2.fragment_1.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".SendFragment">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="전송 프래그먼트"
android:textSize="30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.502"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.261" />
<Button
android:id="@+id/sendFrag_btn_send"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:text="전송"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.507"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.603" />
<EditText
android:id="@+id/sendFrag_et_input"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="수신 프래그먼트에 보낼 메세지를 적어주세요"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.513"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.424" />
</androidx.constraintlayout.widget.ConstraintLayout>
3_1.Fragment2 (ReceiveFragment)
package com.example.fragmentdatatransaction
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.setFragmentResultListener
import com.example.fragmentdatatransaction.databinding.Fragment2Binding
class ReceiveFragment : Fragment() {
private var _binding : Fragment2Binding? = null
private val binding get() = _binding!!
private lateinit var receive_textView: TextView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = Fragment2Binding.inflate(inflater,container,false)
val view = binding.root
receive_textView = binding.receiveFragTvOutput
// Inflate the layout for this fragment
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setFragmentResultListener("SendFragment") { _, bundle ->
receive_textView.text = bundle.getString("MessageKey")
}
}
}
3_2.fragment_2.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app = "http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ReceiveFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="수신 프래그먼트"
android:textSize="40dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.243" />
<TextView
android:id="@+id/receiveFrag_tv_output"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textAlignment="center"
android:text="여기는 수신된 메세지가 표시되는 곳 입니다."
android:textSize="30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
상위 및 하위 프래그먼트 간 결과 전달
하위 프래그먼트의 결과를 상위 요소로 전달하려면 상위 프래그먼트가 setFragmentResultListener()를 호출할 때 getPaarentFragmentManager() 대신 getChildFragmentManager()를 사용해야 한다.
1.부모 프래그먼트
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
childFragmentManager.setResultListener("requestKey") {key, bundle ->
val result = bundle.getStrung("bundleKey")
}
}
하위 프래그먼트는 FragmentManager에 결과를 설정한다. 다음과 같이 프래그먼트가 STARTED 상태가 되면 상위 요소에서 결과를 수신한다.
2.자식 프래그먼트
button.setOnClickListener{
val result = "result"
setResult("requestKey", bundleOf("bundleKey" to result))
}
실제 코드
1_1.MainActivity
package com.example.parentfragmentdatatransaction
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.example.parentfragmentdatatransaction.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private var _binding : ActivityMainBinding? = null
private val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val fragmentManager = supportFragmentManager
val fragTransaction = fragmentManager.beginTransaction()
fragTransaction.add(R.id.parentFragment, ParentFragment()).commit()
}
}
1_2.activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="메인 액티비티"
android:textSize="40dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.047" />
<FrameLayout
android:id="@+id/parentFragment"
android:layout_width="360dp"
android:layout_height="600dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.492"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView3"
app:layout_constraintVertical_bias="1.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
2_1.ParentFragment - Receive의 역할을 한다.
package com.example.parentfragmentdatatransaction
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.setFragmentResultListener
import com.example.parentfragmentdatatransaction.databinding.FragmentParentBinding
class ParentFragment : Fragment() {
private var _binding: FragmentParentBinding? = null
private val binding get() = _binding!!
private lateinit var receciveTextView: TextView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentParentBinding.inflate(layoutInflater, container, false)
val view = binding.root
receciveTextView = binding.parentFragTvInput
val fragmentManager = childFragmentManager
val fragTransaction = fragmentManager.beginTransaction()
fragTransaction
.add(R.id.childFragmentLayout, ChildFragment())
.commit()
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
childFragmentManager.setFragmentResultListener("ParentKey", viewLifecycleOwner) {resultKey, bundle ->
receciveTextView.text = bundle.getString("MessageKey")
}
}
}
2_2.fragment_parent.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".ParentFragment"
android:background="@color/teal_200">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="부모 프래그먼트"
android:textSize="40dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.054" />
<TextView
android:id="@+id/parentFrag_tv_input"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="자식 프래그먼트로부터\n 수신받을 메시지 입니다."
android:textAlignment="center"
android:textSize="20dp"
app:layout_constraintBottom_toTopOf="@+id/childFragmentLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<FrameLayout
android:id="@+id/childFragmentLayout"
android:layout_width="340dp"
android:layout_height="400dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.483"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.951" />
</androidx.constraintlayout.widget.ConstraintLayout>
3_1.ChildFragment - Send 역할을 한다.
package com.example.parentfragmentdatatransaction
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import androidx.core.os.bundleOf
import androidx.fragment.app.setFragmentResult
import com.example.parentfragmentdatatransaction.databinding.FragmentChildBinding
class ChildFragment : Fragment() {
private var _binding : FragmentChildBinding? = null
private val binding get() = _binding!!
private lateinit var sendButton:Button
private lateinit var sendEditText: EditText
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentChildBinding.inflate(layoutInflater, container, false)
val view = binding.root
sendButton = binding.childFragBtnSend
sendEditText = binding.childFragEtSend
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sendButton.setOnClickListener {
var msgFunction : (String?) -> String = {
msg->
var tmp = ""
if(msg == null || msg?.length!! <= 0) tmp = "아무런 메시지를 수신받지 못했습니다."
else tmp = msg
tmp
}
val msg = msgFunction(sendEditText.text.toString())
parentFragmentManager.setFragmentResult("ParentKey", bundleOf("MessageKey" to msg))
}
}
}
3_2.fragment_child.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app = "http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ChildFragment"
android:background="@color/purple_700">
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="자식 프래그먼트"
android:textSize="40dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.287" />
<EditText
android:id="@+id/childFrag_et_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="부모 프래그먼트로 전송할 메세지를 적어주세요"
app:layout_constraintBottom_toTopOf="@+id/childFrag_btn_send"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.491"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
<Button
android:id="@+id/childFrag_btn_send"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:text="전송"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.669" />
</androidx.constraintlayout.widget.ConstraintLayout>
프래그먼트 결과 테스트 (아직 할 줄 모르겠음 ㅎㅎ)
FragmentScenario를 사용하여 setFragmentResult() 및 setFragmentResultListener() 호출을 테스트.
- launchFragmentInContainer
- launchFragment
를 사용하여 테스트 중인 프래그먼트의 시나리오를 만들고 테스트 중이 아닌 메서드를 수동으로 호출한다.
setrResultListener()를 테스트하려면 setResultListenr()를 호출하는 프래그먼트로 시나리오를 만든다. 그런 다음 setResult 를 직접 호출하여 다음과 같이 결과 확인한다.
@Test
fun testFragmentResultListener() {
val scenario = launchFragmentInContainer<ResultListenerFragment>()
scenario.onFragment {fragment ->
val expectedResult = "result"
fragment.parentFragmentManager.setResult("requestKey", bundleOf("bundleKey" to expectedResut))
assertThat(fragment.result).isEqualTo(expectedResult)
}
}
class ResultListenerFragment : Fragment() {
var result : String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setResultLisntener("requestKey") {key, bundle ->
result = bundle.getString("bundleKey")
}
}
}
setResult()를 테스트하려면 setResult()를 호출하는 프래그먼트로 시나리오를 만든다. 그런 다음 setResultListener()를 직접 호출하여 다음과 같이 결과를 확인한다.
@Test
fun testFragmentResult() {
val scenario = launchFragmentInContainer<ResultFragment>()
lateinit var actualResult: String?
scenario.onFragment{ fragment ->
fragment.parentFragmentManager.setResultListener("requestKey") {key, bundle ->
actualResult = bundle.getString("bundleKey")
}
onView(withId(R.id.result_button)).perform(click())
assertThat(actualResult).isEqualTo("result")
}
class ResultFragmet : Fragment(R.layout.fragment_result) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
view.findViewById(R.id.result_button).setOnClickListener{
val result = "result"
setResult("requestKey", bundleOf("bundleKey" to result))
}
}
}