오늘은 ViewModel을 사용하여 데이터를 유지하는 간단한 todoList 앱을 만들어 본다.
ViewModel 클래스는 수명 주기를 고려하여 UI 관련 데이터를 저장하고 관리하도록 설계되어있다.
ViewModel 클래스를 사용하면 화면 회전과 같이 구성을 변경할 때도 데이터를 유지할 수 있다.
일반적인 방법으로 데이터를 관리 시 다음과 같이
기기의 회전 처리를 하면 데이터가 날아가는걸 확인 할 수 있다.
화면 회전 시 activity가 destroy후 create가 되기 때문이다.
이를 해결하기 위해 구글에서 제공해주는 View model을 사용해본다.
viewmodels() 사용을 위해 android-ktx를 추가해준다.
//build.gradle - dependencies에 추가해준다.
implementation "androidx.fragment:fragment-ktx:1.3.2"
(1) ViewModel 객체 생성
class MyViewModel : ViewModel() {
private val users: MutableLiveData<List<User>> by lazy {
MutableLiveData().also {
loadUsers()
}
}
fun getUsers(): LiveData<List<User>> {
return users
}
private fun loadUsers() {
}
}
(2) Activity에서 사용되도록 설정하기
class MyActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
// 위 MyViewModel을 가져와 쓰겠다.
val model: MyViewModel by viewModels()
// 위 데이터를 observe(관찰)하겠다.
model.getUsers().observe(this, Observer<List<User>>{ users ->
// 업데이트 할 작업
})
}
}
크게는 위와 같이 설정하여 사용이 가능하다.
아래는 ViewModel을 사용한 TodoList 앱이다.
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
//ViewModel 객체 선언
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.rvItem.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
adapter = TodoAdapter(viewModel.data, onClickDelete = {
//viewModel의 deleteTodo를 실행
viewModel.deleteTodo(it)
binding.rvItem.adapter?.notifyDataSetChanged()
}, onClickItem = {
viewModel.toggleTodo(it)
binding.rvItem.adapter?.notifyDataSetChanged()
})
}
binding.addBtn.setOnClickListener {
val todo = Todo(binding.addEt.text.toString())
viewModel.addTodo(todo)
binding.rvItem.adapter?.notifyDataSetChanged()
}
}
}
data class Todo(
val text: String,
var isDone: Boolean = false
) //자동으로 게터세터가 생성된 dataclass ~!~!
class TodoAdapter(
private var myDataset: List<Todo>,
val onClickDelete: (todo: Todo) -> Unit,
val onClickItem: (todo: Todo) -> Unit
) :
RecyclerView.Adapter<TodoAdapter.TodoViewHolder>() {
class TodoViewHolder(val binding: ItemTodoBinding) : RecyclerView.ViewHolder(binding.root)
//.root 하면 본인이 어떤 뷰로 부터 생성된 바인딩인지 확인 가능
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoAdapter.TodoViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_todo, parent, false)
return TodoViewHolder(ItemTodoBinding.bind(view))
}
override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
val todo = myDataset[position]
holder.binding.tvList.text = todo.text
if (todo.isDone) {
holder.binding.tvList.apply {
paintFlags = paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
setTypeface(null, Typeface.ITALIC)
}
} else {
holder.binding.tvList.apply {
paintFlags = 0
setTypeface(null, Typeface.NORMAL)
}
}
holder.binding.ivDelete.setOnClickListener {
onClickDelete.invoke(todo)
}
holder.binding.root.setOnClickListener {
onClickItem.invoke(todo) //todo를 전달하여 onClickItem함수를 실행시킨다.
}
}
override fun getItemCount(): Int = myDataset.size
}
class MainViewModel : ViewModel() {
val data = arrayListOf<Todo>()
fun toggleTodo(todo: Todo) {
todo.isDone = !todo.isDone
}
fun addTodo(todo: Todo) {
data.add(todo)
}
fun deleteTodo(todo: Todo) {
data.remove(todo)
}
}
화면 회전시에도 UI의 데이터가 남아있는것을 확인 할 수 있다.