앱 UI에서 특정 영역을 재사용하기 위해 사용되는 요소입니다.
프래그먼트는 자체 레이아웃(xml파일을 정의)을 정의 및 관리하고 자체 수명 주기를 보유하며 자체 입력 이벤트를 처리할 수 있습니다.
프래그먼트는 독립적으로 존재할 수 없고 반드시 Activity나 Host Fragment에 호스팅되어야 합니다. (Host Fragment도 Activity로부터 호스팅되므로 결국 Activity로부터 호스팅되어야 합니다)
따라서 프래그먼트의 뷰 계층 구조는 호스트 뷰 계층 구조의 일부가 됩니다.
Android Jetpack 라이브러리 중 Navigation, BottomNavigationView, ViewPager2 등은 프래그먼트와 호환되도록 설계되어 있어서 프래그먼트가 해당 라이브러리와 함께 자주 사용됩니다.
Activity : 앱 전체적인 사용자 인터페이스(UI)에 포함될 요소들을 배치하는 곳입니다.
프래그먼트 : 단일 화면이나 화면 일부에 관한 사용자 인터페이스(UI)를 정의하는데 적합합니다.
Activity UI를 모듈화하여 유연성을 높입니다.
UI 를 조립할 수 있습니다.
화면 크기에 따른 배치를 할 수 있습니다.
유연하게 UI를 디자인할 수 있다는 게 가장 큰 장점
fragment 독립적인 모듈(재사용 가능한 화면)
앱의 단일 화면이나 부분 화면을 프래그먼트 구현하면 런타임 시 UI 모습을 사용자와 상호작용하면서 실시간으로 수정할 수 있습니다.
사용자가 앱을 실행하여 사용하는 도중(Activity is running)에 Activity의 모양을 수정할 수 있다는 의미입니다.
예를 들어 BottomNavigationView가 존재하는 앱에서는 사용자가 ‘홈 탭’을 클릭하면 홈 화면이 나오고, ‘마이페이지 탭’을 클릭하면 마이페이지 화면이 나옵니다. 이러한 현상이 런타임동안 사용자와 상호작용을 하면서 앱의 UI가 실시간으로 바뀌는 것이라고 할 수 있습니다.
(프래그먼트 추가/교체/삭제 등의 작업이 실행됨으로써 화면이 바뀌는 것처럼 보입니다.)
대신 프래그먼트를 사용하여 런타임 동안 UI를 실시간으로 바꿀 때는 호스트 Activity의 수명 주기가 STARTED 상태 이상에 있는 동안에만 가능합니다.
런타임 동안 프래그먼트의 변경 사항(추가/교체/삭제)이 발생하면 FragmentManager 객체가 관리하는 프래그먼트 백 스택(Activity 백 스택과 다름) 에 변경 사항 히스토리를 저장하여 기록할 수 있습니다.
프래그먼트는 재사용 가능한 자체 UI를 가지기 때문에 어느 Activity에나 호스팅 될 수 있고 어느 프래그먼트에나 호스팅 될 수 있습니다.
따라서 프래그먼트 클래스에는 자체 UI를 관리하는 로직만 구현해야 하고 다른 Activity나 다른 프래그먼트를 직접 조작하는 로직을 포함해서는 안됩니다.(모듈성, 재사용성을 해치게 됨)
fragment manager는 액티비티에 프래그먼트를 추가, 삭제, 교체하는 작업을 관리하는 클래스입니다.
즉, 프래그먼트 백스택(Back Stack) 에 프래그먼트 추가/교체/삭제 등 작업에 의한 변경 사항을 push 및 pop 하는 작업을 담당하는 클래스입니다
프래그먼트를 관리하는 더 좋은 방법
Android Jetpack Navigation 라이브러리를 사용하여 앱의 탐색 구조를 구현한다면 FragmentManager를 직접 다뤄야 할 일이 적어져 편합니다.(Google I/O 2018 소개)
이 Navigation 라이브러리 내부 동작에 FragmentManager를 사용한 작업이 구현되어 있기 때문입니다.(개발자의 편의성 증가)
하지만 라이브러리 내부 동작도 결국은 프래그먼트 관리자를 사용하기 때문에 어떤 원리로 프래그먼트 관리자가 작동하는지 이해하는 것이 중요합니다.
프래그먼트 관리자에는 추가, 제거, 변경을 제공하는 Fragment Transaction 클래스가 내부에 구현되어있어, 이를 인스턴스화해야 프래그먼트를 추가, 제거, 변경을 할 수 있습니다.
프래그먼트 관리자는 2가지 방법으로 인스턴스화할 수 있습니다.
Activity에서 인스턴스화
Fragment에서 인스턴스화
프래그먼트 트랜잭션은 Android Jetpack Fragment 라이브러리에서 제공하는 클래스입니다.
프래그먼트 트랜잭션 클래스는 프래그먼트 추가/교체/삭제 작업을 제공합니다.
이 외에도 프래그먼트 트랜잭션은 프래그먼트 관리자가 수행할 단일 단위입니다. 따라서 하나의 프래그먼트 트랜잭션 단위 내에 프래그먼트 트랜잭션 클래스가 제공하는 프래그먼트 추가/교체/삭제 작업 등을 명시하면 됩니다.
하나의 프래그먼트 트랜잭션 단위 내에 작성된 프래그먼트 조작 관련 작업들은 해당 프래그먼트 트랜잭션이 수행될 때 모두 실행됩니다.
(프래그먼트 조작 작업의 묶음 단위라고 생각하면 쉽습니다)
앞서 설명했듯이, FragmentTransaction 클래스는 추상 클래스로 FragmentManger 클래스에 구현되어있습니다. 따라서 FragmentTransaction을 생성하기 위해서는 프래그먼트 관리자 클래스에서 제공하는 beginTransaction() 함수 호출하여 생성해야 합니다.
프래그먼트 트랜잭션을 생성하여 프래그먼트 추가/교체/삭제 작업을 명시한 후에는 반드시 마지막에 커밋(commit)을 해줘야 합니다.
commit() 함수가 비동기로 처리되는 함수이기 때문에 commit() 함수 호출 시점에서 트랜잭션이 즉시 수행되는 것이 아니라 메인 쓰레드에 예약됩니다.
메인 쓰레드가 예약된 트랜잭션을 수행할 준비가 되면 비로소 그 때 트랜잭션이 수행되어 명시한 프래그먼트 조작 작업들이 실행됩니다.
프래그먼트가 별도로 라이프사이클을 가지는 이유?
프래그먼트는 액티비티에서 inflate되는 구성 요소입니다.
따라서 액티비티의 상태가 변함에 따라 프래그먼트의 상태도 변합니다.
FragmentInstance를 구성합니다.
→ Model과 같이 Fragment 구성에 필요한 것을 초기화합니다.
onCreate() 에서 inflate 하면 안되는 이유
Fragment뷰의 Layout을 inflate합니다.
inflate된 뷰를 hostingactivity에 반환합니다
attachToRoot는 false로 해야 합니다.
inflater.inflate(R.layout.fragment_home, container, false)
에서 attachToRoot를 false로 하면 호스트 액티비티 또는 호스트 프래그먼트가 모두 생성된 다음에 해당 레이아웃을 inflate 시킵니다. [view] : onCreateView에서 생성된 View객체
→ 초기값설정, listener설정 등
Created 단계에서 호출되는 콜백함수들은 프래그먼트를 프래그먼트 매니저에 추가할 때 호출되는 메서드들입니다.
Activity와 Fragment의 생명주기 콜백은 대응됩니다.
Fragment는 Activity의 상태를 반영해야 합니다.
→Fragment의 생명주기함수는 FragmentManager가 관리합니다.
따라서 각 콜백함수에 적합한 작업은 프래그먼트에도 동일하게 적용할 수 있다
참고로 onSaveInstanceState()은 화면이 소멸되기 전에 화면을 복원할 때 필요한 데이터를 저장할 때 호출되는 콜백함수입니다.. 여기서 저장한 데이터를 onViewStateRestored()에 전달하여 화면을 복원합니다.
onDestroyView() 는 onCreateView()와 대응하는 단계로, 화면이 소멸되기 직전에 아직 리소스를 해제하지 못했다면 여기서 처리합니다.
액티비티와 동일하게 화면이 소멸되기 전에 onDestroy()가 호출됩니다.
액티비티와 동일하게 프래그먼트도 Back Stack 을 가지고 있습니다.
하지만 액티비티와는 다르게 프래그먼트 백스택은 프래그먼트 관리자가 프래그먼트가 아니라 프래그먼트 트랜잭션을 push합니다.
(프래그먼트 관리자가 백스택을 관리합니다)
따라서 프래그먼트에서도 뒤로 가기를 누르면 이전 프래그먼트 화면 또는 작업이 보이게 됩니다.
단, 트랜잭션을 기록하기 위해서는 addToBackStack()
메서드를 트랜잭션 안에서 호출해야 합니다.
multiple backstack 이란?