안드로이드

[프래그먼트 간 데이터 전달]

란서 2021. 7. 5. 16:14

프래그먼트 간 데이터 전달

  • 각 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() 콜백을 실행하면 결과는 삭제된다. 이 동작에는 다음 두 가지 의미가 있다.
  1. 백 스택의 프래그먼트는 표시되어 STARTED 상태가 될 때까지 결과를 수신하지 않는다.
  2. 결과를 수신하는 프래그먼트가 STARTED 상태인 경우 결과가 설정 되면 리스너의 콜백이 즉시 실행된다.
참고 : 프래그먼트가 DESTROYED 상태가 되면 이 프래그먼트에 리스너를 더 이상 설정할 수 없다.

 

그림 1.   FragmentManager 를 사용하여 프래그먼트 A로 데이터를 전송하는 프래그먼트 B

 

 

 

실제 코드

 

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

그림 2.   FragmentManager 를 사용하여 상위 요소에 결과를 전송할 수 있는 하위 프래그먼트

 

 

실제 코드

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

부모자식프래그먼트간 통신.zip
0.14MB
프래그먼트 간 통신.zip
0.14MB