본문 바로가기

Android/Architecutre

MVVM 시작하기(4) ― Repository, Model-View 연동

728x90
반응형

 

 

 

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

 

 

728x90
반응형