구현 과정
1. 프로젝트 구성
2. Room 영속성 라이브러리 관련 클래스 구현
3. Repository 구현
4. ViewModel 구현
5. RecyclerViewAdapter 구현
6. MainActivity 구현
7. AddActivity 구현
MainActivity.kt : UI Controller(View)
먼저 xml 파일을 살펴보자.
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="activity"
type="com.example.sampleapp.MainActivity" />
<variable
name="viewModel"
type="com.example.sampleapp.MainViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Hello MVVM!"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toTopOf="@+id/main_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textview"
tools:listitem="@layout/item_todo" />
<Button
android:id="@+id/main_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:text="Add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
위를 보면 data의 변수로 MainActivity, ViewModel이 존재한다. 이는 관찰을 위한 변수이다. 그 다음 MainActivity를 살펴보자.
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
val ViewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//setContentView(R.layout.activity_main)
binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
binding.lifecycleOwner = this
binding.viewModel = ViewModel
setRecyclerView()
binding.mainButton.setOnClickListener {
val intent = Intent(this, AddActivity::class.java)
startActivity(intent)
}
}
private fun deleteDialog(todo: Todo) {
val builder = AlertDialog.Builder(this)
builder.setMessage("Delete selected contact?")
.setNegativeButton("취소") { _, _ -> }
.setPositiveButton("편집") { _, _ ->
val intent = Intent(this, AddActivity::class.java)
intent.putExtra(AddActivity.EXTRA_TODO_TITLE, todo.title)
intent.putExtra(AddActivity.EXTRA_TODO_DESC, todo.description)
intent.putExtra(AddActivity.EXTRA_TODO_ID, todo.id)
startActivity(intent)
}.setNeutralButton("삭제"){_, _ ->
lifecycleScope.launch(Dispatchers.IO){ViewModel.delete(todo)}
}
builder.show()
}
private fun setRecyclerView(){
// Set contactItemClick & contactItemLongClick lambda
val adapter = TodoAdapter ({ todo -> deleteDialog(todo)},{ todo -> deleteDialog(todo)})
binding.recyclerView.adapter=adapter
binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.setHasFixedSize(true)
ViewModel.getAll().observe(this, Observer { todos ->
adapter.setTodos(todos!!)
})
}
}
아이템의 변화를 관찰하기 위해 바인딩 생성 및 뷰모델을 생성하였다. 그리고 setRecyclerView를 통해 어탑터를 생성하여 각종 세팅을 해주는데 중요한 점은 ViewModel.getAll().observe... 부분이다. 이 부분을 통해 실질적인 관찰이 이루어지고 변화를 감지한다. 변화가 감지되면 변경된 todos 리스트를 어답터에 전달하여 초기화한다. (참고로 binding.lifecyclerOwner=this 부분은 굳이 삽입하지 않아도 된다.) 나머지 코드들은 AddActivity 이동, 아이템 클릭시 나타나는 다이얼로그 등과 관련되어 있다.
마지막으로 AddActivity.kt의 코드이다.
class AddActivity : AppCompatActivity() {
private val todoViewModel: MainViewModel by viewModels()
private var id: Int? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add)
if(intent!=null && intent.hasExtra(EXTRA_TODO_TITLE) && intent.hasExtra(EXTRA_TODO_DESC) && intent.hasExtra(
EXTRA_TODO_ID)){
add_edittext_title.setText(intent.getStringExtra(EXTRA_TODO_TITLE))
add_edittext_descript.setText(intent.getStringExtra(EXTRA_TODO_DESC))
id=intent.getIntExtra(EXTRA_TODO_ID, -1)
}
add_button.setOnClickListener {
if(add_edittext_title.text.isNotEmpty() && add_edittext_descript.text.isNotEmpty()){
val todo = Todo(id, add_edittext_title.text.toString(), add_edittext_descript.text.toString())
lifecycleScope.launch(Dispatchers.IO){todoViewModel.insert(todo)}
finish()
}else{
Toast.makeText(this,"Please enter title and desc", Toast.LENGTH_LONG).show()
}
}
}
companion object{
const val EXTRA_TODO_TITLE = "EXTRA_TODO_TITLE"
const val EXTRA_TODO_DESC = "EXTRA_TODO_DESC"
const val EXTRA_TODO_ID = "EXTRA_TODO_ID"
}
}
크게 설명할 부분은 없다. 다만 중요한 부분은 데이터를 편집하기 위해 코루틴을 이용하여 비동기적으로 처리하고있다.
activity_add.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".AddActivity">
<TextView
android:text="일정"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/add_tv_title"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.41000003"/>
<EditText
android:layout_width="0dp"
android:layout_height="wrap_content"
android:inputType="textPersonName"
android:ems="10"
android:id="@+id/add_edittext_title"
app:layout_constraintBottom_toBottomOf="@+id/add_tv_title"
app:layout_constraintTop_toTopOf="@+id/add_tv_title"
app:layout_constraintStart_toEndOf="@+id/add_tv_title"
android:layout_marginStart="32dp"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="32dp"/>
<TextView
android:text="설명"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/add_tv_descript"
android:layout_marginTop="40dp"
app:layout_constraintTop_toBottomOf="@+id/add_tv_title"
app:layout_constraintStart_toStartOf="@+id/add_tv_title"/>
<EditText
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ems="10"
android:id="@+id/add_edittext_descript"
app:layout_constraintStart_toStartOf="@+id/add_edittext_title"
app:layout_constraintTop_toTopOf="@+id/add_tv_descript"
app:layout_constraintBottom_toBottomOf="@+id/add_tv_descript"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="32dp"/>
<Button
android:text="done"
android:layout_width="0dp"
android:layout_height="49dp"
android:id="@+id/add_button"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
참고로 AddActivity 부분은 편집을 위한 액티비티이기 때문에 특별한 데이터 바인딩이 불가능하다. (내가 모르는걸수도..)
리뷰
실제 구현을 하긴 했지만 다시 구현하라고 하면 복잡해서 시간이 좀 걸릴 것 같다. 그리고 몇몇 부분에 대한 추가적인 이해도 필요해보인다. Repository의 필요성, 기본적인 코틀린 문법 등을 학습할 필요를 느꼈다. 또한, 다른 코드들도 참조하여 내가 구현한 MVVM 패턴이 적절한지도 판단할 필요가 있다.
Github Repo : https://github.com/Gongcu/MVVMExample
참고 : https://blog.yena.io/studynote/2019/03/27/Android-MVVM-AAC-2.html
'안드로이드' 카테고리의 다른 글
[안드로이드] 예제:MVVM+AAC를 이용한 RecyclerView 6 - ListAdapter (1) | 2020.08.16 |
---|---|
[안드로이드] 예제:MVVM+AAC를 이용한 RecyclerView 5 - BindingAdpater (0) | 2020.08.15 |
[안드로이드] 예제:MVVM+AAC를 이용한 RecyclerView 3 (0) | 2020.08.12 |
[안드로이드] 예제:MVVM+AAC를 이용한 RecyclerView 2 (0) | 2020.08.12 |
[안드로이드] 예제:MVVM+AAC를 이용한 RecyclerView 1 (1) | 2020.08.12 |