포스팅 제목처럼 이번에는 DataBinding, LiveData, ViewModel, Repository를 모두 적용하며 간단한 프로그램을 구현해 보겠습니다.
이전 포스팅에서 공부했던 라이브러리는 간단히 살펴보고 넘어가겠습니다.
이전 포스팅
먼저 예제를 실행하기 위해 build.gradle(Module : app)을 세팅합니다.
plugins {
...
id 'kotlin-kapt'
}
android {
...
buildFeatures {
dataBinding true
}
}
// by viewModels()를 이용하기 위한 종속성
implementation 'androidx.activity:activity-ktx:1.4.0'
implementation 'androidx.fragment:fragment-ktx:1.4.1'
// ViewModel
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
// Livedata
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
Repository
interface Repository {
fun getCount() : LiveData<Int>
fun increaseCount()
fun decreaseCount()
}
Repository는 VIewModel이 DB에 접근할 때 요청하는 논리를 캡슐화하여 클래스로 구성하는 것입니다.
ViewModel과 DB 사이의 일종의 API 역할을 한다고도 볼 수 있습니다.
Repository 패턴을 사용함으로써 ViewModel와 DB의 결합도를 낮추고 로직을 분리할 수 있습니다.
인터페이스에서 선언된 함수의 역할은 RepositoryImpl에서 구체화합니다.
RepositoryImpl
class RepositoryImpl(count : Int) : Repository {
private val liveData = MutableLiveData(count)
override fun getCount(): LiveData<Int> {
return liveData
}
override fun increaseCount() {
liveData.value = liveData.value?.plus(1)
}
override fun decreaseCount() {
liveData.value = liveData.value?.minus(1)
}
}
현재 간단한 카운터 프로그램을 구현하고 있기 때문에 LiveData 객체를 선언해서 현재 liveData를 반환하거나 LiveData의 value를 1씩 증가/감소시키도록 작성했습니다.
MainViewModel
class MainViewModel(val repositoryImpl: RepositoryImpl) : ViewModel() {
val countFromRepository = repositoryImpl.getCount()
fun increaseCount() {
repositoryImpl.increaseCount()
}
fun decreaseCount() {
repositoryImpl.decreaseCount()
}
}
위의 RepositoryImpl에서 작성한 내용을 MainViewModel에서 동일하게 구현할 수 있지만 VIewModel과 DB를 분리하고Repository 패턴을 적용하기 위해 MainViewModel에서 RepositoryImpl 객체를 파라미터로 받으며 DB에 접근하고 있습니다.
MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val repositoryImpl = RepositoryImpl(0)
private val factory = MainViewModelFactory(repositoryImpl)
private val viewModel: MainViewModel by viewModels() { factory }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.lifecycleOwner = this // 생명주기가 액티비티를 따르도록
binding.viewmodel = viewModel // xml의 viewModel 연결
binding.activity = this@MainActivity // xml의 activity 연결
}
// 증가 버튼 클릭
fun plus() {
viewModel.increaseCount()
}
// 감소 버튼 클릭
fun minus() {
viewModel.decreaseCount()
}
}
MainViewModel 클래스의 경우 생성자가 있기 때문에 ViewModelFactory를 사용해 초기화 해줍니다.
버튼의 이벤트도 setOnClickListener로 처리하지 않고 xml에서 처리해 주겠습니다.
MainActivity의 코드가 간결해지고 가독성도 좋아진 것 같습니다.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewmodel"
type="com.example.practiceaac.MainViewModel" />
<variable
name="activity"
type="com.example.practiceaac.MainActivity" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/number_output"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewmodel.countFromRepository.toString()}"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textSize="30sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/minus_button"
android:onClick="@{() -> activity.minus()}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="100dp"
android:layout_marginTop="32dp"
android:text="감소"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/number_output" />
<Button
android:id="@+id/plus_button"
android:onClick="@{() -> activity.plus()}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginEnd="100dp"
android:text="증가"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/number_output" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="30dp"
android:max="10"
app:layout_constraintBottom_toTopOf="@+id/number_output"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintWidth_percent="0.3"
app:progressScaled="@{viewmodel.repositoryImpl.count}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
xml에서 클릭 리스너를 설정하는 방법은 onClick 속성에서 "@{() -> 변수명.함수명}"로 설정합니다.
실행 결과
MainActivity에 모든 코드를 작성하면 매우 단순한 프로그램이지만 위 패턴을 적용을 적용하다 보면 클래스와 .kt 파일이 많아져 다소 복잡해 보이기도 합니다.
하지만 구글에서 권장하는 AAC 라이브러리와 MVVM 패턴 적용에 익숙해지기 위해 노력해야겠습니다.
'Jetpack Library' 카테고리의 다른 글
[Android JetPack] 안드로이드 JetPack 알아보기 (2) | 2023.01.25 |
---|---|
[Android JetPack] Room을 이용해 로컬 데이터베이스 사용하기 - 회원가입 예제 (6) | 2023.01.11 |
[Android JetPack] DataBinding 알아보기 (0) | 2023.01.05 |
[Android JetPack] LiveData 기본 (0) | 2023.01.04 |
[Android JetPack] ViewModel 이란? ( + ViewModelFactory) (0) | 2023.01.03 |