background
Kotlin4분

프래그먼트란?

2025년 2월 20일

Fragment

Fragment는 앱 UI의 재사용 가능한 부분을 나타냄. 프래그먼트는 자체 레이아웃을 정의 및 관리하고 자체 수명 주기를 보유하며 자체 입력 이벤트를 처리할 수 있음. 프래그먼트는 단독으로 실행될 수 없음, 액티비티 혹은 다른 프래그먼트에서 호스팅 해야 함 (Manifest에 등록할 필요 xx)

등장 배경

  • Fragment는 기존 Activity가 가지고 있는 문제를 해결하기 위해 등장했음

  • Activity의 문제점

    1. Activity 안의 코드가 길어지게 되면 유지보수가 힘듦

    2. 안드로이드 디바이스는 휴대폰, 태블릿 등 다양하기 때문에 태블릿 UI를 고려할 때 단순 Activity로 화면을 그리기에 한계가 있음

      예를 들어, 태블릿 카카오톡에서 채팅방을 클릭하면, 태블릿 에서는 공간이 충분하기 때문에 채팅방이 바로 옆에 뜨게 되지만, 핸드폰에서는 공간이 충분하지 않기 때문에 새로운 Activity 호출 후 그 위에 채팅방이라는 fragment를 호출하는 방식

      image.png

      image.png

    → 결국 Fragment를 서로 다른 조합으로 재사용하면서 태블릿과 휴대폰 화면 모두 지원할 수 있게 됨

    특징

    Activity VS Fragment

    비교 항목ActivityFragment
    생명주기 관리ActivityManager가 관리FragmentManager(Activity가 관리)가 관리
    AndroidManifest 등록필수 등록 필요등록 불필요
    독립적 실행 여부독립적으로 실행 가능Activity나 다른 Fragment에 종속적
    화면 전환 방식Intent를 통해 전환메소드 호출을 통해 전환
    시스템 관리안드로이드 시스템이 직접 관리Activity의 관리를 받음
    재사용성Activity 단위로만 재사용여러 Activity에서 재사용 가능
    리소스 소모상대적으로 더 많은 리소스 필요상대적으로 가벼움
    UI 구성의 유연성전체 화면 단위로 구성화면의 일부분으로 유연하게 구성 가능
  • Fragment는 자체 레이아웃을 가질 수 있으며 자체 생명주기를 보유함 또, 자체 입력 이벤트를 받으며 처리할 수 있음

  • Fragment는 앱의 전체 및 사용자와 앱이 상호작용 할 수 있는 부분 어딘가에서 반복적으로 재사용 가능한 부분임

  • Fragment는 독립적으로 존재할 수 없고, 반드시 Activity나 다른 Fragment에서 호스팅 되어야 함

    • 항상 Activity, Fragment 내에서 호스팅 되어야 하며 해당 Fragmnet의 수명 주기는 호스트 Activity의 수명 주기에 직접적으로 영향을 받음
    • 예를 들어, Activity가 일시 정지 되는 경우, 그 안의 모든 Fragment도 일시정지 되며 Activity가 소멸되면 모든 Fragment도 마찬가지로 소멸됨. 하지만 Activity가 실행 중인 동안에는 각 Fragment는 추가, 제거 될 수 있는 등 개별적으로 조작 가능
  • 따라서 Fragment의 뷰 계층 구조는 호스트 뷰 계층 구조의 일부가 됨

즉, Framgnet 는 Activity 혹은 다른 Fragment 위에 그려지는 재사용 가능한 독립적인 화면 이다

Fragment의 재사용성

  • Fragment의 특징에서 보다시피, Fragment의 존재 이유는 UI 중에서 재사용 가능한 부분을 재사용하기 위함임
  • Fragment는 자체 UI를 개별적인 청크로써 사용할 수 있음. 개별 청그 단위로 다른 곳에서 재사용할 수 있음

🗒️ 청크란?
데이터나 정보를 작은 단위로 나누는 것

Activity와 Fragment의 다른 목적성

  • Activity : 앱 전체적인 사용자 인터체이스 (UI)에 포함될 요소들을 배치하는 곳
  • Fragment : 단일 화면이나 화면 일부에 관한 사용자 인터페이스 (UI)를 정의하는 데 적합

image.png

image.png

  • 파란색 부분은 Acivitiy에 배치된 Navigation이고 초록색 부분은 Fragment 자체 UI
  • React로 예를 들면, App.jsx에 div 태그를 열고, Header, Footer를 미리 정의 해주고 그 중간에 컴포넌트를 배치하는데, Activity, Fragment도 비슷한 결임
javascript
// React
function App() {
  return (
    <div>
      <Header />
      <MainContent />  {/* 여러 컴포넌트 배치 */}
      <Footer />
    </div>
  )
}
kotlin
// Android (개념적 유사성)
class MainActivity : AppCompatActivity() {
    override fun onCreate() {
        setContentView(R.layout.activity_main)
        // Header Fragment
        // Main Content Fragment
        // Footer Fragment
    }
}

Fragment의 장점

  • 앱의 단일 화면, 부분 화면을 Fragment로 구현하면 런터임 시 UI 모습을 사용자와 상호작용 하면서 실시간으로 수정할 수 있음

  • 사용자가 앱을 실행하여 사용하는 도중에 Activity의 모양을 수정할 수 있음

    예를 들어 BottomNavigationView가 존재하는 앱에서는 사용자가 어느 탭을 클릭했는지에 따라 화면이 나오는데 이러한 현상이 런타임 동안 사용자와 상호작용 하면서 앱의 UI가 실시간으로 바뀌는 것이라고 할 수 있음.(Fragment 추가, 교체, 삭제 등의 작업이 실행됨으로써 화면이 바뀌는 것처럼 보임.) → React, Vue 의 SPA 방식과 유사

  • 대신 Fragment를 사용하여 런타임 동안 UI를 실시간으로 바꿀 때는 호스트 Activity의 수명 주기가 STARTED 상태 이상에 있는 동안에만 가능함 즉, Acitivity가 살아 있는 동안에만 가능함

  • 런타임 동안 Fragment의 변경이 발생하면 FragmentManager라는 것이 관리하는 Fragment Back Stack에 변경사항 히스토리를 저장하여 기록할 수 있음

  • Fragment Back Stack 에 저장된 변경 사항들에 한해서 사용자가 ‘뒤로’ 버튼을 눌렀을 경우 ‘변경 사항 취소’ 가 되어 ‘되돌리기’ 작업을 진행할 수 있음

    예를 들어,

    • 사용자가 본문 작성 중 실수로 내용을 잘못 입력
    • 뒤로가기 버튼을 누르면 이전 상태(제목 입력 화면)로 돌아감
    • 다시 뒤로가기를 누르면 받는사람 입력 화면으로 돌아감

    와 같은 흐름이다.

주의점

  • Fragment는 재사용 가능한 자체 UI를 가지기 때문에 어느 Activity에나 호스팅 될 수 있고 어느 Fragment에나 호스팅 될 수 있음
  • 따라서 Fragment 클래스에는 자체 UI를 관리하는 로직만 구현해야 하고 다른 Activity나 다른 Fragment를 직접 조작하는 로직을 포함하면 안됨
  • 즉, Fragment가 다른 Activity, Fragment에 의존하면 안됨

위반 예시

kotlin
class BadFragment : Fragment() {
    fun onButtonClick() {
        // 나쁜 예시: 직접 MainActivity의 메소드 호출
        (activity as MainActivity).updateTitle("새로운 제목")
        
        // 나쁜 예시: MainActivity의 뷰에 직접 접근
        val mainActivityButton = (activity as MainActivity)
            .findViewById<Button>(R.id.main_button)
        mainActivityButton.text = "변경된 텍스트"
    }
}
kotlin
class BadFragment : Fragment() {
    fun updateOtherFragment() {
        // 나쁜 예시: 다른 Fragment를 직접 찾아서 조작
        val otherFragment = parentFragmentManager
            .findFragmentById(R.id.other_fragment) as OtherFragment
        otherFragment.updateData("새로운 데이터")
    }
}
kotlin
class BadFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // 나쁜 예시: 특정 Activity 타입을 가정
        if (activity !is MainActivity) {
            throw IllegalStateException("이 Fragment는 MainActivity에서만 사용할 수 있습니다")
        }
    }
}

좋은 예시 (구현체를 통한 통신)

kotlin
// 1. 인터페이스 정의
interface FragmentCallback {
    fun onDataUpdate(data: String)
    fun onUserAction()
}

// 2. Fragment는 인터페이스만 알고 있음
class MyFragment : Fragment() {
    private var callback: FragmentCallback? = null
    
    override fun onAttach(context: Context) {
        super.onAttach(context)
        // Activity가 인터페이스를 구현했는지 확인
        if (context is FragmentCallback) {
            callback = context
        }
    }
    
    fun someAction() {
        // 인터페이스를 통해 통신
        callback?.onUserAction()
    }
}

// 3. Activity에서 인터페이스 구현
class MainActivity : AppCompatActivity(), FragmentCallback {
    override fun onDataUpdate(data: String) {
        // 실제 구현
    }
    
    override fun onUserAction() {
        // 실제 구현
    }
}

Fragment 시작하기

  1. 환경 설정

    1. build.gradle(app 단위)에 의존성 추가
    xml
    dependencies {
        def fragment_version = "1.6.2"
        
        // Fragment 기본 라이브러리
        implementation "androidx.fragment:fragment-ktx:$fragment_version"
        
        // 테스트를 위한 Fragment 라이브러리 (선택사항)
        debugImplementation "androidx.fragment:fragment-testing:$fragment_version"
    }
    
  2. 레이아웃 설정

    xml
    <!-- activity_main.xml -->
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.example.FragmentTest" />
    
  3. Fragment 클래스 생성

    kotlin
    class TestFragment : Fragment() {
        
        override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            // Fragment의 레이아웃을 inflate
            return inflater.inflate(R.layout.fragment_example, container, false)
        }
    }
    
  4. Activity에서 Fragment 사용

    kotlin
    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            
            // 동적으로 Fragment 추가
            if (savedInstanceState == null) {
                supportFragmentManager.beginTransaction()
                    .add(R.id.fragment_container, ExampleFragment())
                    .commit()
            }
        }
    }
    

실행 결과

image.png

image.png

image.png

image.png

코드

Activity

kotlin
package com.example.constraintlayout_practice.ui

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.constraintlayout_practice.databinding.ActivityFragmentTestBinding

class TestFragmentActivity : AppCompatActivity() {
    private lateinit var binding: ActivityFragmentTestBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityFragmentTestBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.btnShowPopup.setOnClickListener {
            PopupFragment().show(supportFragmentManager, "popup")
        }
    }


}

Fragment

kotlin
package com.example.constraintlayout_practice.ui

import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import com.example.constraintlayout_practice.R
import com.example.constraintlayout_practice.databinding.FragmentPopUpBinding

class PopupFragment : DialogFragment() {
    private var _binding: FragmentPopUpBinding? = null
    private val binding get() = _binding!!

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setStyle(STYLE_NO_FRAME, R.style.PopupFragment)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentPopUpBinding.inflate(inflater, container, false)
        dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.btnClose.setOnClickListener {
            dismiss()  // remove() 대신 dismiss() 사용
        }
    }

    override fun onStart() {
        super.onStart()
        dialog?.window?.apply {
            setLayout(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT
            )
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

태그

#Kotlin#AndroidStudio