지난 게시글에 이어
ViewModel을 활용하여 뷰에서 데이터 관리 로직을 분리했다. 그렇다면 뷰에서 코드를 더 줄일 수 없을까?
우선 지난 번 코드를 되짚어보자
findViewById<Button>(R.id.btn_main_input).setOnClickListener {
val word = findViewById<EditText>(R.id.et_main_name).text.toString()
mainViewModel.setName(word)
Snackbar.make(
findViewById(R.id.main_layout),
"ViewModel에 ${word}가 저장되었습니다.",
Snackbar.LENGTH_SHORT
).show()
}
findViewById<Button>(R.id.btn_main_get).setOnClickListener {
findViewById<TextView>(R.id.txt_name).text = mainViewModel.getName()
}
우선 findViewById
가 눈에 거슬린다. Kotlin Android Extensions가 Deprecate를 예고한 시점에서 이에 대한 대체재가 없을까?
EditText에서 변수를 가져오고 ViewModel에 저장하는 과정, ViewModel에서 TextView로 변수를 전달하는 과정을 더 줄일 수 없을까?
이를 해결하고자 Google에서는 Data Binding과 LiveData를 제시했다.
데이터 바인딩은 뷰 코드를 거치지 않고 뷰모델(뿐만 아니라 다양한 클래스/변수)의 변수/함수를 뷰의 xml에 연결할 수 있는 기능이다.
데이터 바인딩을 어떻게 구현하고 사용하는지는 실습을 통해서 확인해보자.
android{
...
buildFeatures {
dataBinding true
}
}
이 부분을 앱 단위의 build.gradle 파일에 넣어준다. 그리고 데이터 바인딩을 할 때에는 상위 메뉴에서 Build를 누르고 Clean Project - Rebuild Project를 눌러줘야 데이터 바인딩이 잘 된다.
Main Activity의 루트 레이아웃에 Alt+Enter(맥은 Option+Enter)를 누르면 다음과 같이 Data Binding Layout으로 전환하겠냐고 뜬다. 이걸 누르면
다음과 같이 layout을 루트 태그로 하는 새로운 Layout이 만들어진다. <data>
태그 안에는 variable 데이터를 선언할 수 있게 만들어 뷰의 변수(여기서는 뷰 내의 변수가 ViewModel)를 xml 내에서 직접 사용할 수 있게 한다.
이제 데이터 바인딩 변수를 뷰 코드에 설정해서 Activity 코드를 통해 xml의 뷰들을 id를 통해 접근할 수 있게 할 것이다.
private val mainViewModel: MainViewModel by viewModels()
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.mainViewModel = mainViewModel
binding.lifecycleOwner = this
binding.btnMain.setOnClickListener {
mainViewModel.setName(binding.etMainName.text.toString())
}
}
binding
변수를 보면 데이터 타입이 특이하다는 걸 알 수 있다. 데이터 바인딩을 설정하면 안드로이드 프레임워크 자체에서 뷰 레이아웃에 따라 바인딩 클래스를 만들어 데이터 바인딩 변수의 데이터 타입을 결정한다.
예를 들어 뷰 xml 코드의 이름이 activity_main
이면 바인딩 클래스의 이름은 ActivityMainBinding
이 된다.
binding 변수를 선언했으면 초기화를 해줘야한다. 바인딩 클래스 변수의 초기화는 두 가지 방법으로 이뤄지는데
DataBindingUtil.setContentView(activity, layoutResId)
DataBindingUtil.inflate(inflater, layoutResId, container, attatchToParent)
위와 같은 방식으로 바인딩 변수를 초기화해주면 이제 binding 변수 설정은 끝난다.
binding은 레이아웃을 가리키는 변수라 생각하면 된다. 레이아웃 내의 변수에 mainViewModel
을 선언했으니 onCreate에서 binding.mainViewModel
을 초기화해준다.
lifeCycleOwner는 바인딩 변수의 생명주기를 Activity/Fragment의 생명주기에 종속시키기 위해서 설정한다. 이와 같이 설정할 시 바인딩 변수가 단독으로 메모리에 남는 것이 아니라 뷰의 생명주기에 따라 변수도 생성/해제되기에 메모리 누수의 가능성이 현저히 줄어들게 된다.
뷰 접근 및 클릭리스너 달기
바인딩 변수를 통해서 뷰를 접근하는 방식은 이전 android-extensions를 통해 접근하는 방식과 근접하다.
그러나 다른 점이 있다면 id의 값이 그대로 오는 것이 아니라 snake_case가 camelCase로 바뀐다. 이 점을 유의해서 뷰에 접근한다면 이전과 같이 속성을 설정할 수 있다.
나는 여기서 클릭 리스너를 달고 뷰모델의 값을 바꿀 것이다.
https://github.com/l2hyunwoo/SampleDataBinding 에서 feature/databinding
참고