-
MVVM 따라하기 Data Binding, LiveData (1)Android(+ Kotlin) 2019. 12. 20. 18:36
github 검색 앱을 만들고 관련된 주요내용을 설명한다.
https://github.com/CharkoT/CoroutineTester
Android 개발자 Jetpack 사이트 참조하여 코드를 만들었다.
https://developer.android.com/jetpack
주요내용으로 MVVM 패턴을 위해 사용된 Data binding, LiveData를 설명한다. 그리고 코루틴 살짝.
먼저 문서에서는 MVVM 구조를 설명하지 않고, ViewModel에 대해서 설명한다. ViewModel에서 데이터를 관리하여 화면구성에 용이함을 가져다 주는 역할을 한다.
MVVM은 Model(이하 m), View(이하 v), ViewModel(이하 vm) 각각의 의존성을 낮춰 비즈니스로직의 수정과정에서 오류를 줄여주는데 역할을 한다.
그리고 TDD(Test Driven Development)에 용이하다. (빨리 해보아야 하는데...)
v와 vm의 관계를 느슨하게 하기 위해 Data Binding, LiveData를 사용하게 되었다.
먼저 v와 vm관계를 살펴보자 (예제코드는 activity에서 fragment를 호출하여 fragment가 v가 된다.)
v와 vm 관계에서는 observer를 통해 데이터를 지속적으로 구독한다.
v의 xml내부에서는 Data Binding을 사용하게 된다.
(v와 vm사이에는 Data Binding을 통해 빌드된 클래스로 관계를 잇는다.)
먼저 Data Binding에 대해 알아보자
바인딩 연결
데이터 바인딩을 하려면 모듈 gradle에서 android내부 아래 문구를 추가한다.
dataBinding { enabled true }
이미 제작된 xml에 있거나 생성 한 후 Layout에서 option + enter(Mac 기준)을 누를 경우 아래와 같이 나타난다.
convert to data binding layout을 선택하면 아래와 같이 자연스럽게 만들어진다. (복붙하는 불편함을 없애세요!!)
해당하는 데이터를 넣어주면 된다.
<data> <variable name="viewmodel" type="kr.co.coroutinetester.ui.main.viewmodel.MainViewModel" /> </data>
Main의 데이터를 모두 사용하기 위해 간단하게 해당 vm 호출했으며, 지정된 변수 "viewmodel"을 통해 내부 변수값을 가지고 올 수 있다.
android:text="@{viewmodel.title}"
v와 vm의 바인딩 연결작업이 필요하다!
위 와 같이 정상적으로 작업이 되었다면
자동으로 implement생성이 된다. 생성된 파일명은 xml 파일명 + BindingImpl.xml (xml 파일이름이 main_fragment)
val binding: MainFragmentBinding = DataBindingUtil.inflate(inflater, R.layout.main_fragment, container, false) binding.viewModel = viewModel
위와 같이 하면 바인딩의 연결작업은 끝났다.
[추가]
binding에 viewModel 인스턴스를 전달해야한다.
[추가]
위 .inflate 방법은 Fragment에서 binding작업이다. activity에서는 .setContentView를 사용하면 된다.
식별 가능한 변수 연결
이제 v에서 vm의 변수값을 옵저버할 수 있게 vm의 변수를 Observable클래스를 사용하여 식별할 수 있어야 한다.
문서상에서는 ObservableField<> 혹은 ObservableBoolean, Int 등등으로 나와 있으니 참고하자
예제 코드는 LiveData를 사용하기 위해 MutableLiveData를 사용하였다.
LiveData의 코드를 타고 들어가면 observe() 매서드를 통해 Observable역할을 한다.
데이터 바인딩에는 단방향(vm → v, v → vm), 양방향이 있다.
위 내용은 vm에서 v로 가는 단방향이며,
edittext, checkbox등은 입력 및 체크 유무를 확인하기 위해 v에서 vm과 양방향으로 공유한다.
그리고 onClickListener와 같은 클릭 이벤트는 v → vm가는 방향성을 띈다.
이제는 v → vm으로 가는 단방향을 알아보자. (이벤트 처리)
<Button android:id="@+id/search_btn" android:layout_width="40dp" android:layout_height="40dp" android:layout_marginRight="20dp" android:onClick="@{() -> viewmodel.onSearchClick()}" app:layout_constraintBottom_toBottomOf="@+id/search_et" app:layout_constraintRight_toRightOf="@+id/search_et" app:layout_constraintTop_toTopOf="@+id/search_et" />
android:onClick="@{() -> viewmodel.onSearchClick()}" 이 부분을 통해 v의 버튼액션이 vm에서 동작할 수 있다.
양방향에 대해서 알아보자
<EditText android:id="@+id/search_et" android:layout_width="0dp" android:layout_height="60dp" android:layout_margin="20dp" android:background="@drawable/search_bg" android:hint="검색어를 입력해 주세요." android:paddingLeft="15dp" android:paddingRight="75dp" android:singleLine="true" android:text="@={viewmodel.etTextSearch}" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/title_tv" />
android:text="@={viewmodel.etTextSearch}"을 통해 v에서 변경된 값이 vm에 적용이 된다.
Binding Adapter
공식문서에서는 setText()를 하기 위해 사용된다라고 했지만, 비교적 간단한것은 android:text="@{}"를 통해 되지만, RecyclerView의 setAdapter와 glide를 사용하여 이미지 변경을 시도하려할때 어렵다.
이 문제를 해결 할 수 있는 부분이 binding adapter이다.
예제코드에서는 별도의 코드로 분리하였지만 vm 내부에서도 작성이 가능하다.
@JvmStatic @BindingAdapter("imageUrl") fun loadImage(imageView: ImageView, url: String) { Glide.with(imageView.context).load(url).into(imageView) } @JvmStatic @BindingAdapter("items") fun setBindItem(view: RecyclerView, items: MutableLiveData<ArrayList<MainModel>>) { view.adapter?.run { if (this is MainAdapter) { this.items = items.value!! this.notifyDataSetChanged() } } }
여기서 주목해야할것은 BindingAdapter() 내부이다.
왜냐하면 이 내부의 네이밍을 통해 v와 연결이 된다.
<androidx.recyclerview.widget.RecyclerView android:id="@+id/rvItems" items="@{viewmodel.userList}" android:layout_width="0dp" android:layout_height="0dp" android:layout_margin="20dp" android:background="@drawable/search_bg" android:padding="15dp" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/search_et" tools:listitem="@layout/item_git_user" />
위 설정에서 items="@{viewmodel.userList}"을 확인하자.
<ImageView android:id="@+id/profile_iv" android:layout_width="100dp" android:layout_height="100dp" android:background="#cccccc" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" imageUrl="@{mainItem.avatar_url}" />
위 설정에서 imageUrl="@{mainItem.avatar_url}"을 확인하자.
위에서 만든 BindingAdapter에서 네이밍한 내용이 xml에서 부여되었다.
vm → Binding adapter→ xml의 순서로 진행되는것으로 추측된다.
LiveData
장점으로
- UI와 데이터 상태의 일치 보장 (Data Binding에도 ObservableField)
- 메모리 누출 없음
- 중지된 활동으로 인한 비정상 종료 없음
- 수명 주기를 자동으로
- 항상 최신 데이터 유지 (
Data Binding에도 됨 ObservableField) - 적절한 구성 변경 (Data Binding에도 됨 ObservableField)
- 리소스 공유
괄호 내용처럼 기존 데이터 바인딩을 통해 사용되었기에 처음에 무의식적으로 LiveData를 사용하지 않았다.
현재도 사용을 했지만 아직 이해하지 못했다.
조금더 공부해서 LiveData만을 위한 추가 글 작성 예정.
'Android(+ Kotlin)' 카테고리의 다른 글
MVVM 따라하기 Data Binding + LiveData(2) (0) 2020.01.09 [수정] ViewModel의 ViewModelProvider (0) 2019.12.30 [Kotlin]무작정 따라하기3 (0) 2019.11.11 [Kotlin]무작정 따라하기2 (0) 2019.11.08 [Kotlin]무작정 따라하기 1 (0) 2019.10.16