DataBinding and ViewBinding
DataBinding
- 안드로이드에서의 data binding 이란, Android Archictecture Components 의 한 부분으로서 Ui 요소와 데이터를 프로그램적 방식으로 연결하지 않고, 선언적 형식으로 결합할 수 있게 도와주는 라이브러리를 말한다. 즉, xml에 Data를 연결하는 작업을 말합니다.
- 주로 MVVM패턴, LiveData와 함께 사용합니다. 단, 필수는 아닙니다.
- Android JetPack 라이브러리 중 하나입니다.
(DataBinding) 프로그램적 방식 -> 선언적 방식
프로그램적 방식
TextView textView = findViewById(R.id.sample_text);
textView.setText("올라프");
Binding 사용시 layout 파일에서 밑에것처럼 선언 가능. 이를 선언적(declarative) 레이아웃 작성이라고 한다.
<TextView
android:text="@{viewmodel.userName}" />
Data binding 장점
데이터 바인딩을 사용하면, 데이터를 UI 요소에 연결하기 위해 필요한 코드를 최소화할 수 있다.
- 액티비티에서 UI 호출을 제거하여 코드를 더 간단하고 쉽게 유지 및 관리할 수 있다.
- 앱의 성능을 개선하고 메모리 누수 및 널 포인터 예외를 방지하는 데에 도움이 될 수 있다.
- 예를 들어 @{user.name} 표현식에서 user가 null이면 user.name에 null이 기본값으로 할당. age의 유형이 int인 user.age를 참조하면 데이터 결합은 0의 기본값을 사용.
- 기본 데이터 소스가 변경될 때, UI 새로고침에 대해 걱정할 필요가 없다.
- xml 리소스만 보고도 View 에 어떤 데이터가 들어가는지 파악이 가능하다.
- 코드 가독성이 좋아지고, 상대적으로 코드량이 줄어든다.
Data binding 단점
- 클래스 파일이 많이 생기고, 빌드 속도가 느려짐
- 디버깅이 어렵다. xml은 기본적으로 디버깅이 안되기 때문에, 데이터가 제대로 넘어가지 않는 경우 이유를 확인하기 어렵다.
- Data binding 단독으로만 사용하는 것보다 MVVM 또는 MVP 아키텍처과 함께 사용하면 효율적
ViewBinding
과거 Activity에서 뷰들의 값을 변경하려면 findViewByID를 이용해 xml과 뷰를 연결시켜주는 작업을 해야했음. 이걸 대체하는 개념이 view binding 임.
뷰와 상호작용하는 코드를 쉽게 작성하기 위해 사용한다.
뷰 바인딩이 활성화되면 해당 모듈에있는 각 XML 레이아웃 파일에 대한 바인딩 클래스가 자동으로 생성된다.
바인딩 클래스 인스턴스에는 해당 레이아웃에 ID가 있는 모든 뷰에 대해 직접적으로 참조된다
- build.gradle에서 enable 시킵니다. (추가 libarary가 필요하지 않습니다.)
- 모든 layout에 대해서 binding object가 생성됩니다. Binding object는 id를 갖는 모든 view들을 하나의 property로 가집니다.
- Java, kotlin 모두를 지원합니다.
- null safety - 뷰 직접 참조로 없는 아이디로 널포인트 익셉션 발생 안함.
- 널 안전 (Null Safety): 바인딩 클래스에서 제공하는 필드는 각 뷰를 직접 참조하게 구성되어 있습니다. 따라서, 잘못된 ID를 대입하여 널 포인터 오류가 발생하는 등의 문제가 일어나지 않습니다. 또한, 특정 구성 (configuration)에서만 접근할 수 있는 뷰가 있는 경우 이는 @Nullable로 표시되므로 뷰 참조시 실수를 방지할 수 있습니다.
- type safety - 뷰 타입이 일치함으로 Class Cast Exception 발생 안하지.
- 타입 안정성 (Type safecy): 바인딩 클래스 내 필드는 레이아웃 내 선언된 뷰의 타입을 갖습니다. 따라서 잘못된 타입으로 캐스팅 (예: ImageView를 TextView로 캐스팅)하는 실수를 원천 봉쇄할 수 있습니다.
출처: https://tourspace.tistory.com/314 [투덜이의 리얼 블로그:티스토리]
바인딩 클래스 이름은 규칙
Activity 이름 | Binding Class 이름 |
MainActivity | ActivityMainBinding |
HelloActivity | ActivityHelloBinding |
XXXActivity | ActivityXXXBinding |
참고 : https://todaycode.tistory.com/29
ViewBinding vs DataBinding (차이)
- 데이터 바인딩 라이브러리는 <layout> 태그를 사용하여 만든 레이아웃만 처리한다
- 내부적으로 데이터 바인딩 클래스를 생성할 때는 루트 뷰에 tag를 삽입하는데 뷰바인딩은 삽입하지 않음
- 뷰바인딩은 데이터바인딩보다 더 빠르게 바인딩 클래스를 생성: 어노테이션 프로세싱의 일부를 사용하기 때문
- layout variable, layout expression 지원: 데이터 바인딩은 레이아웃 변수, 표현식 등을 통해 XML 내에서 다이내믹 UI 콘텐츠를 표현할 수 있습니다.
- 뷰바인딩은 양방향 데이터 결합을 지원하지 않는다(to way binding)
데이터 바인딩은 뷰 바인딩의 역할 뿐만 아니라, 동적 UI 콘텐츠 선언, 양방향 데이터 바인딩 기능도 지원한다.
그럼 뷰 바인딩을 안쓰고 데이터 바인딩만 쓰면 되는게 아닌가 싶지만, 안드로이드 공식문서에서는 findViewByID()만을 목적으로 할 때는 뷰 바인딩을 쓰는걸 권장한다.
코드
데이터 바인딩 추가 예정 :
뷰바인딩 :
1. build.gradle
android {
...
buildFeatures {
viewBinding = true
}
}
2. Activity 예제 : ViewBinding + BottomNavigation
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initBottomNavigation()
}
//Bottom Navigation 하는 부분
private fun initBottomNavigation() {
supportFragmentManager.beginTransaction()
.replace(R.id.main_frm, StudyListFragment())
.commitAllowingStateLoss()
binding.bottomNavigationView.setOnItemSelectedListener { item ->
when (item.itemId) {
R.id.first -> {
supportFragmentManager.beginTransaction()
.replace(R.id.main_frm, StudyListFragment())
.commitAllowingStateLoss()
return@setOnItemSelectedListener true
}
else -> {
supportFragmentManager.beginTransaction()
.replace(R.id.main_frm, CodeExampleListFragment())
.commitAllowingStateLoss()
return@setOnItemSelectedListener true
}
}
}
}
}
3. Fragment 예제
class StudyListFragment : Fragment() {
private var binding: FragmentStudyListBinding? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentStudyListBinding.inflate(inflater, container, false)
val mRecyclerView = binding!!.recyclerView
val mAdapter = StudyListAdapter()
mRecyclerView.adapter = mAdapter
mRecyclerView.addItemDecoration(DividerItemDecoration(activity, LinearLayoutManager.VERTICAL))
val lm = LinearLayoutManager(activity)
mRecyclerView.layoutManager = lm
mRecyclerView.setHasFixedSize(true)
val view = binding!!.root
return view
}
override fun onDestroyView() {
binding = null
super.onDestroyView()
}
}
4. RecyclerView Adapter인 경우
class StudyListAdapter() : RecyclerView.Adapter<StudyListAdapter.ViewHolder>() {
val dataSet = AndroidStudyList.androidStudyList
class ViewHolder(view: View, dataSet: List<AndroidStudyItem>) : RecyclerView.ViewHolder(view) {
val itemBinding: FragmentStudyListItemBinding;
init {
itemBinding = FragmentStudyListItemBinding.bind(itemView);
//item 클릭시 웹뷰로 넘어감.
view.setOnClickListener {
val pos = absoluteAdapterPosition
val nextIntent = Intent(view.context, WebViewActivity::class.java)
nextIntent.putExtra("url", dataSet[pos].url);
view.context.startActivity(nextIntent)
}
}
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.fragment_study_list_item, viewGroup, false)
return ViewHolder(view, dataSet)
}
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
viewHolder.itemBinding.studyItemTitle.text = dataSet[position].title
}
override fun getItemCount() = dataSet.size
}