본문 바로가기
Mobile Development/Android

[Android] Kotlin + MVVM + AAC 로 Todo 앱 만들기 - 5

by 두잇뚜 2020. 3. 8.
반응형

이번 포스팅은 RecyclerView에 이벤트 처리를 구현 할 것이다.

아이템을 클릭하면 Todo 아이템을 추가할 때 띄웠던 다이얼로그를 통해 Todo 아이템을 수정 할 것이다.

그리고 롱클릭을 통해 Todo 아이템을 삭제 해 보겠다.

- Interface 구현

class TodoListAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {

	...
    
    interface OnTodoItemClickListener {
        fun onTodoItemClick(position: Int)
        fun onTodoItemLongClick(position: Int)
    }

    var listener: OnTodoItemClickListener? = null

	...
}

우선 어댑터의 상단에 위와 같은 코드를 작성한다.

아이템 클릭 인터페이스를 생성하고 아이템 클릭과 아이템 롱 클릭에 대하여 구현하게 할 것이고 외부에서 리스너를 통해 구현하게 된다.

- TodoListAdapter, ViewHolder 수정

class TodoListAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
	...

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_todo, parent, false)
        val viewHolder = TodoViewHolder(view, listener)

        return viewHolder
    }

    class TodoViewHolder(view: View, listener: OnTodoItemClickListener?): RecyclerView.ViewHolder(view) {
		...
    
        init {
            view.setOnClickListener {
                listener?.onTodoItemClick(adapterPosition)
            }

            view.setOnLongClickListener {
                listener?.onTodoItemLongClick(adapterPosition)
                return@setOnLongClickListener true
            }
        }
    	...
}

뷰홀더의 생성자에 리스너를 전달받도록 수정하고 뷰홀더 객체가 생성될 때 이 리스너를 통해 뷰에 리스너를 달아준다. 그리고 어댑터에서 뷰홀더를 생성할 때 이 리스너를 전달해준다.

- MainAcitivty 수정

class MainActivity : AppCompatActivity() {
	...
    
    private fun initRecyclerView() {
        mTodoListAdater = TodoListAdapter().apply {
            listener = object: TodoListAdapter.OnTodoItemClickListener {
                override fun onTodoItemClick(position: Int) {
                    Toast.makeText(this@MainActivity, "onTodoItemClick!", Toast.LENGTH_SHORT).show()
                }

                override fun onTodoItemLongClick(position: Int) {
                    Toast.makeText(this@MainActivity, "onLongTodoItemClick!", Toast.LENGTH_SHORT).show()
                }
            }
        }
        ...
    }
    
    ...
}

TodoListAdapter를 apply 메서드를 통해 내부 블럭에서 listener에 접근하여 인터페이스를 구현 해 준다.

토스트 메시지를 통해 잘 동작하는지 확인 해 보았다.

한번 클릭했을 때 onTodoItemClick! 토스트가, 꾹 클릭했을 때 onLongTodoItemClick이 호출되면 성공이다.

- onTodoItemClick 처리 구현

    private fun initRecyclerView() {
        mTodoListAdater = TodoListAdapter().apply {
            listener = object: TodoListAdapter.OnTodoItemClickListener {
                override fun onTodoItemClick(position: Int) {
                    openModifyTodoDialog(getItem(position))
                }
                ...
            }
        }
		...
    }

onTodoItemClick 메서드에서 포지션에 해당하는 TodoModel을 얻어오는 getItem(position: Int)를 호출한 뒤 얻어온 TodoModel을 openModifyTodoDialog 함수에 전달한다.

getItem은 TodoListAdaper에서 구현해야 한다. 간단한 코드이니 패스한다.

    private fun openModifyTodoDialog(todoModel: TodoModel) {
        val dialogView = layoutInflater.inflate(R.layout.dialog_add_todo, null)
        dialogView.et_todo_title.setText(todoModel.title)
        dialogView.et_todo_description.setText(todoModel.description)

        val dialog = AlertDialog.Builder(this)
            .setTitle("수정하기")
            .setView(dialogView)
            .setPositiveButton("확인", { dialogInterface, i ->
                val title = dialogView.et_todo_title.text.toString()
                val description = dialogView.et_todo_description.text.toString()

                todoModel.description = description
                todoModel.title = title
                
                mTodoViewModel.updateTodo(todoModel)
            })
            .setNegativeButton("취소", null)
            .create()

        dialog.show()
    }

전달받은 todoModel의 값으로 다이얼로그뷰의 텍스트들을 세팅한다.

그리고 다이얼로그를 통해 값을 받은 뒤 todoModel의 값을 변경하고 뷰모델의 updateTodo 함수에 전달한다.

class TodoViewModel(application: Application): AndroidViewModel(application) {
	...

    fun updateTodo(todoModel: TodoModel) {
        mTodoRepository.updateTodo(todoModel)
    }
    ...
}

class TodoRepository(application: Application) {
	...
    fun updateTodo(todoModel: TodoModel) {
        Observable.just(todoModel)
            .subscribeOn(Schedulers.io())
            .subscribe({
                mTodoDAO.updateTodo(todoModel)
            }, {
                // Handle error.
            })
    }
}

interface TodoDAO  {
	...
    
    @Update
    fun updateTodo(todoModel: TodoModel)
}

뷰모델, 리포지토리, DAO를 위와같이 변경한다. 매우 짧고 간단하다.

- onTodoItemLongClick 처리 구현

롱클릭을 하면 확인과 취소 버튼이 있는 다이얼로그를 출력하고 확인을 누르면 삭제하도록 구현 할 것이다.

    private fun openDeleteTodoDialog(todoModel: TodoModel) {

        val dialog = AlertDialog.Builder(this)
            .setTitle("삭제하기")
            .setMessage("확인을 누르면 삭제됩니다.")
            .setPositiveButton("확인", { dialogInterface, i ->
                mTodoViewModel.deleteTodo(todoModel)
            })
            .setNegativeButton("취소", null)
            .create()

        dialog.show()
    }

기본 다이얼로그를 사용하여 위와 같은 함수를 MainAcitivty 내에 추가한다.

    private fun initRecyclerView() {
        mTodoListAdater = TodoListAdapter().apply {
            listener = object: TodoListAdapter.OnTodoItemClickListener {
            	...
                
                override fun onTodoItemLongClick(position: Int) {
                    openDeleteTodoDialog(getItem(position))
                }
            }
        }
        ...
    }

onTodoItemLongClick 메서드에서 위에서 추가한 다이얼로그 함수를 호출한다.

class TodoViewModel(application: Application): AndroidViewModel(application) {
	...

    fun deleteTodo(todoModel: TodoModel) {
        mTodoRepository.deleteTodo(todoModel)
    }
}

class TodoRepository(application: Application) {
	...

    fun deleteTodo(todoModel: TodoModel) {
        Observable.just(todoModel)
            .subscribeOn(Schedulers.io())
            .subscribe({
                mTodoDAO.deleteTodo(todoModel)
            }, {
                // Handle error.
            })
    }
}

interface TodoDAO  {
	...

    @Delete
    fun deleteTodo(todoModel: TodoModel)
}

위 처럼 뷰모델, 리포지토리, DAO를 수정한다.

굉장히 간단하게 이벤트를 추가하고 처리하는 기능을 구현하게 되었다.

- 다음 할 일

우선 지금까지 한 프로젝트에 문제가 있는데, update를 하고나서 리스트가 업데이트가 안된다는 것이다.

이유는 LiveData와 DiffUtil 때문인 것 같은데, 실제로 업데이트는 잘 이루어지지만 리스트 갱신이 되지 않고 있었다.

또한 예제이고 기능 사용에 중점을 뒀기 때문에 다이얼로그 쪽 코드가 중복이 많다.

심각한 문제는 아니기에 추후 수정으로 미루고, 다음으로는 DataBinding 이라는 것을 적용 볼 것이다.

댓글8

  • 박용준 2020.06.14 00:26

    안녕하세요. 올리신 코드 보면서 공부하는 학생입니다. 혹시 올리신 부분에 대한 전체적인 코드를 볼 수 있을까요?
    이 코드를 보면서 이번에 프로젝트를 하고 있는데.. 가능하신지 댓글 남깁니다.

    (+수정) 비밀댓글을 볼 수가 없습니다. 비밀글 풀어주세요
    답글

    • 두잇뚜 2020.06.14 14:08 신고

      안녕하세요~ 제가 올린 코드를 보고 공부를 하신다니 굉장히 뿌듯하네요.

      https://bitbucket.org/winan305/android-sample-codes/src/master/MyTodo/

      전체 코드는 위 저장소에 있습니다. 감사합니다.

    • 두잇뚜 2020.06.27 19:54 신고

      알림이 안와서 이제야 비밀글을 보지 못하시는 것을 봤어요. 너무 늦게 보았네요 ㅠㅠ

  • 카페모카 2020.09.17 22:45

    잘보고 갑니다.
    답글

  • 임종빈 2020.10.06 15:39

    👍
    답글

  • 최동희 2021.01.20 20:07

    안녕하세요, 블로그 보면서 하나하나 따라하고있는데요. Databinding에 대한 부분이 github에 다 안올라와있고 블로그에도 없네요 github에 업로드해주실수있나요?? 감사합니다.
    답글

    • 두잇뚜 2021.01.20 22:45 신고

      안녕하세요, 최동희님. :)

      제 블로그를 보고 공부해주셔서 감사합니다.

      요청해주신 DataBinding 관련한 포스트를 작성했습니다.
      부족하지만 도움이 되셨으면 좋겠습니다.

      아래 링크 확인 부탁드릴게요. :)
      https://doitddo.tistory.com/117

      새해 복 많이받으세요. ^^