몇일전에 CustomView에서 viewModel 을 주입하며 삽질한 경험입니다.
보통 커스텀 뷰를 만들면 이런 모습이 많이 나오게 됩니다.
class CustomView
: BaseCustomView<CustomViewBinding> {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
}
근데 만들고 나면 CustomViewModel 을 주입 시켜줘야 하는데 액티비티가 아니기 때문에 by inject() 함수를 사용해 주입해줄 수 없다.
그럴때 간단하게 KoinComponent 를 implement 해주면 된다 그럼 by inject() 를 사용해 주입시킬 수 있다.
class CustomView
: BaseCustomView<CustomViewBinding>, KoinComponent {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
val customViewModel : CustomViewModel by inject()
// 뷰모델 초기화
override fun setTypeArray(typedArray: TypedArray) {
binding.run {
vm = customViewModel
}
}
}
하지만! 이때 by inject() 를 해줘도 customViewModel 의 getValue 가 null 이 나오는 상황이 있다.
보통 이런 실수는 클래스의 생성자에서 viewModel 을 주입하기 때문이다.
-> 문제의 원인을 생각해보면 setTypeArray() 함수가 너무 빠르게 시작되어, 아직 만들어지지않은 CustomViewModel 을 참조하기 때문이다.
보통 부모 클래스를 아래와 같은식으로 코딩해서, initView() 나 setTypeArray() 같은 함수에서 데이터 바인딩을 사용하여 초기화를 해주는 코드가 많은데 Koin을 사용하면, 데이터 바인딩을 사용하여 초기화를 해주는 시점을 뒤로 미뤄야한다.
abstract class BaseCustomView<B : ViewDataBinding>
: FrameLayout {
constructor(context: Context) : super(context) {
initView()
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
initView()
getAttrs(attrs)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
initView()
getAttrs(attrs, defStyleAttr)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
initView()
getAttrs(attrs, defStyleAttr, defStyleRes)
}
private fun getAttrs(attrs: AttributeSet?) {
setTypeArray(context.obtainStyledAttributes(attrs, getCustomViewStyle()))
}
private fun getAttrs(attrs: AttributeSet?, defStyle: Int, defStyleRes: Int = 0) {
setTypeArray(context.obtainStyledAttributes(attrs, getCustomViewStyle(), defStyle, defStyleRes))
}
private fun initView() {
binding = DataBindingUtil.bind(LayoutInflater.from(context)
.inflate(getLayoutId(), this@BaseCustomView, false).apply {
addView(this)
})!!
}
abstract fun setTypeArray(typedArray: TypedArray)
abstract fun getLayoutId(): Int
abstract fun getCustomViewStyle(): IntArray
protected lateinit var binding: B
}
우리는 생성자의 호출이 끝나고, 화면에 커스텀 뷰가 추가되는 시점인 onAttachedToWindow 에서 초기화를 진행해주면 된다.
아래와 같이 코딩하면 아주 잘 작동한다!
override fun onAttachedToWindow() {
super.onAttachedToWindow()
binding.run {
vm = customViewModel
}
}
'Android > Development Tips' 카테고리의 다른 글
Clean Architecture (무비 앱) (0) | 2020.04.26 |
---|---|
KOIN FragmentFactory 사용하기 (3) | 2020.02.29 |
YUV420_8888 to NV21 (0) | 2019.04.16 |
중첩 프래그먼트와 제어 및 헤드리스 프래그먼트 (0) | 2018.12.13 |
커스텀 뷰의 초기화 시점 (0) | 2018.12.12 |