본문 바로가기
Android Studio

[Android 앱개발 숙련] 프래그먼트의 데이터 전달 전체코드 + 주석 (# )

by 젼젼39 2024. 4. 12.

1-1. 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/frameLayout"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="10dp"
        android:layout_marginEnd="10dp"
        app:layout_constraintBottom_toTopOf="@+id/fragment1_btn"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <Button
        android:id="@+id/fragment1_btn"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="Frag1"
        android:textAllCaps="false"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/fragment2_btn"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/frameLayout" />

    <Button
        android:id="@+id/fragment2_btn"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="Frag2"
        android:textAllCaps="false"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/fragment1_btn"
        app:layout_constraintTop_toBottomOf="@+id/frameLayout" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

1-2. MainActivity.kt

// [3] SecondFragment -> Activity (FragmentDataListener 인터페이스 구현.. 아래줄)
class MainActivity : AppCompatActivity(), FragmentDataListener {

    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        //변경한 코드. 프래그먼트의 데이터 전달, 보내는 부분
        binding.run {
            fragment1Btn.setOnClickListener {
                // [1] Activity -> FirstFragment
                val dataToSend = "Hello First Fragment! \n From Activity"
                //FragmentBtn1 클릭 리스너 안에서 FirstFragment의 인스턴스를 생성하고,
                //  newInstance 메소드에 데이터를 설정해 생성된 프래그먼트에 전달함
                val fragment = FirstFragment.newInstance(dataToSend)
                //생성한 프래그먼트를 setFragment 안에 넣어서 프래그먼트를 설정, 화면에 표시함
                setFragment(fragment)
            }
            fragment2Btn.setOnClickListener {
                // [1] Activity -> SecondFragment
                val dataToSend = "Hello Second Fragment!\n From Activity"
                val fragment = SecondFragment.newInstance(dataToSend)
                setFragment(fragment)
            }
        }

        //프로그램이 시작될 때 FirstFragment를 뿌리라고 코드 작성함
        setFragment(FirstFragment())
    }

    private fun setFragment(frag: Fragment) {
        //동적
        //setFragment가 불리면 전달받은 프래그먼트로 전환하는 것
        supportFragmentManager.commit {
            //frameLayout에 전달받은 프래그먼트를 뿌릴 것임
            replace(R.id.frameLayout, frag)
            setReorderingAllowed(true)
            addToBackStack("")
        }
    }

    // [3] SecondFragment -> Activity
    //MainActivity에서 해당 인터페이스 상속받고, 그 안에 구현체도 있어야 함...
    // FragmentDataListener 리스너를 걸어두고, 그 리스너를 통해 onDataReceived가 발생하면
    //   그 안으로 스트링 데이터가 들어올거고, 그걸 토스트로 띄울 것임. (onDataReceived를 override 해서 구현체 만들었음)
    override fun onDataReceived(data: String) {
        // Fragment에서 받은 데이터를 처리
        Toast.makeText(this, data, Toast.LENGTH_SHORT).show()
    }
}

 

 

2-1. fragment_first.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:background="#F19B9B"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tvFrag1Text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="프래그먼트 1"
        android:textAllCaps="false"
        android:textSize="40sp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

    <Button
        android:id="@+id/btnGofrag2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        tools:layout_editor_absoluteX="156dp"
        tools:layout_editor_absoluteY="421dp"
        tools:ignore="MissingConstraints" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

2-2. FirstFragment.kt

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
//private const val ARG_PARAM2 = "param2"

/**
 * A simple [Fragment] subclass.
 * Use the [FirstFragment.newInstance] factory method to
 * create an instance of this fragment.
 */
class FirstFragment : Fragment() {

    private var _binding: FragmentFirstBinding? = null
    private val binding get() = _binding!!

    // TODO: Rename and change types of parameters
    private var param1: String? = null
    //private var param2: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 프래그먼트가 onCreate 될 때... 프래그먼트가 생성될 때 인자로 전달된 데이터 읽어옴
        arguments?.let {
            // 이 argument 안에 Bundle이라는 객체로 들어있는 it을 꺼내오는 것
            //  Bundle에서 getString 함
            //    전역변수 param1에 받아온 string 값을 넣어줌
            // let을 쓰는 이유 : argumenet가 null일 수도 있어서... null이 아닐때만 들어오게 ?.let임
            // 전달받은 데이터가 있는 경우, "ARG_PARAM1" 상수를 사용해 해당 데이터를 가져와 param1 변수에 저장
            param1 = it.getString(ARG_PARAM1)
            //param2 = it.getString(ARG_PARAM2)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentFirstBinding.inflate(inflater, container, false)
        return binding.root
    }


    companion object {
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param param1 Parameter 1.
         * @param param2 Parameter 2.
         * @return A new instance of fragment FirstFragment.
         */
        // TODO: Rename and change types and number of parameters
        @JvmStatic
        fun newInstance(param1: String) =
            // [1] Activity -> FirstFragment
            // FirstFragment의 새 인스턴스를 생성
            //새로운 프래그먼트 인스턴스를 생성할 때, 해당 프래그먼트에 전달할 데이터를 bundle에 설정함
            FirstFragment().apply {
                //전달받은 데이터를 bundle에 담음
                //새 bundle을 생성하고, 이를 사용해 프래그먼트에 전달할 데이터를 설정
                arguments = Bundle().apply {
                    //프래그먼트의 인자로  설정함... argument에 저장되는데... 위에 onCreate 코드 확인!
                    //param1 에는 Main에서 보낸 string이 들어있음.
                    //Bundle에 데이터 추가, "ARG_PARAM1" 상수를 키로 사용
                    putString(ARG_PARAM1, param1)
                    //putString(ARG_PARAM2, param2)
                }
            }
        //newInstance() 메서드는 프래그먼트가 생성될 때 데이터를 설정하고,
        // onCreate() 메서드는 프래그먼트가 초기화(onCreate)될 때 데이터를 읽어와 param1에 집어넣고,
        //  onViewCreated에서 해당 param1을 사용해 텍스트 바꿈
    }


    // [1] Activity -> FirstFragment
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //인자로 받은 데이터를 텍스트 뷰에 저장
        // view가 create 될 때 param1을 텍스트에 넣어줌
        binding.tvFrag1Text.text = param1

        // [2] Fragment -> Fragment
        binding.btnGofrag2.setOnClickListener {
            val dataToSend = "Hello Fragment2! \n From Fragment1"
            //버튼 클릭 시 SecondFragment 의 새 인스턴스를 생성하고,
            //  newInstance 메소드를 사용해 데이터를 전달한 후
            val fragment2 = SecondFragment.newInstance(dataToSend)

            //프래그먼트 트랜잭션을 통해 두 번째 프래그먼트를 시작함
            // 프래그먼트에서 프래그먼트로!
            requireActivity().supportFragmentManager.beginTransaction()
                .replace(R.id.frameLayout, fragment2)
                .addToBackStack(null)
                .commit()
        }
    }
}

 

3-1. fragment_second.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:background="#C785D3"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tvFrag2Text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="프래그먼트 2"
        android:textAllCaps="false"
        android:textSize="40sp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

    <Button
        android:id="@+id/btnSendActivity"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        tools:layout_editor_absoluteX="158dp"
        tools:layout_editor_absoluteY="444dp"
        tools:ignore="MissingConstraints" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

3-2. SecondFragment.kt

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
//private const val ARG_PARAM2 = "param2"

// [3] SecondFragment -> Activity
interface FragmentDataListener {
    fun onDataReceived(data: String)
}

/**
 * A simple [Fragment] subclass.
 * Use the [SecondFragment.newInstance] factory method to
 * create an instance of this fragment.
 */
class SecondFragment : Fragment() {

    private var _binding: FragmentSecondBinding? = null
    private val binding get() = _binding!!

    // TODO: Rename and change types of parameters
    private var param1: String? = null
    //private var param2: String? = null


    // [3] SecondFragment -> Activity
    // 해당 인터페이스 타입의 리스너를 만들었음
    private var listener: FragmentDataListener? = null

    // [3] SecondFragment -> Activity
    // MainActivity와 SecondFragent를 연결해야 하니까... context 사용
    override fun onAttach(context: Context) {
        super.onAttach(context)

        // 프래그먼트가 액티비티에 붙을 때 액티비티가 이 인터페이스를 구현했는지 확인
        // context는 MainActivity에서 들어온 것

        //context를 검사해서 MainActivity 안에 FragmentDataListener가 구현되어있는지를 체크
        if (context is FragmentDataListener) {
            //이 리스너를 통해 호출하게 됨
            listener = context
        } else {
            throw RuntimeException("$context must implement FragmentDataListener")
        }
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
            //param2 = it.getString(ARG_PARAM2)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentSecondBinding.inflate(inflater, container, false)
        return binding.root
    }

    companion object {
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param param1 Parameter 1.
         * @param param2 Parameter 2.
         * @return A new instance of fragment SecondFragment.
         */
        // TODO: Rename and change types and number of parameters
        @JvmStatic
        fun newInstance(param1: String) =
            // [1] Activity -> FirstFragment
            SecondFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                    //putString(ARG_PARAM2, param2)
                }
            }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // [2] Fragment -> Fragment
        binding.tvFrag2Text.text = param1

        // [3] SecondFragment -> Activity
        binding.btnSendActivity.setOnClickListener {
            val dataToSend = "Hello from SecondFragment!"
            //데이터를 액티비티에 전달
            //listener는 위에서 FragmentDataListener 타입으로 만들어놓고 MainActivity의 context가 할당된 상태.
            //  인터페이스의 onDataReceived 를 호출함, 그걸 상속받고 있는 메인에서 토스트가 뜸
            listener?.onDataReceived(dataToSend)
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        // Binding 객체 해제
        _binding = null

        // [3] SecondFragment -> Activity
        listener = null
    }
}