[Android] Fragment 생성
Crashlytics에 올라온 버그 하나..
기존 사용했던 코드
1
2
3
4
// fragment
companion object {
fun newInstance(param: String) = MainFragment(param)
}
문제는 여기서부터 시작…
아래처럼 수정되어야 했다
1
2
3
4
5
6
7
companion object {
fun newInstance(param: String) = MainFragment().apply {
val args = Bundle()
args.punString(KEY_STRING, param)
this.arguments = args
}
}
이렇게 빈 생성자로 프래그먼트 인스턴스를 생성한 후 번들을 사용해서 데이터를 전달하거나, Fragment Factory를 사용해서 Fragment 생성자를 사용하게끔 됐어야 했다
..!
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.
- 모든 프래그먼트는 빈 생성자를 가져야 한다
- 그렇지 않으면 메모리 부족과 같은 현상으로 프래그먼트를 재생성할 때 런타임 에러를 유발한다
해결방법
-
AndroidX Fragment 초기화
- FragmentFactory를 사용하면 Fragment를 선언할 때 인자가 있는 생성자를 사용할 수 있다.
- androidx.fragment.app 패키지의 Fragment 문서에서 instantiate()의 설명을 다시 찾아보면 deprecated 되었다는 것을 알 수 있다. Fragment의 instantiate 대신 FragmentFactory의 instantiate를 사용하라고 나와있다.
- FragmentFactory를 상속하는 클래스를 생성
1 2 3 4 5 6 7 8
class MyFragmentFactory(param: String): FragmentFactory() { override fun instantiate(classLoader: ClassLoader, className: String): Fragment { return when (className) { MainFragment::class.java.name -> MainFragment(param) else -> super.instantiate(classLoader, className) } } }
- activity에서 생성
1 2 3 4 5 6 7 8 9 10 11
override fun onCreate(savedInstanceState: Bundle?) { supportFragmentManager.fragmentFactory = MyFragmentFactory() super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(this, layoutId) val fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, MainFragment::class.java.name) supportFragmentManager.commit { add(containerId, fragment, MyFragment.TAG) addToBackStack(null) } }
super.onCreate(savedInstanceState)
전에 Factory객체를 설정해야 한다- 이렇게 하면 빈 생성자 + Bundle을 사용하지 않아도 Fragment가 재생성될 때 인자로 넘긴 데이터가 유지된다!
-
Koin을 사용한 Fragment 생성
- dependency 추가
1 2 3
implementation "org.koin:koin-androidx-scope:$koin_version" implementation "org.koin:koin-androidx-viewmodel:$koin_version" implementation "org.koin:koin-androidx-fragment:$koin_version"
- Application class에 KoinApplication 초기화 수정
1 2 3 4 5
startKoin { androidContext(applicationContext) fragmentFactory() modules(appModules) }
- appModules 수정
1 2 3 4 5 6 7 8 9
/* viewModel */ val viewModelModule = module { ... } /* fragment */ val fragmentModule = module { fragment { (param: String) -> MainFragment(param) } } val appModules = listOf(viewModelModule, fragmentModule)
- 페이저 어댑터에 적용
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
class PagerAdapter( fragment: Fragment, private val list: List<String> ) : FragmentStateAdapter(fragment) { override fun getItemCount() = list.size // 기존 사용했던 코드 // override fun createFragment(position: Int) = MainFragment().newInstance(list[position]) // Koin을 사용한 inject override fun createFragment(position: Int): MainFragment { val fragment by inject(MainFragment::class.java, null) { parametersOf(list[position]) } return fragment } }
- KoinFragmentFactory Class
1
2
3
4
5
6
7
8
9
10
/**
* FragmentFactory for Koin
*/
class KoinFragmentFactory(val scope: Scope? = null) : FragmentFactory(), KoinComponent {
override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
val javaClass = Class.forName(className)
return scope?.let { it.get<Fragment>(javaClass) }
?: getKoin().get(javaClass.kotlin)
}
}
- 위 방법대로 하면 정상적으로 동작은 했지만….앱이 실행중일 때 언어를 바꾸고 다시 앱을 들어가는 경우에 앱이 죽었다…아직 까지 방법을 찾지 못했다…
- 오류를 봐보니 현재 사용하고있는 library들을 못가져오는것 같은데 ..
- 이유를 찾으면 다시 포스팅 해야겠다… 😂
참고사이트