일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 아비투스
- 리얼하다
- 소프시스
- T자형인재
- 한달독서
- 재택근무
- 1일1커밋
- 베드테이블
- 면접
- 어떻게 나답게 살 것인가
- 목적 중심 리더십
- 베드트레이
- 한달어스
- 한단어의힘
- 좌식테이블
- 소프시스 밤부 좌식 엑슬 테이블
- 캐치마인드
- 프래그먼트
- 목적중심리더십
- 안드로이드
- 끝말잇기
- 한달브런치북만들기
- 커스텀린트
- 지지않는다는말
- 슬기로운 온라인 게임
- 브런치작가되기
- 테트리스
- 북한살둘레길
- 함수형 프로그래밍
- 자취필수템
- Today
- Total
정상에서 IT를 외치다
[Android, FragmentFactory] framgnet에서 newInstance() 쓰지 말라고? 본문
안녕하세요. 블랙진입니다.
우리는 프래그먼트를 인자와 함께 생성할 때 newInstance()를 사용하곤 합니다. 왜 그럴까요? 흔히 알고 있는 두가지 이유가 있을 겁니다.
1. 프래그먼트 재생성(화면 회전과 같은)시 빈생성자가 있어야 한다.
2. 재생성시 받아온 데이터를 유지하기 위해서 사용한다.
우리가 자주 사용하는 코드
companion object {
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
@JvmStatic
fun newInstance(param1: String, param2: String) =
MainFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
newInstance() 방식은 빈 생성자를 만들고 bundle에 데이터를 넣어 처리해 주고 있습니다. 이렇게 함으로써 화면 회전과 같은 재사용이 되었을 때 데 받아온 데이터를 유지하며 화면에 보여줄 수 있습니다.
빈 생성자 없이 사용한다면?
MainFragment에 두개의 파라미터를 가지는 생성자를 만들어 줍니다.
class MainFragment(param1: String, param2: String) : Fragment()
MainActivity에서 함수를 호출해주면 정상적으로 화면을 보여줍니다.
val fragment = MainFragment("black","jin", this)
supportFragmentManager.beginTransaction()
.replace(R.id.container_fragment, fragment)
.commitNow()
하지만 화면을 회전하게 되면 아래 문구와 함께 InstantiationException이 발생합니다.
could not find Fragment constructor
이는 프래그먼트의 빈 생성자가 없기 때문에 발생하는 에러입니다.
위 이유에 대해 구글 문서를 확인해보겠습니다.
All subclasses of Fragment must include a public no-argument constructor. The framework will often re-instantiate a fragment class when needed, in particular during state restore, and needs to be able to find this constructor to instantiate it. If the no-argument constructor is not available, a runtime exception will occur in some cases during state restore.
출처 - Google Developer
내용을 요약하면 모든 프레그먼트는 빈 생성자를 가져야 합니다. 그렇지 않다면 메모리 부족과 같은 현상으로 프레그먼트를 재생성할 때 런타임 에러를 유발합니다. 왜 그런지 위에서 언급만 했지 아마 이유는 몰랐을 겁니다. 그 이유는 Fragment.java에 있는 instantitae 함수를 보면 알 수 있습니다.
Fragment.java
@NonNull
public static Fragment instantiate(@NonNull Context context, @NonNull String fname,
@Nullable Bundle args) {
try {
Class<? extends Fragment> clazz = FragmentFactory.loadFragmentClass(
context.getClassLoader(), fname);
Fragment f = clazz.getConstructor().newInstance();
if (args != null) {
args.setClassLoader(f.getClass().getClassLoader());
f.setArguments(args);
}
return f;
} catch (java.lang.InstantiationException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": make sure class name exists, is public, and has an"
+ " empty constructor that is public", e);
} catch (IllegalAccessException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": make sure class name exists, is public, and has an"
+ " empty constructor that is public", e);
} catch (NoSuchMethodException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": could not find Fragment constructor", e);
} catch (InvocationTargetException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": calling Fragment constructor caused an exception", e);
}
}
위 코드 중 아래 함수를 자세히 보겠습니다.
Class<? extends Fragment> clazz = FragmentFactory.loadFragmentClass(
context.getClassLoader(), fname);
Fragment f = clazz.getConstructor().newInstance();
if (args != null) {
args.setClassLoader(f.getClass().getClassLoader());
f.setArguments(args);
}
이 부분을 보면 newInstance() 호출합니다. 이는 빈 생성자를 가진 Fragment를 의미합니다. 그래서 위 함수가 호출 될 때 빈 생성자가 없다면 에러를 발생하게 됩니다.
또한 bundle에 있는 데이터를 그대로 setArguments 해주기 때문에 bundle에 데이터를 넣어야만 화면 재생성시 데이터를 보존할 수 있게 됩니다.
우리는 위 두 가지 이유에서 인자를 가진 프래그먼트 생성자를 사용할 수 없었습니다. 하지만!
AndroidX 부터는 다른 방식으로 초기화 해줘도 가능합니다!
우리는 위 방식을 고수해 왔지만 AndroidX로 업데이트 되면서 위에서 살펴본 instantiate()는 deprecated 되었습니다.
@Deprecated
@NonNull
public static Fragment instantiate(@NonNull Context context, @NonNull String fname,
@Nullable Bundle args) {
//...
}
구글에서는 위 방식이 아닌 이제 FragmentFactory의 instantiate를 사용해 구현하라고 합니다.
* @deprecated Use {@link FragmentManager#getFragmentFactory()} and
* {@link FragmentFactory#instantiate(ClassLoader, String)}, manually calling
* {@link #setArguments(Bundle)} on the returned Fragment.
위 방법을 사용하는 이유는 기존에는 프래그먼트에 빈 생성자가 없다면 구성 변경 및 앱의 프로세스 재생성과 같은 특정 상황에서 시스템은 프래그먼트를 초기화 하지 못했습니다. 이 점을 해결하기 위해 FragmentFractory를 사용하게 되었고 Fragment에 필요한 인수 및 종속성을 제고하여 시스템이 Fragment를 더욱 잘 초기화하는데 도움을 줍니다.
Android X에서 인자가 있는 프래그먼트 생성 방법
1. 빈 생성자 없이 인자가 있는 생성자를 만들어 줍니다.
class MainFragment(private val param1: String, private val param2: String) : Fragment() {
//...}
2. FragmentFactory의 instantiate를 구현해 줍니다.
class MainFragmentFactoryImpl: FragmentFactory() {
override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
return when (className) {
MainFragment::class.java.name -> MainFragment("black","jin")
else -> super.instantiate(classLoader, className)
}
}
}
3. MainActivity에서 다음과 같이 구현합니다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
supportFragmentManager.fragmentFactory = MainFragmentFactoryImpl()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, MainFragment::class.java.name)
supportFragmentManager.beginTransaction()
.replace(R.id.container_fragment, fragment)
.commitNow()
}
위 방법을 사용하면 Framgnet.java에 있던 instatiate를 사용하지 않고 우리가 정의한 FragmentFactory의 instatiate 사용해 초기화 하게 됩니다. 여기서 중요한 것은
supportFragmentManager.fragmentFactory = FragmentFactoryImpl()
위 함수를 Activity의 onCreate보다 먼저 선언해 주어야 한다는 것입니다.
이렇게 하면 빈 생성자를 굳이 생성할 필요가 없으며 bundle에 데이터를 넣지 않아도 재생성 상황에서 데이터가 보존됩니다.
FragmentFactory 꼭 필요한가?
그렇지 않습니다. 기존 방식대로 사용하셔도 무방합니다. 다만 인자가 있는 생성자를 사용할 시 반드시 빈 생성자 프래그먼트를 만들어 주어야 합니다. 만약 인자가 있는 생성자와 FragmentFactory를 사용한다면 위 방법을 권장합니다.
Koin을 사용한 Fragment 초기화
FragmentFactory 는 생성자를 주입하는 패턴과 비슷합니다. 이에 안드로이드 의존성 주입 라이브러리 중 하나인 Koin 에서는 위 방법을 지원해 줍니다.
<참고자료>
koin - setupKoinFragmentFactory
proandroiddev - fragmentfactory
'안드로이드' 카테고리의 다른 글
[Android, SingleClick] 싱글 클릭 리스너 구현 (0) | 2020.03.19 |
---|---|
[Android, Null Check] 다중 null 체크 (0) | 2020.03.19 |
[Android, Proguard] 안드로이드 프로가드 설정하기 3 (0) | 2020.03.13 |
[Dagger] Dagger step5 - Binds와 Multi binding (0) | 2020.02.11 |
[Dagger] Dagger step4 - Module 초기화 (0) | 2020.02.11 |