-
Fragment
-
Fragment Lifecycle
-
FragmentManager
-
Navigation
-
실습 프로젝트
-
Dependency
-
Fragment 추가
-
Fragment의 ViewBinding - 1번째 방법
-
Fragment의 ViewBinding - 2번째 방법
-
Fragment 추가
-
Navigation Graph 추가
-
NavHost 설정
-
Navigation Graph 작성하기
-
테스트
-
Fragment와 ViewModel
-
ViewModel 다시 보기
-
Activity와 Fragment의 ViewModel
-
Fragment 간의 데이터 공유 - 1번 방법
-
AvatarFragment 수정
Fragment + Navigation
Fragment + ViewModel
Fragment
https://developer.android.com/guide/fragments
- 앱에서 사용하는 UI를 재사용할 수 있도록 해 주는 클래스
- 자체의 코드와 레이아웃을 가지고 수명 주기를 가지고 있음.
- Activity와의 관계
- Android Application Component는 아니기 때문에 독립적으로 존재할 수는 없음. → Activity 또는 다른 Fragment에서 불러써야 함.
- 하나의 Activity에 여러 개의 Fragment를 사용할 수 있음. → 회원가입 시 아이디 비밀번호만 입력받고, 그 다음에 여러가지 정보를 입력하는 화면으로 이동 그러한 형태로 사용할 수 도 있음
- Activity는 STARTED 수명주기일 때 Fragment를 추가, 교체, 삭제할 수 있음.
- BottomNavigationView, ViewPager2 등 JetPack과 호환

- 필수 Override 함수 : onCreateView()
- 레이아웃을 지정하여 View 생성
- Lifecycle을 가진다.
- Fragment 자체의 lifecycle
- View의 lifecycle
- ⇒ 두가지의 lifecyle이 다르다.
Fragment Lifecycle
- lifecycle
- Fragment의 lifecycle에 access 할 수 있는 객체
- lifecycleScope
- Coroutine을 실행시킬 수 있는 fragment의 CoroutineScope
- lifecycle이 끝날 때 제거 된다.
- viewLifecycleOwner : 화면에 출력중인지 화면 뒤에 숨어서 대기중인지
- view의 lifecycle에 access 할 수 있는 객체
- viewLifecycleOwnerLiveData:LiveData<LifecycleOwner>
생명 주기
하나의 Activity에 여러 개의 Fragment를 불러서 사용할 때 Fragment는 Activity와 같은 생명주기를 유지하는데 Fragment에서 불러서 사용하는 View는 View가 화면에서 사용하지 않으면 Fragment는 유지 되지만 View는 생명주기가 끝난다
Activity —————————————————————————————————> destory
Fragment1 ———————————————————————————————> destory
View ———————> destoryView View —————→ destoryView
Fragment2 ———————————————————————————————> destory
View ———————> destoryView
Fragment3 ———————————————————————————————> destory
Fragment는 lifecycle이 두 개이다. 자기 자신의 생명주기와 View 생명주기도 갖는다.
FragmentManager
- Activity 에 포함된 fragment들의 추가, 삭제, 교환, 백 스택 관리
- 기존에는 Activity의 getSupportFragmentManager( )를 통해 개발자가 직접 관리
- Android Jetpack의 Navigation
- Navigation에서 FragmentManager를 사용하지만 개발자가 직접 사용하지는 않음
- 직관적으로 Fragment들을 관리할 수 있음.
Navigation
Android Jetpack
- 구성 요소
- Navigation Graph : 앱 내에서 이동 가능한 요소들을 표기하고 어디로 이동할지가 그려진 그래프.
- xml 파일이지만 GUI처럼 작업 가능하다.
- 중첩하여 설계할 수도 있다.
- NavHost : 탐색의 각 항목이 출력 될 자리. 빈 액자 같은 역할
- NavController : NavHost를 대상 콘텐츠로 전환하는 역할.
- Navigation Graph : 앱 내에서 이동 가능한 요소들을 표기하고 어디로 이동할지가 그려진 그래프.
실습 프로젝트
- 새 프로젝트 생성
- 이름: Trivia
- 패키지: com.example.trivia
- View Binding 적용
- MainActivity는 코드 그냥 둠
- 모든 UI는 Fragment에 그릴 예정
Dependency
libs.versions.toml
[versions]
agp = "8.3.2"
kotlin = "1.9.0"
coreKtx = "1.13.1"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
appcompat = "1.6.1"
material = "1.12.0"
activity = "1.9.0"
constraintlayout = "2.1.4"
navigation = "2.7.7" # 추가
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-navigation-ui = {group = "androidx.navigation", name = "navigation-ui-ktx", version.ref="navigation"} # 추가
androidx-navigation-fragment = {group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref="navigation"} # 추가
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
build.gradle.kts(Module:app)
android {
buildFeatures.viewBinding = true
dependencies {
implementation(libs.androidx.navigation.ui)
implementation(libs.androidx.navigation.fragment)
....
}
Fragment 추가
- 구성 계획
• IntroFragment: 로고와 들어가기 버튼
• AvatarFragment: 아바타 및 닉네임 선택 화면
• QuestionFragment: 퀴즈 화면
• WinFragment: 정답 화면
• LoseFragment: 오답 화면 - com.example.trivia.ui 패키지 추가
- UI 패키지에서 우클릭 > New > Fragment > Fragment(with ViewModel)


- 모든 소스코드에서 필요 없는 부분은 삭제 : onCreateView 함수 빼고 모두 삭제
- 자동으로 만들어진 ViewModel 파일 삭제
Fragment의 ViewBinding - 1번째 방법
- Fragment의 binding은 Fragment의 lifecycle에 맞춰 onCreateView( ) 부터 onDestroyView( ) 까지 존재해야 함.
- 이 방법으로 모두 View Binding 적용
class IntroFragment : Fragment() {
private var _binding:FragmentIntroBinding?=null
private val binding get()= _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding= FragmentIntroBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() { // onDestoryView를 override
super.onDestroyView()
_binding = null
}
}
Fragment의 특성 상 onCreateView는 여러번 불린다. 그러기에 binding을 null이 가능하도록 만들고 onCreateView에서 binding 변수에 값을 대입시켜줌
이전의 binding 변수가 있으면 이전의 binding 변수 참조, 만약 onCreateView밖에서 메모리 Leak이 발생할 수도 있다
만약 View가 Destory되면 binding 변수를 null로 설정, 만약 다시 UI에 불린다면(onCreateView) 에서 _binding에 값을 넣음
Fragment의 ViewBinding - 2번째 방법
- Fragment의 View가 Destroy될 때 함께 값을 clear 하는 AutoClearedValue를 구현하여 이를 ViewBinding에 사용한다.
- fragment.viewLifecycleOwnerLiveData 를 observe 하여 onDestroy 상황에서 관련 객체를 해제하는 방법
- 코드
- 사용 예
Fragment 추가
- 각 Fragment의 레이아웃 파일을 다음과 같이 수정하고 자신의 이름이 뜨도록 수정한다.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.IntroFragment"> <!-- 각 파일에 맞는지 이 부분 확인 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="IntroFragment"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</FrameLayout>
Navigation Graph 추가
- res 폴더에서 우클릭 > New > Android Resource Directory
- Resource type을 navigation을 선택 후 Ok


- res/navigation에서 우클릭 > New > Navigation Resource File
- File name: nav_graph
NavHost 설정
- 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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph"
/>
<!-- app:defaultNavHost app:navGpaph 속성 추가 -->
</androidx.constraintlayout.widget.ConstraintLayout>
Navigation Graph 작성하기
- activity_main.xml에서 NavHost를 잘 작성했다면 nav_graph.xml의 디자인 화면은 다음과 같다.


- 화면 상단의 + 버튼을 클릭해 IntroFragment를 추가해본다.

- 나머지 Fragment도 불러온 다음 선으로 연결한다.


- nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="@+id/nav_graph"
app:startDestination="@id/introFragment">
<!-- 시작하는 화면 설정-->
<fragment
android:id="@+id/introFragment"
android:name="com.lunadev.trivia.ui.IntroFragment"
android:label="intro_fragment"
tools:layout="@layout/intro_fragment" >
<action
android:id="@+id/action_introFragment_to_avatarFragment"
app:destination="@id/avatarFragment" />
</fragment>
<fragment
android:id="@+id/avatarFragment"
android:name="com.lunadev.trivia.ui.AvatarFragment"
android:label="fragment_avatar"
tools:layout="@layout/fragment_avatar" >
<action
android:id="@+id/action_avatarFragment_to_questionFragment"
app:destination="@id/questionFragment" />
</fragment>
<fragment
android:id="@+id/questionFragment"
android:name="com.lunadev.trivia.ui.QuestionFragment"
android:label="fragment_question"
tools:layout="@layout/fragment_question" >
<action
android:id="@+id/action_questionFragment_to_winFragment"
app:destination="@id/winFragment" />
<action
android:id="@+id/action_questionFragment_to_loseFragment"
app:destination="@id/loseFragment" />
</fragment>
<fragment
android:id="@+id/winFragment"
android:name="com.lunadev.trivia.ui.WinFragment"
android:label="fragment_win"
tools:layout="@layout/fragment_win" >
<action
android:id="@+id/action_winFragment_to_avatarFragment"
app:destination="@id/avatarFragment" />
</fragment>
<fragment
android:id="@+id/loseFragment"
android:name="com.lunadev.trivia.ui.LoseFragment"
android:label="fragment_lose"
tools:layout="@layout/fragment_lose" />
</navigation>
테스트
- 앱을 실행하여 첫 화면에 IntroFragment가 출력되는지 확인
- 정상 동작 확인하엿으면 각 fragment의 레이아웃에 디자인 작업을 진행한다.

Intro → Avatar로 이동하기
- IntroFragment.kt 파일 수정 : onViewCreated 함수 구현

- AvatarFragment → QuestionFragment
- QuestionFragment → Win 또는 Lose로 이동하는 코드 작성
- Win/LoseFragment → 다시 하기 선택 시 AvatarFragment로 이동하는 코드 작성
- AvatarFragment.kt 파일
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonNext.setOnClickListener {
findNavController().navigate(R.id.action_avatarFragment_to_questionFragment)
// R.id의 경로는 nav_graph에 해당 fragment에 정의된 action의 id를 사용함
}
}
- QuestionFragment.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonOne.setOnClickListener {
findNavController().navigate(R.id.action_questionFragment_to_loseFragment)
}
binding.buttonTwo.setOnClickListener {
findNavController().navigate(R.id.action_questionFragment_to_winFragment)
}
}
- LoseFragment.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonReplay.setOnClickListener {
findNavController().navigate(R.id.action_loseFragment_to_avatarFragment)
}
}
- WinFragment.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonReplay.setOnClickListener {
findNavController().navigate(R.id.action_winFragment_to_avatarFragment)
}
}
Fragment와 ViewModel
ViewModel 다시 보기
- 다음과 같은 코드라면 어떻게 동작할까?
- MyViewModel 객체를 MainActivity와 SecondActivity가 각각 만들어서 사용한다면?
- 각각 다른 ViewModel 객체가 2개 생기고 변수는 각각 관리된다.
- Activity에서와 마찬가지로 Fragment에서 자신만의 ViewModel을 생성하더라도 해당 ViewModel은 자신만의 것이 된다.
- 즉 Owner 객체가 같아야 같은
- 데이터에 접근한다.

- Activity가 Destroy 되면 ViewModel도 Destroy된다.
- 화면 회전 등의 이유로 Destroy된 다음 다시 그려도 데이터를 유지하려면?
1.ViewModel 객체를 생성할 때 ViewModelProvider를 사용한다.
val model:MyViewModel = ViewModelProvider(this)[MyViewModel::class.java]
2. build.gradle.kts(Module:app) 에 다음 dependency를 확인
implementation(libs.androidx.activity)
by viewModels() 를 사용한다
val model:MyViewModel by viewModels()
Activity와 Fragment의 ViewModel
- 하나의 Activity에 여러 Fragment가 속해 있을 경우
- Lifecycle이 가장 긴 것은 Activity
- Activity가 ViewModel을 초기화 한 경우 Fragment들이 access 해서 사용할 수 있다.
- Fragment는 자신이 포함된 activity에 대한 reference를 가지고 있음.
- ViewModelProvider 방법 가능: owner로 activity를 넘기면 됨.
- by activityViewModels() 가능: build.gradle 수정 후 사용
ViewModel를 하나로 모든 Fragment가 같은 ViewModel을 불러서 사용할 수 있음
→ 공동으로 같은 ViewModel 사용 가능(같은 데이터 공유 가능)
Fragment 간의 데이터 공유 - 1번 방법
ViewModelProvider 사용
- 프로젝트에 MainViewModel 클래스 추가
class MainViewModel:ViewModel() {
val avatarDrawable = MutableLiveData<Int>()
}
- question_fragment.xml 에 ImageView 하나 추가
<ImageView
android:id="@+id/imageViewAvatar"
android:layout_width="90dp"
android:layout_height="90dp"
android:layout_marginTop="24dp"
android:src="@drawable/m1"
android:contentDescription="Avatar Image"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
앞에 화면에서 아바타를 선택하고 넘어오면 선택된 이미지를 뜨게 끔 하기 위함
AvatarFragment 수정
- 변수 추가
class AvatarFragment : Fragment() {
private var _binding: AvatarFragmentBinding?=null
private val binding get()= _binding!!
private lateinit var viewModel:MainViewModel
private val imageId = mapOf(
R.id.buttonM1 to R.drawable.m1,
R.id.buttonM2 to R.drawable.m2,
R.id.buttonM3 to R.drawable.m3,
R.id.buttonW1 to R.drawable.w1,
R.id.buttonW2 to R.drawable.w2,
R.id.buttonW3 to R.drawable.w3,
)
private fun onAvatar(v:View){
viewModel.avatarDrawable.value=imageId[v.id]
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel= ViewModelProvider(requireActivity())[MainViewModel::class.java]
// requireActivity 로 모든 Fragment가 공통으로 사용할 수 있게 해줌
binding.buttonM1.setOnClickListener(::onAvatar)
binding.buttonM2.setOnClickListener(::onAvatar)
binding.buttonM3.setOnClickListener(::onAvatar)
binding.buttonW1.setOnClickListener(::onAvatar)
binding.buttonW2.setOnClickListener(::onAvatar)
binding.buttonW3.setOnClickListener(::onAvatar)
binding.buttonNext.setOnClickListener {
findNavController().navigate(R.id.action_avatarFragment_to_questionFragment)
}
}
- 변수 추가
class AvatarFragment : Fragment() {
private var _binding: AvatarFragmentBinding?=null
private val binding get()= _binding!!
private lateinit var viewModel:MainViewModel
private val imageId = mapOf(
R.id.buttonM1 to R.drawable.m1,
R.id.buttonM2 to R.drawable.m2,
R.id.buttonM3 to R.drawable.m3,
R.id.buttonW1 to R.drawable.w1,
R.id.buttonW2 to R.drawable.w2,
R.id.buttonW3 to R.drawable.w3,
)
private fun onAvatar(v:View){
viewModel.avatarDrawable.value=imageId[v.id]
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel= ViewModelProvider(requireActivity())[MainViewModel::class.java]
// requireActivity 로 모든 Fragment가 공통으로 사용할 수 있게 해줌
binding.buttonM1.setOnClickListener(::onAvatar)
binding.buttonM2.setOnClickListener(::onAvatar)
binding.buttonM3.setOnClickListener(::onAvatar)
binding.buttonW1.setOnClickListener(::onAvatar)
binding.buttonW2.setOnClickListener(::onAvatar)
binding.buttonW3.setOnClickListener(::onAvatar)
binding.buttonNext.setOnClickListener {
findNavController().navigate(R.id.action_avatarFragment_to_questionFragment)
}
}
Fragment + Navigation
Fragment + ViewModel
Fragment
https://developer.android.com/guide/fragments
- 앱에서 사용하는 UI를 재사용할 수 있도록 해 주는 클래스
- 자체의 코드와 레이아웃을 가지고 수명 주기를 가지고 있음.
- Activity와의 관계
- Android Application Component는 아니기 때문에 독립적으로 존재할 수는 없음. → Activity 또는 다른 Fragment에서 불러써야 함.
- 하나의 Activity에 여러 개의 Fragment를 사용할 수 있음. → 회원가입 시 아이디 비밀번호만 입력받고, 그 다음에 여러가지 정보를 입력하는 화면으로 이동 그러한 형태로 사용할 수 도 있음
- Activity는 STARTED 수명주기일 때 Fragment를 추가, 교체, 삭제할 수 있음.
- BottomNavigationView, ViewPager2 등 JetPack과 호환

- 필수 Override 함수 : onCreateView()
- 레이아웃을 지정하여 View 생성
- Lifecycle을 가진다.
- Fragment 자체의 lifecycle
- View의 lifecycle
- ⇒ 두가지의 lifecyle이 다르다.
Fragment Lifecycle
- lifecycle
- Fragment의 lifecycle에 access 할 수 있는 객체
- lifecycleScope
- Coroutine을 실행시킬 수 있는 fragment의 CoroutineScope
- lifecycle이 끝날 때 제거 된다.
- viewLifecycleOwner : 화면에 출력중인지 화면 뒤에 숨어서 대기중인지
- view의 lifecycle에 access 할 수 있는 객체
- viewLifecycleOwnerLiveData:LiveData<LifecycleOwner>
생명 주기
하나의 Activity에 여러 개의 Fragment를 불러서 사용할 때 Fragment는 Activity와 같은 생명주기를 유지하는데 Fragment에서 불러서 사용하는 View는 View가 화면에서 사용하지 않으면 Fragment는 유지 되지만 View는 생명주기가 끝난다
Activity —————————————————————————————————> destory
Fragment1 ———————————————————————————————> destory
View ———————> destoryView View —————→ destoryView
Fragment2 ———————————————————————————————> destory
View ———————> destoryView
Fragment3 ———————————————————————————————> destory
Fragment는 lifecycle이 두 개이다. 자기 자신의 생명주기와 View 생명주기도 갖는다.
FragmentManager
- Activity 에 포함된 fragment들의 추가, 삭제, 교환, 백 스택 관리
- 기존에는 Activity의 getSupportFragmentManager( )를 통해 개발자가 직접 관리
- Android Jetpack의 Navigation
- Navigation에서 FragmentManager를 사용하지만 개발자가 직접 사용하지는 않음
- 직관적으로 Fragment들을 관리할 수 있음.
Navigation
Android Jetpack
- 구성 요소
- Navigation Graph : 앱 내에서 이동 가능한 요소들을 표기하고 어디로 이동할지가 그려진 그래프.
- xml 파일이지만 GUI처럼 작업 가능하다.
- 중첩하여 설계할 수도 있다.
- NavHost : 탐색의 각 항목이 출력 될 자리. 빈 액자 같은 역할
- NavController : NavHost를 대상 콘텐츠로 전환하는 역할.
- Navigation Graph : 앱 내에서 이동 가능한 요소들을 표기하고 어디로 이동할지가 그려진 그래프.
실습 프로젝트
- 새 프로젝트 생성
- 이름: Trivia
- 패키지: com.example.trivia
- View Binding 적용
- MainActivity는 코드 그냥 둠
- 모든 UI는 Fragment에 그릴 예정
Dependency
libs.versions.toml
[versions]
agp = "8.3.2"
kotlin = "1.9.0"
coreKtx = "1.13.1"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
appcompat = "1.6.1"
material = "1.12.0"
activity = "1.9.0"
constraintlayout = "2.1.4"
navigation = "2.7.7" # 추가
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-navigation-ui = {group = "androidx.navigation", name = "navigation-ui-ktx", version.ref="navigation"} # 추가
androidx-navigation-fragment = {group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref="navigation"} # 추가
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
build.gradle.kts(Module:app)
android {
buildFeatures.viewBinding = true
dependencies {
implementation(libs.androidx.navigation.ui)
implementation(libs.androidx.navigation.fragment)
....
}
Fragment 추가
- 구성 계획
• IntroFragment: 로고와 들어가기 버튼
• AvatarFragment: 아바타 및 닉네임 선택 화면
• QuestionFragment: 퀴즈 화면
• WinFragment: 정답 화면
• LoseFragment: 오답 화면 - com.example.trivia.ui 패키지 추가
- UI 패키지에서 우클릭 > New > Fragment > Fragment(with ViewModel)


- 모든 소스코드에서 필요 없는 부분은 삭제 : onCreateView 함수 빼고 모두 삭제
- 자동으로 만들어진 ViewModel 파일 삭제
Fragment의 ViewBinding - 1번째 방법
- Fragment의 binding은 Fragment의 lifecycle에 맞춰 onCreateView( ) 부터 onDestroyView( ) 까지 존재해야 함.
- 이 방법으로 모두 View Binding 적용
class IntroFragment : Fragment() {
private var _binding:FragmentIntroBinding?=null
private val binding get()= _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding= FragmentIntroBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() { // onDestoryView를 override
super.onDestroyView()
_binding = null
}
}
Fragment의 특성 상 onCreateView는 여러번 불린다. 그러기에 binding을 null이 가능하도록 만들고 onCreateView에서 binding 변수에 값을 대입시켜줌
이전의 binding 변수가 있으면 이전의 binding 변수 참조, 만약 onCreateView밖에서 메모리 Leak이 발생할 수도 있다
만약 View가 Destory되면 binding 변수를 null로 설정, 만약 다시 UI에 불린다면(onCreateView) 에서 _binding에 값을 넣음
Fragment의 ViewBinding - 2번째 방법
- Fragment의 View가 Destroy될 때 함께 값을 clear 하는 AutoClearedValue를 구현하여 이를 ViewBinding에 사용한다.
- fragment.viewLifecycleOwnerLiveData 를 observe 하여 onDestroy 상황에서 관련 객체를 해제하는 방법
- 코드
- 사용 예
Fragment 추가
- 각 Fragment의 레이아웃 파일을 다음과 같이 수정하고 자신의 이름이 뜨도록 수정한다.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.IntroFragment"> <!-- 각 파일에 맞는지 이 부분 확인 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="IntroFragment"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</FrameLayout>
Navigation Graph 추가
- res 폴더에서 우클릭 > New > Android Resource Directory
- Resource type을 navigation을 선택 후 Ok


- res/navigation에서 우클릭 > New > Navigation Resource File
- File name: nav_graph
NavHost 설정
- 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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph"
/>
<!-- app:defaultNavHost app:navGpaph 속성 추가 -->
</androidx.constraintlayout.widget.ConstraintLayout>
Navigation Graph 작성하기
- activity_main.xml에서 NavHost를 잘 작성했다면 nav_graph.xml의 디자인 화면은 다음과 같다.


- 화면 상단의 + 버튼을 클릭해 IntroFragment를 추가해본다.

- 나머지 Fragment도 불러온 다음 선으로 연결한다.


- nav_graph.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="@+id/nav_graph"
app:startDestination="@id/introFragment">
<!-- 시작하는 화면 설정-->
<fragment
android:id="@+id/introFragment"
android:name="com.lunadev.trivia.ui.IntroFragment"
android:label="intro_fragment"
tools:layout="@layout/intro_fragment" >
<action
android:id="@+id/action_introFragment_to_avatarFragment"
app:destination="@id/avatarFragment" />
</fragment>
<fragment
android:id="@+id/avatarFragment"
android:name="com.lunadev.trivia.ui.AvatarFragment"
android:label="fragment_avatar"
tools:layout="@layout/fragment_avatar" >
<action
android:id="@+id/action_avatarFragment_to_questionFragment"
app:destination="@id/questionFragment" />
</fragment>
<fragment
android:id="@+id/questionFragment"
android:name="com.lunadev.trivia.ui.QuestionFragment"
android:label="fragment_question"
tools:layout="@layout/fragment_question" >
<action
android:id="@+id/action_questionFragment_to_winFragment"
app:destination="@id/winFragment" />
<action
android:id="@+id/action_questionFragment_to_loseFragment"
app:destination="@id/loseFragment" />
</fragment>
<fragment
android:id="@+id/winFragment"
android:name="com.lunadev.trivia.ui.WinFragment"
android:label="fragment_win"
tools:layout="@layout/fragment_win" >
<action
android:id="@+id/action_winFragment_to_avatarFragment"
app:destination="@id/avatarFragment" />
</fragment>
<fragment
android:id="@+id/loseFragment"
android:name="com.lunadev.trivia.ui.LoseFragment"
android:label="fragment_lose"
tools:layout="@layout/fragment_lose" />
</navigation>
테스트
- 앱을 실행하여 첫 화면에 IntroFragment가 출력되는지 확인
- 정상 동작 확인하엿으면 각 fragment의 레이아웃에 디자인 작업을 진행한다.

Intro → Avatar로 이동하기
- IntroFragment.kt 파일 수정 : onViewCreated 함수 구현

- AvatarFragment → QuestionFragment
- QuestionFragment → Win 또는 Lose로 이동하는 코드 작성
- Win/LoseFragment → 다시 하기 선택 시 AvatarFragment로 이동하는 코드 작성
- AvatarFragment.kt 파일
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonNext.setOnClickListener {
findNavController().navigate(R.id.action_avatarFragment_to_questionFragment)
// R.id의 경로는 nav_graph에 해당 fragment에 정의된 action의 id를 사용함
}
}
- QuestionFragment.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonOne.setOnClickListener {
findNavController().navigate(R.id.action_questionFragment_to_loseFragment)
}
binding.buttonTwo.setOnClickListener {
findNavController().navigate(R.id.action_questionFragment_to_winFragment)
}
}
- LoseFragment.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonReplay.setOnClickListener {
findNavController().navigate(R.id.action_loseFragment_to_avatarFragment)
}
}
- WinFragment.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonReplay.setOnClickListener {
findNavController().navigate(R.id.action_winFragment_to_avatarFragment)
}
}
Fragment와 ViewModel
ViewModel 다시 보기
- 다음과 같은 코드라면 어떻게 동작할까?
- MyViewModel 객체를 MainActivity와 SecondActivity가 각각 만들어서 사용한다면?
- 각각 다른 ViewModel 객체가 2개 생기고 변수는 각각 관리된다.
- Activity에서와 마찬가지로 Fragment에서 자신만의 ViewModel을 생성하더라도 해당 ViewModel은 자신만의 것이 된다.
- 즉 Owner 객체가 같아야 같은
- 데이터에 접근한다.

- Activity가 Destroy 되면 ViewModel도 Destroy된다.
- 화면 회전 등의 이유로 Destroy된 다음 다시 그려도 데이터를 유지하려면?
1.ViewModel 객체를 생성할 때 ViewModelProvider를 사용한다.
val model:MyViewModel = ViewModelProvider(this)[MyViewModel::class.java]
2. build.gradle.kts(Module:app) 에 다음 dependency를 확인
implementation(libs.androidx.activity)
by viewModels() 를 사용한다
val model:MyViewModel by viewModels()
Activity와 Fragment의 ViewModel
- 하나의 Activity에 여러 Fragment가 속해 있을 경우
- Lifecycle이 가장 긴 것은 Activity
- Activity가 ViewModel을 초기화 한 경우 Fragment들이 access 해서 사용할 수 있다.
- Fragment는 자신이 포함된 activity에 대한 reference를 가지고 있음.
- ViewModelProvider 방법 가능: owner로 activity를 넘기면 됨.
- by activityViewModels() 가능: build.gradle 수정 후 사용
ViewModel를 하나로 모든 Fragment가 같은 ViewModel을 불러서 사용할 수 있음
→ 공동으로 같은 ViewModel 사용 가능(같은 데이터 공유 가능)
Fragment 간의 데이터 공유 - 1번 방법
ViewModelProvider 사용
- 프로젝트에 MainViewModel 클래스 추가
class MainViewModel:ViewModel() {
val avatarDrawable = MutableLiveData<Int>()
}
- question_fragment.xml 에 ImageView 하나 추가
<ImageView
android:id="@+id/imageViewAvatar"
android:layout_width="90dp"
android:layout_height="90dp"
android:layout_marginTop="24dp"
android:src="@drawable/m1"
android:contentDescription="Avatar Image"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
앞에 화면에서 아바타를 선택하고 넘어오면 선택된 이미지를 뜨게 끔 하기 위함
AvatarFragment 수정
- 변수 추가
class AvatarFragment : Fragment() {
private var _binding: AvatarFragmentBinding?=null
private val binding get()= _binding!!
private lateinit var viewModel:MainViewModel
private val imageId = mapOf(
R.id.buttonM1 to R.drawable.m1,
R.id.buttonM2 to R.drawable.m2,
R.id.buttonM3 to R.drawable.m3,
R.id.buttonW1 to R.drawable.w1,
R.id.buttonW2 to R.drawable.w2,
R.id.buttonW3 to R.drawable.w3,
)
private fun onAvatar(v:View){
viewModel.avatarDrawable.value=imageId[v.id]
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel= ViewModelProvider(requireActivity())[MainViewModel::class.java]
// requireActivity 로 모든 Fragment가 공통으로 사용할 수 있게 해줌
binding.buttonM1.setOnClickListener(::onAvatar)
binding.buttonM2.setOnClickListener(::onAvatar)
binding.buttonM3.setOnClickListener(::onAvatar)
binding.buttonW1.setOnClickListener(::onAvatar)
binding.buttonW2.setOnClickListener(::onAvatar)
binding.buttonW3.setOnClickListener(::onAvatar)
binding.buttonNext.setOnClickListener {
findNavController().navigate(R.id.action_avatarFragment_to_questionFragment)
}
}
- 변수 추가
class AvatarFragment : Fragment() {
private var _binding: AvatarFragmentBinding?=null
private val binding get()= _binding!!
private lateinit var viewModel:MainViewModel
private val imageId = mapOf(
R.id.buttonM1 to R.drawable.m1,
R.id.buttonM2 to R.drawable.m2,
R.id.buttonM3 to R.drawable.m3,
R.id.buttonW1 to R.drawable.w1,
R.id.buttonW2 to R.drawable.w2,
R.id.buttonW3 to R.drawable.w3,
)
private fun onAvatar(v:View){
viewModel.avatarDrawable.value=imageId[v.id]
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel= ViewModelProvider(requireActivity())[MainViewModel::class.java]
// requireActivity 로 모든 Fragment가 공통으로 사용할 수 있게 해줌
binding.buttonM1.setOnClickListener(::onAvatar)
binding.buttonM2.setOnClickListener(::onAvatar)
binding.buttonM3.setOnClickListener(::onAvatar)
binding.buttonW1.setOnClickListener(::onAvatar)
binding.buttonW2.setOnClickListener(::onAvatar)
binding.buttonW3.setOnClickListener(::onAvatar)
binding.buttonNext.setOnClickListener {
findNavController().navigate(R.id.action_avatarFragment_to_questionFragment)
}
}