인자를 전달받는 Fragment 생성자
Fragment를 생성할 때 파라미터를 전달받는 생성자를 이용하여 Fragment를 만들어 본 적이 있는가? 아래 코드와 같은 방식으로 Fragment 정의 시 분명 문제가 있었을 것 이다. 아래 Fragment는 문자열을 생성자의 인자로 전달받는 Fragment이다.
class TestFragment constructor (
private val testString:String
): Fragment() {
...
}
이러한 방식은 아래와 같은 런타임 예외를 발생 시킬 수 있다. 분명 Fragment의 생성자가 있음에도 불구하고 생성자가 없다는 예외이다.
could not find Fragment constructor
런타임 예외가 왜 발생하는가?
Fragment 생성자를 찾지 못한다는 이유는 왜 발생하는지부터 확인해야 한다. 안드로이드 개발자 페이지의 Fragment 문서를 확인하면 그 이유를 알 수 있다.
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.
해당 문서에는 인자가 하나도 존재하지 않는 기본 생성자가 필수로 포함되어야 한다고 명시되어 있다. Fragment는 생명주기에 따라서 안드로이드 내부적인 이유로 파괴와 생성을 반복하게 된다. 이 때 파괴 후 생성되는 단계에서 인자가 존재하는 생성자가 아닌 빈 생성자가 호출된다. 그러므로 이때 빈 생성자를 찾지 못하게 되어 문제가 발생하게 된다.
아래 코드를 살펴보자,
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;
}
...
}
- FragmentFactory.loadFragmentClass()를 이용하여 Fragment 클래스를 가져온다.
- getConstructor()를 이용하여 생성자를 가져와 newInstance()로 객체를 생성한다.
- 생성된 Fragment 인스턴스에 전달할 Argument를 설명하고
- 인스턴스를 리턴한다.
위 순서 중 문제가 발생하는 부분은 2번(getConstructor())이다. 여기서 인자가 없는 빈 생성자를 가져오게 되고, 문제가 발생한다. 해당 함수를 설명하는 주석에 명확하게 작성되어 있다.
Create a new instance of a Fragment with the given class name. This is the same as calling its empty
constructor, setting the ClassLoader on the supplied arguments, then calling setArguments(Bundle).
그럼 어떻게 인자를 받는 생성자를 사용할 수 있을까?
Fragment Factory 사용하여 Fragment 생성하기.
FragmentFactory를 상속받는 클래스를 선언하고, instantiate() 함수를 오버라이드 하여 Fragment가 생성될 시 인자를 전달하는 방법을 사용한다.
instantiate() 함수의 인자로 전달된 className을 통해 인자를 전달하여 생성항 Fragment를 구별하여 생성해준다.
class TestFragmentFactory constructor (
private var testString:String
): FragmentFactory() {
override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
return when (className) {
TestFragment::class.java.name -> TestFragment(testString)
else -> super.instantiate(classLoader, className)
}
}
}
이렇게 생성한 FragmentFactory는 Fragment Manager의 fragmentFactory 값을 변경해 줌으로써 적용할 수 있다. 예를 들어 Activity에서 Fragment 생성 시에는 아래와 같이 사용이 가능하다.
FragmentFactory를 생성하고, 해당 FragmentFactory를 supportFragmentManager의 fragmentFactory로 설정해준다. 그 후 instantiate()를 통해 생성된 fragment로 Fragment Container에 Fragment를 추가하여 표시한다.
class MainActivity : AppCompatActivity() {
lateinit var fragmentFactory: TestFragmentFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fragmentFactory = TestFragmentFactory("Test Fragment")
supportFragmentManager.fragmentFactory = fragmentFactory
val fragment = supportFragmentManager.fragmentFactory.instantiate(classLoader, TestFragment::class.java.name)
supportFragmentManager.commit {
add(containerId, fragment)
addToBackStack(null)
}
}
}
Fragment의 빈 생성자가 필요한 이유와 인자가 있는 생성자를 사용하기 위한 방법에 대해 정리하였다. 다음글에서 Navigation과 Hilt를 이용한 방법에 대해서 정리한다. (정리라기 보단 코드의 나열이 되지 않을까..)
참고
https://developer.android.com/reference/android/app/Fragment
'Android > Component' 카테고리의 다른 글
[Android] MutableFlowState의 원자성 보장 (0) | 2022.10.27 |
---|---|
ViewModel의 DataEvent 처리 방법 #1. LiveData (0) | 2022.10.13 |
[Android] 하단 탭 사용하기 (BottomNavigation) (0) | 2022.08.31 |
[Android] WearOS HealthServicesClient 성능 문제 분석기 (0) | 2022.08.16 |
[Android] Google Map Key 관리 (0) | 2022.08.16 |