Android

[안드로이드]FragmentManager

나맘임 2023. 7. 9. 19:35

FragmentManager

앱 프래그먼트에서 프래그먼트를 추가, 삭제 또는 교체하고 백 스텍에 추가하는 등의 작업을 실행하는 클래스

 

FragmentManager에 접근

FragmentManager는 액티비티당 FragmentManager 하나가 존재하기 때문에 이를 가져와서 사용하면 됨

 

액티비티에서 getSupportFragmentManager() 매서드 사용

 

프래그먼트에선 하위 프래그먼트가 존재할 시 getChildFragmentManager() 매서드 사용

 

상위 프래그먼트의 FragmentManager에 접근할려면 getParentFragmentManager() 메서드 사용

 

FragmentTransaction

FragmentManager를 통하여 프래그먼트를 추가, 삭제하기 위한 작업

beginTransaction 매서드를 사용하여 시작하기도 하지만 commit 을 사용하면 스코프 매서드로 바로 사용 가능하다.

val fragmentManager = supportFragmentManager
fragmentManager.commit {
    add(R.id.hostFragment, HostFragment())
}

Commit에 관하여

1.프래그먼트 추가, 교체, 삭제 작업을 명시했으면 반드시 commit 해야 하는 이유

2. commit 를 해야지만 FragmentManager가 해당 FragmentTransaction을 수행함

3. 비동기로 처리되기 때문에 commit을 호출하자마자 Transaction이 즉시 수행되는 것이 아니라 메인 스레드에 예약됨

4. 메인 스레드가 예약된 Transaciton을 수행할 준비가 되면 비로소 그 때 Transaction이 수행되며 명시한 Fragment 작업들이 실행됨

5. Transaction이 비동기가 되야 한다면 커밋 즉시 수행하는 commitNow()를 호출하면 됨

    => 백스택을 추가하는데 문제가 발생할 수 있음

 

Add에 관하여

1. 프래그먼트를 추가하는 작업

2. add로 수행되는 프래그먼트 추가 작업은 호스트 Activity의 수명 주기에 프래그먼트 수명 주기를 추가하는 것임

3. 프래그먼트 onAttach() 때 Fragment가 자신의 호스트인 Activity에 붙게 되고 onCreateView에서 프래그먼트 View가 inflate가 되며 Fragment Container 위치에 보이게 됨

 

Replace

1. 프래그먼트를 교체하는 작업

2. 교체가 프래그먼트 하나랑 다른 하나를 교체하는 것이 아니라 프로퍼티로 받는 프래그먼트를 제외한 나머지 모든 프래그먼트를 remove를 하게 되는 작업

 

Show & Hide

1. 이미 프래그먼트 컨테이너에 추가 되어잇는 프래그먼트의 UI 를 감추거나 보이게 해주는 작업

2. 프래그먼트 수명에 영향을 주지 않고 오직 Visibility에만 영향

    => 프래그먼트가 생성되거나 삭제되는 것이 아니기 때문에 상태가 유지되어 있음

3. 계층 구조가 유지됨

 

Attach & Detach

1. Detach는 프래그먼트의 UI를 분리하여 파괴시키나 메모리에 유지됨 (remove는 메모리에서 삭제!)

2. Attach는 분리되어있던 프래그먼트의 UI를 다시 붙임

3. Attach하게 되면 프래그먼트 컨테이너의 가장 위에 쌓임

=> 수명 주기에 영향을 줌

 

백스택

안드로이드 플랫폼에서 백스택이라는 자료구조를 통해 액티비티의 히스토리를 저장함

만약, 메인 액티비티에서 Second액티비티로 Intent를 이용하여 넘어갔다면 맨 밑에 메인이 있고 그 위에 Second가 존재

이 때 뒤로가기를 누르면 Second액티비티는 Pop이 되어 사라지고 메인 액티비티가 나타남

즉, Stack의 Peek만 보여주는 구조라고 생각하면 됨

 

FragmentManager의 백스택

위 안드로이드 플랫폼이 관리하는 백스텍에 FragmentManager의 Transaction도 포함시킴

하는 이유는 사용자가 뒤로 가기 버튼을 눌렀을 때 이전 Fragment 또한 표시해주기 위함

액티비티는 자동으로 추가되지만 프래그먼트 트랜잭션은 아님

그걸 해주는 메서드가 addToBackStack()

 

뷰모델, 하단 네비게이션 뷰를 사용하여 프래그먼트 전환

PageType.kt

enum class PageType {
    HOME,
    TEST
}

ViewModel.kt

class ViewModel : androidx.lifecycle.ViewModel(){
    private var _pageType = MutableLiveData<PageType>()

    val pageType : LiveData<PageType> get() = _pageType


    fun updageFragmentStatus(pageType: PageType){
        _pageType.value = pageType
    }
}

MainActivity.kt

package com.example.myandroidstudy.view.main

import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.commit
import androidx.lifecycle.Observer
import androidx.lifecycle.asLiveData
import com.example.myandroidstudy.R
import com.example.myandroidstudy.databinding.ActivityMainBinding
import com.example.myandroidstudy.model.PageType
import com.example.myandroidstudy.model.ViewModel
import com.example.myandroidstudy.model.preferences.CountManager
import com.example.myandroidstudy.model.preferences.dataStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    private val viewModel : ViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        viewModel.updageFragmentStatus(PageType.HOME)

        viewModel.pageType.observe(this, Observer {
            when(it){
                PageType.HOME ->{
                    loadFragment(HomeFragment())
                }
                PageType.TEST ->{
                    loadFragment(TestFragment())
                }
            }
        })

        setNavigationItemClickListener()


    }

    private fun setNavigationItemClickListener() {

        binding.navigationView.setOnItemSelectedListener { item ->
            when (item.itemId) {
                R.id.navi_homeFragment -> {
                    viewModel.updageFragmentStatus(PageType.HOME)
                    return@setOnItemSelectedListener true
                }
                R.id.navi_testFragment -> {
                    viewModel.updageFragmentStatus(PageType.TEST)
                    return@setOnItemSelectedListener true
                }
            }
            false
        }
    }

    private fun loadFragment(fragment: Fragment) {
        val transaction = supportFragmentManager.beginTransaction()
        transaction.replace(R.id.mainFrameLayout, fragment)
        transaction.addToBackStack(null)
        transaction.commit()
    }

    private fun addFragment(fragment: Fragment){
        val transaction = supportFragmentManager.beginTransaction()
        transaction.add(R.id.mainFrameLayout, fragment)
        transaction.addToBackStack(null)
        transaction.commit()
    }
}