ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Android] Fragment 데이터 전달
    안드로이드 2024. 4. 11. 20:57

    ✏️ TIL(Today I Learned)

     

    프래그먼트의 데이터 전달에 대해 알아보기 위해 프로젝트를 만들었다.

    우선 액티비티에서 프래그먼트로 데이터를 전달하는 것을 확인하기 위해 메인 엑티비티와 프래그먼트를 2개 만들었다.

     


    activity_main.xml에 FrameLayout을 만들어서 이곳에 프래그먼트를 위치할 것이다.

    <?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">
        </FrameLayout>
    
        <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>


    MainActivity에서 supportFragmentManager를 사용해 frameLayout에 동적으로 프래그먼트 추가했다.

     

    [1] Activity ➝ FirstFragment 데이터 보내기

    그리고 각 버튼의 클릭 리스너 안에서 FirstFragment의 인스턴스를 생성하고, newInstance 메소드에 데이터를 전달하여 프래그먼트를 설정(set)했다. 클릭 시, 데이터 전달과 동시에 해당 프래그먼트로 전환된다. 

    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"
                    val fragment = FirstFragment.newInstance(dataToSend)
                    setFragment(fragment)
                }
    
                fragment2Btn.setOnClickListener {
                    // [1] Activity -> SecondFragment
                    val dataToSend = "Hello Second Fragment!\n From Activity"
                    val fragment = SecondFragment.newInstance(dataToSend)
                    setFragment(fragment)
                }
            }
    
            setFragment(FirstFragment())
        }
    
        private fun setFragment(frag : Fragment) {
            supportFragmentManager.commit {
                replace(R.id.frameLayout, frag)
                setReorderingAllowed(true)
                addToBackStack("")
            }
        }
    
        // [3] SecondFragment -> Activity
        override fun onDataReceived(data: String) {
            // Fragment에서 받은 데이터를 처리
            Toast.makeText(this, data, Toast.LENGTH_SHORT).show()
        }
    }

     

    [1] Activity ➝ FirstFragment 데이터 받기

    newInstance 메소드에서 전달받은 데이터를 Bundle에 담고, 프래그먼트의 인자로 설정면 된다.

    onViewCreated에서는 인자로 받은 데이터를 텍스트 뷰에 설정했다.

    // FirstFragment 
    
    private const val ARG_PARAM1 = "param1"
    private var param1: String? = null
    
    companion object {
        @JvmStatic
        fun newInstance(param1: String) =
            // [1] Activity -> FirstFragment
            FirstFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                }
            }
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // [1] Activity -> FirstFragment
        binding.tvFrag1Text.text = param1     
    }

     

    그럼 Frag1버튼 클릭 시, 프래그먼트 가운데의 텍스트뷰에 받은 데이터가 표시된다.

     

     

    [2] FirstFragment SecondFragment 데이터 보내기

    프래그먼트에서 다른 프래그먼트로 데이터를 전달하기 위해서는 보내는 프래그먼트에서 "받을 프래그먼트의 newInstance 메소드"를 사용하여 인스턴스를 생성한 뒤, 데이터를 전달하면 된다.

     

    아래 코드에서는 버튼 클릭 시 SecondFragment의 새 인스턴스를 생성하고, newInstance 메소드를 사용하여 데이터를 전달한 후 프래그먼트 트랜잭션을 통해 두 번째 프래그먼트를 시작한다.

    // FirstFragment 
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // [1] Activity -> FirstFragment
        binding.tvFrag1Text.text = param1
    
        // [2] Fragment -> Fragment
        binding.btnSendFrag2.setOnClickListener{
            val dataToSend = "Hello Fragment2! \n From Fragment1"
            val fragment2 = SecondFragment.newInstance(dataToSend)
            requireActivity().supportFragmentManager.beginTransaction()
                .replace(R.id.frameLayout, fragment2)
                .addToBackStack(null)
                .commit()
        }
    }

     

    [2] SecondFragment  FirstFragment  데이터 받기

    newInstance 메소드로 전달받은 데이터를 Bundle에 담고, onCreate 또는 onViewCreated에서 Bundle로부터 데이터를 추출하여 사용하면 된다.

    // SecondFragment
    
    private var param1: String? = null
    private var _binding: FragmentSecondBinding? = null
    private val binding get() = _binding!!
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
        }
    }
    
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentSecondBinding.inflate(inflater, container, false)
        return binding.root
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // [2] Fragment -> Fragment
        binding.tvFrag2Text.text = param1
    }
    
    companion object {
        @JvmStatic
        fun newInstance(param1: String) =
            // [1] Activity -> FirstFragment
            SecondFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                }
            }
    }

     

    Send Fragment2 버튼 클릭 시, 두번째 프래그먼트로 전환되면서 데이터를 전달받아 텍스트뷰에 표시된다.

     

     

    [3] SecondFragment MainActivity 데이터 보내기
    프래그먼트에서 액티비티로 데이터를 전달할 때는 콜백 인터페이스를 정의하고, 해당 인터페이스를 액티비티가 구현하도록한다. 프래그먼트는 이 인터페이스를 사용하여 액티비티에 데이터를 전달한다.

     

    FragmentDataListener 인터페이스를 정의한 뒤 프래그먼트가 액티비티에 붙을 때(onAttach), 액티비티는 해당 인터페이스가 구현됐는지 확인한다. 버튼 클릭 리스너에서 onDataReceived 메소드를 호출하여 데이터를 액티비티에 전달한다.

    private const val ARG_PARAM1 = "param1"
    
    interface FragmentDataListener {
        fun onDataReceived(data: String)
    }
    
    class SecondFragment : Fragment() {
        // [3] SecondFragment -> Activity
        private var listener: FragmentDataListener? = null
    
        override fun onAttach(context: Context) {
            super.onAttach(context)
            // [3] SecondFragment -> Activity
            if (context is FragmentDataListener) {
                listener = context
            } else {
                throw RuntimeException("$context must implement FragmentDataListener")
            }
        }
    
        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?.onDataReceived(dataToSend)
            }
        }
    }

     

    [3] MainActivity SecondFragment  데이터 받기

    MainActivity는 FragmentDataListener 인터페이스를 구현하고, onDataReceived 메소드를 오버라이드하여 프래그먼트로부터 데이터를 받는다. 데이터를 받으면 Toast 메시지로 표시했다.

    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"
                    val fragment = FirstFragment.newInstance(dataToSend)
                    setFragment(fragment)
                }
    
                fragment2Btn.setOnClickListener {
                    // [1] Activity -> SecondFragment
                    val dataToSend = "Hello Second Fragment!\n From Activity"
                    val fragment = SecondFragment.newInstance(dataToSend)
                    setFragment(fragment)
                }
            }
    
            setFragment(FirstFragment())
        }
    
        private fun setFragment(frag : Fragment) {
            supportFragmentManager.commit {
                replace(R.id.frameLayout, frag)
                setReorderingAllowed(true)
                addToBackStack("")
            }
        }
    
        // [3] SecondFragment -> Activity
        override fun onDataReceived(data: String) {
            // Fragment에서 받은 데이터를 처리
            Toast.makeText(this, data, Toast.LENGTH_SHORT).show()
        }
    }

     

    send Activity 버튼 클릭 시, 아래 사진처럼 MainActivity에서 받은 데이터가 토스트로 표시된다.

     

    📝 공부한 Kotlin 정리

    프래그먼트 (Fragment)

    01. 프래그먼트(Fragment)

    프래그먼트는 액티비티 위에서 동작하는 모듈화된 사용자 인터페이스이다.

    • 액티비티와 분리되어 독립적으로 동작할 수 없음
    • 여러 개의 프래그먼트를 하나의 액티비티에 조합하여 창이 여러 개인UI를 구축할 수 있음
    • 하나의 프래그먼트를 여러 액티비티에서 재 사용할 수 있음 

    02. 액티비티 vs 프래그먼트 비교

    - Activity

    시스템의 액티비티 매니저에서 인텐드를 해석해 액티비티간 데이터를 전달

     

    - Fragment

    액티비티의 프래그먼트 매니저에서 메소드로 프래그먼트간 데이터를 전달

     

    03. 프래그먼트 생명주기

    프래그먼트(Fragment)는 안드로이드 애플리케이션의 UI 부분을 모듈화하여 재사용할 수 있도록 해주는 구성 요소이다.

    프래그먼트는 자체적인 생명주기(lifecycle)를 가지며, 액티비티의 생명주기와 밀접하게 연결되어 있다. 프래그먼트의 생명주기를 이해하는 것은 안드로이드 앱을 효율적으로 관리하고, 사용자에게 부드러운 인터페이스 경험을 제공하는 데 중요하다.

     

    프래그먼트의 주요 생명주기 메서드:

    1. onAttach()
      • 프래그먼트가 액티비티에 연결될 때 호출된다.
      • 이 시점에서 프래그먼트는 액티비티와 아직 완전히 연결되지는 않았다.
    2. onCreate()
      • 프래그먼트가 생성될 때 호출된다.
      • 초기화 작업, 리소스 바인딩 등을 수행할 수 있다.
    3. onCreateView()
      • 프래그먼트의 레이아웃을 인플레이트하는 곳이다.
      • 뷰를 생성하고, 레이아웃을 설정한다.
    4. onActivityCreated()
      • 액티비티의 onCreate() 메서드가 완료된 후 호출된다.
      • 액티비티와 프래그먼트의 뷰가 모두 생성된 상태이므로, 뷰와 관련된 초기화를 수행한다.
    5. onStart()
      • 프래그먼트가 사용자에게 보여질 준비가 되었을 때 호출된다.
      • 필요한 리소스를 할당하거나, 애니메이션을 시작할 수 있다.
    6. onResume()
      • 프래그먼트가 사용자와 상호작용할 수 있는 상태가 되었을 때 호출된다.
      • 프래그먼트가 포그라운드에 있을 때 실행되는 작업을 여기서 처리한다.
    7. onPause()
      • 프래그먼트가 일시정지될 때 호출된다.
      • 상태 저장, 스레드 중지 등의 작업을 수행한다.
    8. onStop()
      • 프래그먼트가 더 이상 사용자에게 보이지 않을 때 호출된다.
      • 리소스 해제, 스레드 정지 등을 수행한다.
    9. onDestroyView()
      • 프래그먼트의 뷰와 관련된 리소스를 정리할 때 호출된다.
    10. onDestroy()
      • 프래그먼트가 파괴될 때 호출된다.
      • 프래그먼트의 상태를 정리하고, 모든 리소스를 해제한다.
    11. onDetach()
      • 프래그먼트가 액티비티로부터 분리될 때 호출된다.
      • 프래그먼트가 액티비티와의 모든 연결을 해제한다.

    각 단계에서는 프래그먼트의 생명주기에 맞춰 적절한 작업을 수행해야, 메모리 누수를 방지하고 애플리케이션의 성능을 최적화할 수 있다.

     

    04. 프래그먼트를 사용하는 이유

    • Activity의 복잡도를 줄일 수 있다.
    • Activity를 적게 만들 수 있다.
    • Activity로 화면을 계속 넘기는 것보다는 Fragment로 일부만 바꾸는 것이 자원 이용량이 적어 속도가 빠르다.
    • 최소 1개의 Activity안에서 Fragment공간에 view만 넣으면 여러 Activity를 만들지 않아도 여러 화면을 보여줄 수 있다.
    • Fragment를 사용하면 재사용할 수 있는 레이아웃을 분리해서 관리가 가능하다.
Designed by Tistory.