티스토리 뷰
DataBinding, Binding 객체 생성
나는 DatabindingUtil 클래스로 bindng 객체를 생성해서 사용하고 있었다.
하지만 다른 사람들이 작성한 databinding 코드들을 보면 여러 방법으로 binding 객체를 생성해주는 것을 보고 내가 제대로 쓰고 있는 것에 대한 의문이 들었다.
inflate()
, bind()
의 차이도 궁금하던 차였고 ..
이러한 의문들을 해결하기 위해 대표적인 Activity, Fragment 에서의 사용법을 분석해보았다.
Activity
ActivityMainBinding.inflate(inflater: LayoutInflator)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
생성된 Binding 객체의 static api 를 사용하여 레이아웃 inflation
- Activity 의 layoutInflater 전달하여 Binding 객체를 생성
- Binding 객체의 root view 를 파라미터로
setContentView(view: View)
호출하여 레이아웃 inflation
LayoutInflater : xml 을 View 객체로 변환시켜 준다. 컴파일 시점에 완성된 xml 파일에 대해서만 변환이 가능하다.
DataBindingUtil.setContentView(activity: Activity, int layoutId)
xxxxxxxxxx
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
}
DataBindingUtil 클래스의 static api 를 사용하여 레이아웃 inflation
- Activity, layout 의 리소스 아이디를 전달하여 Binding 객체를 생성 및 레이아웃 inflation
ActivityMainBinding.inflate()
와 다르게 setContentView(view: View)
를 호출하지 않는데 메소드의 내부 동작을 살펴보면 그 이유를 알 수 있다.
DataBindingUtil.java
public static <T extends ViewDataBinding> T setContentView( Activity activity,
int layoutId, DataBindingComponent bindingComponent) {
activity.setContentView(layoutId);
View decorView = activity.getWindow().getDecorView();
ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
}
API 내부적으로 파라미터로 전달받은 Activity 의 setContentView() 호출해주고 있다.
왜 이런 차이가 있는 걸까 ?
ActivityMainBinding 클래스와 DataBindingUtil 클래스의 사용 상 차이를 살펴보면, ActivityMainBinding 객체 내에는 resId(?)를 포함하고 있기 때문에 resId 전달 없이 Binding 객체를 생성할 수 있는 것으로 추측된다.
정말 그럴까 ? build 폴더 내 생성된 클래스 파일을 열어 보았다.
ActivityMainBinding.class
xxxxxxxxxx
public static ActivityMainBinding inflate( LayoutInflater inflater) {
return inflate(inflater, DataBindingUtil.getDefaultComponent());
}
/** @deprecated */
public static ActivityMainBinding inflate( LayoutInflater inflater, Object component) {
return (ActivityMainBinding)ViewDataBinding.inflateInternal(inflater,2131296284,(ViewGroup)null, false, component);
}
API 콜 스택 : inflate(inflater: LayoutInflator)
→ inflateLayoutInflater inflater, Object component)
→ static <T extends ViewDataBinding> T inflateInternal(LayoutInflater inflater, int layoutId, ViewGroup parent, boolean attachToParent, Object bindingComponent)
추측한대로 ViewDataBinding.inflateInternal()
호출 시에 전달하는 activity_main.xml 의 layoutId 값이 클래스 파일 내에 정의 되어 있었다.
Fragment, ListView or RecyclerView
Activity 처럼 setContentView()
호출이 필요하지 않고 binding 객체를 생성하여 모든 작업이 가능하다.
FragmentMainBinding.inflate(inflater: LayoutInflator, parent: ViewGroup, attachToParent: Boolean)
xxxxxxxxxx
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentMainBinding.inflate(inflater, container, false)
return binding.root
}
생성된 Binding 객체의 static api 를 사용하여 View 를 리턴
DataBindingUtil.inflate(inflater: LayoutInflator, int: layoutId, parent: ViewGroup, attachToParent: Boolean)
xxxxxxxxxx
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false)
return binding.root
}
DataBindingUtil 클래스의 static api 를 사용하여 View 를 리턴
DataBindingUtil 은 resId 를 모르기 때문에, FragmentMainBinding.inflate()
와 다르게 추가로 layoutResId 를 전달하는 것 외에는 별다른 특이점은 보이지 않는다.
FragmentMainBinding.bind(view: View)&DataBindingUtil.bind(view: View)
또 다른 Binding 객체를 생성하기 위한 방법으로 bind()
를 사용하는 방법이 있다. inflate()
와는 다르게 이미 inflate 된 view 를 전달 받는 방식이기 때문에 view 만을 파라미터로 받는다.
xxxxxxxxxx
private lateinit var binding : FragmentMainBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// LayoutInflater 를 통해 미리 View 를 생성
val view = inflater.inflate(R.layout.fragment_main, container, false)
binding = DataBindingUtil.bind(view)!! or
binding = FragmentMainBinding.bind(view)
return binding.root
}
이 처럼 Fragment 에서 사용 가능하고 한 가지 특이점은 DataBindingUtil.bind(view: View)
는 nullable 객체를 리턴한다는 것이다. 때문에 !!
를 추가하여 lateinit 을 적용하였는데,
이것 역시 내부 동작을 보면 이해 가능하다.
FragmentMainBinding.class
xxxxxxxxxx
/** @deprecated */
public static FragmentMainBinding bind( View view, Object component) {
return (FragmentMainBinding)bind(component, view, 2131427358);
}
빌드된 클래스 파일을 보면 view 를 전달 받았지만 내부적으로 resId 를 전달하며 기능을 수행하는 모습이다.
DataBindingUtil.class
xxxxxxxxxx
public static <T extends ViewDataBinding> T bind( View root) {
return bind(root, sDefaultComponent);
}
내부 동작을 보면 전달받은 view 의 태그를 통해 DataBinderMapper
에 저장된 binding 객체를 전달하는 동작을 수행한다. 때문에 찾지 못한 경우를 위한 예외 처리로 nullable 을 리턴해주고 있는것 같다.
결론
binding 객체를 생성하기 위한 여러 방법들이 제공되고 있으니 경우에 알맞게 적용하면 된다 !!
...
나는 아래처럼 BaseActivity, BaseFragment 내에 정의해서 사용하고 있다.
(피드백 주시는 고수님 감사합니다.)
xxxxxxxxxx
// BaseActivity
abstract class BaseActivity : AppCompatActivity() {
protected inline fun <reified T : ViewDataBinding> bindingView(
@LayoutRes resID: Int
): Lazy<T> =
lazy {
DataBindingUtil.setContentView<T>(this, resID)
.apply { this.lifecycleOwner = this@BaseActivity }
}
}
// BaseFragment
abstract class BaseFragment : Fragment() {
protected inline fun <reified T : ViewDataBinding> binding(
lifecycleOwner: LifecycleOwner,
@LayoutRes resID: Int
): Lazy<T> =
lazy {
DataBindingUtil.setContentView<T>(this, resID)
.apply { this.lifecycleOwner = lifecycleOwner }
}
}
References
https://developer.android.com/topic/libraries/data-binding/generated-binding#create
'프로그래밍 > Android' 카테고리의 다른 글
[Android] getLocationOnScreen vs getLocationInWindow() (1) | 2020.01.19 |
---|---|
[Android] Architecture Components 사용 시의 5가지 일반적인 실수 (0) | 2019.10.20 |
[Android] Message.obtain() vs Handler.obtainMessage() (0) | 2019.10.03 |
[Android] Handler ( sendMessage, post ) / runOnUiThread (1) | 2019.10.03 |
[Android] 인터페이스 상수 (0) | 2019.08.25 |