일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- Rxjava Observable
- rxjava cold observable
- android ar 개발
- ANR이란
- 클래스
- 안드로이드 유닛테스트란
- android retrofit login
- jvm이란
- 플러터 설치 2022
- 서비스 vs 쓰레드
- 2022 플러터 설치
- rxjava disposable
- 2022 플러터 안드로이드 스튜디오
- 큐 자바 코드
- 멤버변수
- 안드로이드 라이선스
- 안드로이드 라이선스 종류
- rxjava hot observable
- 안드로이드 os 구조
- 안드로이드 레트로핏 crud
- 객체
- 스택 큐 차이
- ar vr 차이
- 스택 자바 코드
- jvm 작동 원리
- 안드로이드 유닛 테스트 예시
- 안드로이드 유닛 테스트
- 자바 다형성
- 안드로이드 레트로핏 사용법
- 서비스 쓰레드 차이
- Today
- Total
나만을 위한 블로그
[Android] 프래그먼트란? 프래그먼트 기본 사용법 본문
앱을 만들다 보면 언젠간 액티비티로는 부족하거나 액티비티까지 쓰긴 애매한데 싶은 화면을 구현해야 할 수 있다.
이 때 쓸 수 있는 게 프래그먼트다. 사실 프래그먼트는 DrawerLayout, 뷰페이저, 바텀 시트 등을 구현할 때 요긴하게 사용할 수 있고, 프래그먼트를 써서 구현한 예제들도 검색해보면 심심찮게 볼 수 있다.
뷰페이저의 경우 아예 프래그먼트를 써서 구현한 예시 코드를 제시하는 디벨로퍼 문서도 볼 수 있다.
https://developer.android.com/develop/ui/views/animations/screen-slide?hl=ko
그럼 이 프래그먼트는 대체 뭘까? 구현이야 검색해보고 챗지피티 쪼아대면 할 수 있지만, 정확히 프래그먼트가 무엇이고 왜 써야 하는지, 어떤 특징이 있는지 알면 디테일한 부분을 잡아갈 때 도움이 될 것이다.
디벨로퍼의 프래그먼트 공식문서부터 확인해 본다.
https://developer.android.com/guide/fragments?hl=ko
프래그먼트는 앱 UI의 재사용 가능한 부분을 나타낸다. 프래그먼트는 자체 레이아웃을 정의 및 관리하고 자체 생명주기를 가지며 자체 입력 이벤트를 처리할 수 있다. 프래그먼트는 단독으로 실행될 수 없다. 액티비티 or 다른 프래그먼트에서 호스팅해야 한다. 프래그먼트의 뷰 계층 구조는 호스트 뷰 계층 구조의 일부가 되거나 여기에 연결된다
< 모듈성 >
프래그먼트는 UI를 개별 청크로 분할할 수 있게 해서 액티비티 UI에 모듈성, 재사용성을 도입한다. 액티비티는 앱의 UI 주위에 전역 요소를 배치하기에 적합하다. 반대로 프래그먼트는 단일 화면이나 화면 일부의 UI를 정의하고 관리하는 데 더 적합하다
다양한 화면 크기에 반응하는 앱을 생각해 보라. 큰 화면에선 앱이 정적 탐색 창과 목록을 그리드 레이아웃으로 표시하도록 할 수 있다. 작은 화면에선 앱이 하단 탐색 메뉴와 목록을 선형 레이아웃으로 표시하도록 할 수 있다. 액티비티에서 이런 변형을 관리하는 건 쉽지 않다. 컨텐츠에서 탐색 요소를 분리하면 이 프로세스를 더 쉽게 관리할 수 있다. 그러면 액티비티는 올바른 탐색 UI를 표시하고 프래그먼트는 적절한 레이아웃으로 리스트를 표시한다
UI를 프래그먼트로 나누면 런타임 시 액티비티 모양을 더 쉽게 수정할 수 있다. 액티비티가 STARTED 상태 이상에 있는 동안 프래그먼트를 추가, 교체, 삭제할 수 있다. 이런 변경사항 기록을 액티비티에서 관리하는 백스택에 보관할 수 있어서 변경사항을 취소할 수 있다
같은 액티비티 안에서, 여러 액티비티에서, 또는 다른 프래그먼트의 하위 요소로도 동일한 프래그먼트의 여러 인스턴스를 사용할 수 있다. 이 점을 고려해서 자체 UI를 관리하는 데 필요한 로직만 프래그먼트에 제공하라. 한 프래그먼트에 의존하거나, 다른 프래그먼트에서 프래그먼트를 조작하지 마라
예시 이미지는 갤럭시 폴드를 사용한다면 쉽게 이해할 수 있지만, 쓰지 않더라도 이해하는 데 어렵지 않을 것이다.
폴드 기기에서 카톡 또는 설정 앱을 실행할 경우 접힌 상태에선 오른쪽 그림과 같이 일반 기기에서처럼 표시되지만 펼치면 왼쪽 그림과 같이 좌측에 리스트, 우측에 세부 UI가 표시된다.
그럼 프래그먼트를 사용하려면 어떻게 해야 할까? 이 방법 또한 디벨로퍼에 제시돼 있으니, 이 디벨로퍼의 코드를 바탕으로 프래그먼트를 구현해 본다.
https://developer.android.com/guide/fragments/create?hl=ko
먼저 라이브러리를 추가한다. 이후 확인할 코드에서 commit, add 함수를 사용해야 하는데 이 함수는 라이브러리를 임포트해야 사용할 수 있기 때문이다.
implementation "androidx.fragment:fragment-ktx:1.6.2"
그 다음 패키지를 우클릭해서 나오는 메뉴를 아래 사진과 같이 타고 들어가면 빈 프래그먼트를 만들 수 있다.
이름은 디벨로퍼와 동일하게 ExampleFragment로 지었다. 그리고 완료를 누르면 아래와 같은 코드들이 기본 작성된다.
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
* Use the [ExampleFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class ExampleFragment : Fragment() {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_example, container, false)
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment ExampleFragment.
*/
// TODO: Rename and change types and number of parameters
@JvmStatic
fun newInstance(param1: String, param2: String) =
ExampleFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ExampleFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" />
</FrameLayout>
안드로이드가 이 요소들이 실제론 필요없는데 이유없이 넣었을 리는 없다.
하나씩 살펴보는 건 이후에 하고, 먼저 구현해서 제대로 작동하는지부터 확인한다.
메인 액티비티의 XML을 아래와 같이 수정한다.
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container_view"
android:name="com.example.android14test.ExampleFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
프래그먼트를 액티비티 XML에 선언적으로 추가하려면 FragmentContainerView를 사용한다.
FragmentContainerView의 name 속성은 인스턴스화할 프래그먼트의 클래스명을 지정한다. 이후 액티비티 레이아웃이 inflate되면 해당 프래그먼트가 인스턴스화되고, onInflate()가 인스턴스화된 프래그먼트에 호출되며 FragmentTransaction이 FragmentManager에 프래그먼트를 추가하기 위해 생성된다.
코드 작성을 통해(흔히 말하는 프로그래매틱 방식) 프래그먼트를 추가하려면 프래그먼트를 담을 컨테이너로 사용할 FragmentContainerView를 액티비티 XML 안에 포함해야 한다.
<androidx.fragment.app.FragmentContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
다른 건 name 속성이 있냐 없냐 뿐이다. XML에 선언하는 방식과 다르게 프로그래매틱 방식에선 name 속성이 FragmentContainerView에 사용되지 않아서, 특정 프래그먼트가 자동으로 인스턴스화되지 않는다. 대신 프래그먼트를 인스턴스화하고 액티비티 레이아웃에 추가하는 데 FragmentTransaction이 사용된다.
이후 메인 액티비티 코드를 아래와 같이 수정한다.
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.add
import androidx.fragment.app.commit
import com.example.android14test.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
companion object {
private val TAG = this::class.simpleName
}
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
supportFragmentManager.commit {
setReorderingAllowed(true)
add<ExampleFragment>(R.id.fragment_container_view)
}
}
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
}
}
이 상태에서 앱을 실행하면 아래 화면이 표시될 것이다.
메인 액티비티의 XML에는 어떤 뷰도 없고, 전체 화면으로 표시한 FragmentContainerView 뿐이다.
이 상태에서 프래그먼트 XML에 기본 표시되는 "Hello blank fragment"가 표시된다면, 프래그먼트를 적용하는 데 성공한 것이다.
프래그먼트를 만들기만 할 뿐 아니라, 프래그먼트로 보내서 표시해야 하는 데이터가 있다면 아래와 같이 할 수 있다.
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.add
import androidx.fragment.app.commit
import com.example.android14test.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
companion object {
private val TAG = this::class.simpleName
}
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
val bundle = bundleOf("testText" to "이 텍스트를 표시할 거에요")
supportFragmentManager.commit {
setReorderingAllowed(true)
add<ExampleFragment>(
R.id.fragment_container_view,
args = bundle
)
}
}
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
}
}
bundleOf()는 Pair 형태의 가변 인자를 받아서 Bundle을 리턴하는 함수로, 이 안에 ExampleFragment로 전달할 데이터들을 Pair 형태로 넣는다.
그리고 프래그먼트의 XML과 코틀린 파일을 조금 수정한다.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ExampleFragment">
<TextView
android:id="@+id/tvFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment" />
</FrameLayout>
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "testText"
/**
* A simple [Fragment] subclass.
* Use the [ExampleFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class ExampleFragment : Fragment() {
private var testText: String = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
testText = it.getString(ARG_PARAM1) ?: "받은 문자열이 없거나 문자열을 가져오지 못함"
Log.e(this::class.simpleName, "param1 : $testText")
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_example, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<TextView>(R.id.tvFragment).apply {
text = testText
}
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment ExampleFragment.
*/
// TODO: Rename and change types and number of parameters
@JvmStatic
fun newInstance(param1: String, param2: String) =
ExampleFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
}
}
}
}
이제 다시 앱을 실행해 보면 전달받은 문자열이 프래그먼트에 표시되는 걸 볼 수 있다.
프래그먼트에 데이터 바인딩을 적용하면 아래처럼 수정할 수 있다.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="content"
type="String" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ExampleFragment">
<TextView
android:id="@+id/tvFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@{content}" />
</FrameLayout>
</layout>
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import com.example.android14test.databinding.FragmentExampleBinding
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "testText"
/**
* A simple [Fragment] subclass.
* Use the [ExampleFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class ExampleFragment : Fragment() {
private var testText: String = ""
private lateinit var binding: FragmentExampleBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
testText = it.getString(ARG_PARAM1) ?: "받은 문자열이 없거나 문자열을 가져오지 못함"
Log.e(this::class.simpleName, "param1 : $testText")
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_example, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.content = testText
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment ExampleFragment.
*/
// TODO: Rename and change types and number of parameters
@JvmStatic
fun newInstance(param1: String, param2: String) =
ExampleFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
}
}
}
}
프래그먼트의 XML에 데이터 바인딩을 설정한 후 코틀린 파일에서 다뤄야 할 때는 onCreateView에서 다루는 게 좋다.
반드시 이렇게 해야 한다는 건 아니지만, 안드로이드 디벨로퍼의 뷰 바인딩 문서를 확인하면 onCreateView()에서 바인딩 변수를 초기화하라고 말하는 걸 볼 수 있다.
https://developer.android.com/topic/libraries/view-binding?hl=ko#fragments
private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
그럼 왜 굳이 onCreateView여야 하는가? 아래의 onCreateView()의 주석과 스택오버플로우를 참고한다.
프래그먼트가 UI 뷰를 인스턴스화하도록 호출된다. 이는 선택 사항이며 그래픽이 아닌 프래그먼트는 null을 리턴할 수 있다. 이 함수는 onCreate와 onViewCreated 사이에서 호출된다
생성자에서 Fragment(int)를 호출해서 기본 뷰를 리턴할 수 있다. 그렇지 않으면 이 메서드는 null을 리턴한다. 이 메서드의 레이아웃만 확장하고 리턴된 View에서 작동하는 로직을 onViewCreated로 이동하는 게 좋다. 여기서 View를 리턴하면 나중에 View가 해제될 때 onDestroyView에서 호출된다
https://stackoverflow.com/a/68191193
뷰가 생성되는 순간 레이아웃이 inflation되므로 onCreateView에서 initialize 바인딩을 사용한 다음 onViewCreated 및 기타 함수 안에서 이를 쓰는 게 좋다. 또한 누수 방지를 위해 onDestroyView에 "_binding = null"을 추가해야 한다.
이렇게 기본적인 프래그먼트 사용법을 확인했으니 이제 코드에서 사용된 요소들을 하나씩 확인해 본다.
supportFragmentManager
원래 이름은 getSupportFragmentManager지만 코틀린으로 넘어오면서 get이 사라졌다.
FragmentManager를 리턴 타입으로 갖는 함수로, FragmentManager가 뭔지는 아래를 참고한다.
https://developer.android.com/guide/fragments/fragmentmanager?hl=ko
FragmentManager는 프래그먼트를 추가, 삭제, 교체하고 백스택에 추가하는 등의 작업을 실행하는 클래스다. FragmentActivity 및 그 서브클래스(AppCompatActivity 등)는 getSupportFragmentManager()를 통해 FragmentManager에 접근할 수 있다
위 예시에선 FragmentContainerView에 ExampleFragment를 추가하는 작업을 진행했는데, 이 추가 작업이 가능하려면 FragmentManager를 얻어와야 한다는 것이다.
setReorderingAllowed()
다음으로 setReorderingAllowed()인데 이것은 아래 디벨로퍼 링크를 참고한다.
https://developer.android.com/guide/fragments/fragmentmanager?hl=ko#perform
(중략)...setReorderingAllowed(true)는 애니메이션, 전환이 올바르게 작동하도록 트랜잭션과 관련된 프래그먼트의 상태 변경을 최적화한다
트랜잭션 내 및 트랜잭션 간 작업 최적화를 허용할지 여부를 결정한다. 이렇게 하면 중복 작업이 제거되어 취소되는 작업이 재거된다. 예를 들어 2개의 트랜잭션이 함께 실행되는 경우, 하나는 A 프래그먼트를 추가하고 다른 하나는 이를 B 프래그먼트로 대체하면 작업이 취소되고 B 프래그먼트만 추가된다. 즉 A 프래그먼트는 생성 / 파괴 생명주기를 거치지 않을 수 있다
중복 작업 제거의 사이드 이펙트는 프래그먼트 상태가 예상 순서를 벗어난 상태로 변경될 수 있다는 것이다. 예를 들어 한 트랜잭션이 A 프래그먼트를 추가하고 2번째 트랜잭션이 B 프래그먼트를 추가한 다음 3번째 트랜잭션이 A 프래그먼트를 제거하는 경우, 중복 작업을 제거하지 않으면 B 프래그먼트가 생성되는 동안 A 프래그먼트도 존재할 것으로 예상할 수 있는데, 이는 B 프래그먼트가 추가된 후 A 프래그먼트가 제거되기 때문이다. 중복 연산을 제거하면 A 프래그먼트의 추가 / 제거가 최적화되어 B 프래그먼트가 생성될 때 A 프래그먼트가 존재할 거라고 예상할 수 없다
또한 프래그먼트의 상태 변경 순서를 바꿔서 더 나은 트랜지션을 구현할 수 있다. 추가된 프래그먼트는 교체된 프래그먼트보다 먼저 onCreate가 호출되고, 교체된 조각은 onDestroy가 호출될 수 있다. postponeEnterTransition에는 setReorderingAllowed(true)가 필요하며 기본값은 false다
말이 장황한데 핵심은 트랜잭션 간 작업 최적화인 듯하다. 여기서 트랜잭션은 프래그먼트의 변경사항 집합을 이르는 말로, 대표적으로 프래그먼트 추가 / 제거 / 교체 작업을 이른다.
이 메서드에 대한 자세한 내용은 아래 블로그가 설명이 잘 돼 있어 참고하면 도움이 될 것이다.
https://m.blog.naver.com/squart300kg/222837589623
newInstance
ExampleFragment를 만들었을 때 기본 생성된 코드 중 newInstance라는 동반 객체 함수가 있었다.
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment ExampleFragment.
*/
// TODO: Rename and change types and number of parameters
@JvmStatic
fun newInstance(param1: String, param2: String) =
ExampleFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
}
}
}
주석을 번역하면 "제공된 매개변수를 써서 이 프래그먼트의 새로운 인스턴스를 생성하려면 이 팩토리 메서드를 사용하라"다.
디벨로퍼에서 공식적으로 이 함수를 설명하는 문서는 없지만, 이 함수를 사용하는 예시는 아래 링크에서 확인할 수 있다.
https://developer.android.com/reference/android/app/Fragment
그 말은 안드로이드가 공식적으로 제공하는 함수는 아니란 건데, 그렇다면 헬퍼 함수 정도라고 의심해볼 수 있겠다.
newInstance()가 무엇인지는 아래 링크들을 참고한다.
https://stackoverflow.com/a/36113562
함수 이름은 newInstance, getInstance, newFragment 등 원하는 대로 지정할 수 있다. 헬퍼 메서드일 뿐이라 상관없다. 중요한 건 모든 매개변수를 fragment.setArguments(args)에 넣는 것이다. 안드로이드 시스템은 해당 매개변수를 기억하고 프래그먼트가 재생성될 때 이를 사용한다
https://stackoverflow.com/a/36113801
newInstance는 안드로이드 디자인 패턴으로, 프래그먼트에 기본 생성자 외에 다른 생성자가 없어야 하기 때문이다. 따라서 프래그먼트에 매개변수를 전달하기 위해 헬퍼 함수를 정의한다. 반드시 쓸 필요는 없지만 둘 다 A 프래그먼트를 시작하는 2개의 액티비티가 있다고 가정한다. 헬퍼 함수를 쓰지 않을 경우 코드를 복사해서 프래그먼트를 인스턴스화해야 한다
- 누가 newInstance를 호출하는가?
일반적으로 프래그먼트, 액티비티, 어댑터 등을 생성하는 위치에서 초기화(instantiate) 메서드를 사용한다
그리고 Fragment 디벨로퍼 문서를 확인해 보면, newInstance 같은 헬퍼 함수를 써야 하는 이유가 나와 있다.
https://developer.android.com/reference/android/app/Fragment
프래그먼트의 모든 서브클래스에는 인수가 없는 public 생성자가 포함돼야 한다. 프레임워크는 특히 상태 복원 중에 필요할 때 프래그먼트를 다시 인스턴스화하는 경우가 많으므로 이 생성자를 찾아서 인스턴스화할 수 있어야 한다. 인수가 없는 생성자를 쓸 수 없는 경우 상태 복원 중 에러가 발생할 수 있다
newInstance를 써야 하는 이유는 크게 아래와 같이 정리할 수 있다.
- 프래그먼트 초기화 및 생성 코드를 정적 팩토리 메서드 형태로 캡슐화해서 편하게 쓰기 위해
- 상태 복원 시 프래그먼트는 기본 생성자를 사용한다. 이 때 기본 생성자가 없으면 에러가 발생할 수 있다. 이를 막기 위해 newInstance() 안에서 기본 생성자를 써서 프래그먼트를 초기화하고, Bundle에 값을 넣고 setArguments로 전달해 상태 복원 시 사용할 수 있도록 한다
참고 링크들은 아래에 첨부한다.
https://www.androiddesignpatterns.com/2012/05/using-newinstance-to-instantiate.html
https://gunhansancar.com/best-practice-to-instantiate-fragments-with-arguments-in-android/
https://amitshekhar.me/blog/default-constructor-to-create-a-fragment
다른 코드에선 특이한 부분이 없기 때문에 넘어간다.
'Android' 카테고리의 다른 글
[Android] 모바일 브라우저 앱에서 웹뷰 디버깅하는 법 (0) | 2024.04.15 |
---|---|
[Android] 프래그먼트의 생명주기 (0) | 2024.03.31 |
[Android] Appium이란? (0) | 2024.03.10 |
[Android] 안드로이드 14에서 변경된 미디어 권한 대응 방법 (0) | 2024.03.03 |
[Android] @SerializedName이 둘 이상의 필드명을 탐색하게 설정하는 법 (0) | 2024.02.14 |