MVVM 패턴으로 Room, LiveData 사용하기 마지막 글입니다.
[Android/이론] - MVVM 시작하기(1) ― LiveData, Room을 MVVM패턴으로 사용해보자
[Android/이론] - MVVM 시작하기(2) ― Room Entity, Dao, Database 만들기
[Android/이론] - MVVM 시작하기(3) ― ViewModel, 데이터 바인딩(Data Binding)
Room Database(Model), View, ViewModel을 만들어봤습니다.
이제 ViewModel에서 Model에 접근하기 위해 Repository를 만들어야 합니다.
Repository
Repository는 쿼리를 관리하고 여러 백엔드를 사용할 수 있도록 합니다.
- Repository.kt
class Repository(mDatabase: AppDatabase) {
private val userDao = mDatabase.userDao()
val allUsers: LiveData<List<UserEntity>> = userDao.getAlphabetizedUsers()
companion object{
private var sInstance: Repository? = null
fun getInstance(database: AppDatabase): Repository {
return sInstance
?: synchronized(this){
val instance = Repository(database)
sInstance = instance
instance
}
}
}
suspend fun insert(userEntity: UserEntity) {
userDao.insert(userEntity)
}
}
User list는 공공 데이터 이기 때문에 변수 allUsers는 LiveData로 초기화 됩니다. 그리고 LiveData가 관찰하고 변화가 감지되면 메인 스레드에 알려줍니다.
이제, 감지된 변화에 맞게 UI를 업데이트 할 수 있도록, Data와 View를 연결해 줍니다.ViewModel에도 Repository에서 가져올 UserEntity 변수를 만들어 줍니다.
val repository: Repository = Repository(AppDatabase.getDatabase(application,viewModelScope))
var allUsers: LiveData<List<UserEntity>> = repository.allUsers
그리고, ViewModel에서 repository에 접근해 데이터를 추가할 수 있는 메서드도 추가해줍니다.
fun insert(userEntity: UserEntity) = viewModelScope.launch(Dispatchers.IO) {
repository.insert(userEntity)
}
RecyclerView Adapter
User List를 띄울 Recycler View의 adpater를 만들어 줍니다.
class UserListAdapter internal constructor(context: Context)
: RecyclerView.Adapter<UserListAdapter.UserViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var users = emptyList<UserEntity>() // Cached copy of words
inner class UserViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val userName: TextView = itemView.findViewById(R.id.user_name)
val userGender: TextView = itemView.findViewById(R.id.gender)
val userBirth: TextView = itemView.findViewById(R.id.birth)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val itemView = inflater.inflate(R.layout.recycler_item, parent, false)
return UserViewHolder(itemView)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
val current = users[position]
holder.userName.text = current.name
holder.userGender.text = current.gender
holder.userBirth.text = current.birth
}
internal fun setUsers(users: List<UserEntity>) {
this.users = users
notifyDataSetChanged()
}
override fun getItemCount() = users.size
}
View 연동
MainActivity에 adpater를 연결해주고, viewModel의 LiveData를 관찰해 UI를 업데이트 하도록 합니다.
- MainActivity.kt
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
binding.viewModel = viewModel
val mAdapter = UserListAdapter(this)
recyclerview.apply {
adapter = mAdapter
layoutManager = LinearLayoutManager(applicationContext)
}
viewModel.allUsers.observe(this, Observer { users ->
// Update the cached copy of the users in the adapter.
users?.let { mAdapter.setUsers(it) }
})
}
}
.ovserve의owner(this부분)는 프래그먼트에서는 viewLifecycleOwner를 사용하여야 합니다.
이제, 버튼 클릭 시 아이템을 추가할 수 있도록 하겠습니다. 버튼 클릭 시 dialog를 띄워 입력된 값을 저장되도록 하였습니다.
주의할것은, ViewModel에서 dialog나 새로운 activity를 띄우는 것은 view를 참조하는 것이기 때문에 메모리 누수가 일어납니다. 이러한 활동은 activity나 fragment에서 일어나야 합니다.
그러므로 버튼 onClick과 viewModel을 바인딩해놓았지만 사용하지 않기때문에 지워주겠습니다.
android:onClick="@{()->viewModel.onClickButton()}"
activity_main.xml의 Button 의 윗줄 지움.
activity에서 버튼의 setOnClickListener를 사용하면 됩니다.
- user_dialog.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#fff">
<EditText
android:layout_margin="10dp"
android:id="@+id/user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:hint="이름.."
android:inputType="textAutoComplete"
android:textSize="18sp" />
<EditText
android:layout_margin="10dp"
android:id="@+id/user_gender"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:hint="성별..(남/여)"
android:inputType="textAutoComplete"
android:textSize="18sp" />
<EditText
android:layout_margin="10dp"
android:id="@+id/user_birth"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:hint="생년월일(1993-07-25)"
android:inputType="textAutoComplete"
android:textSize="18sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:layout_margin="10dp"
android:id="@+id/button_save"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:text="SAVE"
android:textColor="#fff" />
<Button
android:layout_margin="10dp"
android:id="@+id/button_cancel"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:text="CANCEL"
android:textColor="#fff" />
</LinearLayout>
</LinearLayout>
- UserDialog.kt
class UserDialog(mContext: Context) : Dialog(mContext) {
private val viewModel:MainViewModel = MainViewModel(mContext.applicationContext as Application)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.user_dialog)
// 다이얼로그의 배경을 투명으로 만든다.
Objects.requireNonNull(window)?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
// 버튼 리스너 설정
button_save.setOnClickListener {
// '확인' 버튼 클릭시 data insert
viewModel.insert(UserEntity(user_name.text.toString(),
user_gender.text.toString(),user_birth.text.toString()))
// Custom Dialog 종료
dismiss()
}
button_cancel.setOnClickListener {
// '취소' 버튼 클릭시
// Custom Dialog 종료
dismiss()
}
}
}
이제 MainActivity에 onClickListener를 추가해줍니다.
btnAdd.setOnClickListener {
val dlg = UserDialog(this)
dlg.show()
}
최종 코드는 다음과 같습니다.
- MainActivity.kt
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
binding.viewModel = viewModel
val mAdapter = UserListAdapter(this)
recyclerview.apply {
adapter = mAdapter
layoutManager = LinearLayoutManager(applicationContext)
}
viewModel.allUsers.observe(this, Observer { users ->
// Update the cached copy of the users in the adapter.
users?.let { mAdapter.setUsers(it) }
})
btnAdd.setOnClickListener {
val dlg = UserDialog(this)
dlg.show()
}
}
}
- MainViewModel.kt
class MainViewModel(application: Application) : AndroidViewModel(application) {
var main_text: ObservableField<String> = ObservableField("Main")
val repository: Repository = Repository(AppDatabase.getDatabase(application,viewModelScope))
var allUsers: LiveData<List<UserEntity>> = repository.allUsers
fun insert(userEntity: UserEntity) = viewModelScope.launch(Dispatchers.IO) {
repository.insert(userEntity)
}
}
이의 연장선으로 Room DB의 검색기능을 구현하는 법도 포스팅 하였으니 참고해주세요.
[Android/Function] - [안드로이드] Room 데이터베이스의 검색기능 구현하기 ― Room Fts4
'Android > Architecutre' 카테고리의 다른 글
[안드로이드 Service] MVVM구조에서 BluetoothLE Service 사용하기 (3) | 2021.02.16 |
---|---|
[안드로이드 MVVM] Repository 에서 ViewModel, View(Activity,Fragment)에 Event/Data 전달하기 ― Repository에서의 LiveData ? (0) | 2021.01.25 |
MVVM 시작하기(3) ― ViewModel, 데이터 바인딩(Data Binding) (0) | 2020.07.30 |
MVVM 시작하기(2) ― Room Entity, Dao, Database 만들기 (3) | 2020.07.29 |