본문 바로가기
Development/Android

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

by du.it.ddu 2020. 2. 29.
반응형

이번 포스팅에서는 이전 포스팅에서 언급했듯이 RecyclerView를 위한 어댑터와 뷰홀더 클래스를 작성하고 임의의 데이터로 리스트를 구현하고 "추가" 버튼으로 데이터를 추가해보도록 하겠다.

- 어댑터 클래스 생성

view 폴더 아래에 adapter 폴더를 만들고 그 안에 TodoListAdapter 클래스를 생성한다.

이 어댑터 클래스는 TodoModel의 리스트를 생성자로부터 전달받으며, RecyclerView.Adapter를 상속받고, RecyclerView.ViewHolder를 뷰홀더로 갖는 클래스로 구현한다. 아래 코드를 참고한다.

class TodoListAdapter(val todoItems: ArrayList<TodoModel>): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    override fun getItemCount(): Int {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
    
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
}

onCreateViewHolder와 onBindViewHolder, getItemCount를 구현해야 한다. 하나씩 구현해보자.

- 어댑터 클래스 구현

getItemCount 메서드는 이 어댑터가 아이템을 얼마나 가지고 있는지 얻는 함수다. 아이템의 개수는 todoItems의 개수와 같으므로 todoItems의 사이즈를 반환한다.

    override fun getItemCount(): Int {
        return todoItems.size
    }

onCreateViewHolder 에서는 현재 아이템이 사용할 뷰홀더를 생성하여 반환한다. 이 메서드에서 item_todo 레이아웃을 사용하여 뷰를 생성하고 뷰홀더에 뷰를 전달하여 생성된 뷰홀더를 반환 할 것이다.

뷰홀더는 리스트를 스크롤하는 동안 뷰를 생성하고 다시 뷰의 구성요소를 찾는 행위를 반복하면서 생기는 성능저하를 방지하기 위해 미리 저장 해 놓고 빠르게 접근하기 위하여 사용하는 객체다. 자세한 내용은 구글링하자.

아무튼 우리가 사용할 뷰홀더 클래스를 아래와 같이 정의하자.

    class TodoViewHolder(view: View): RecyclerView.ViewHolder(view) {
        fun bind(todoModel: TodoModel) {
            
        }
    }

RecyclerView.ViewHolder를 상속하며, 뷰홀더가 저장할 뷰를 생성자에서 받는다. 또한 내부에 bind 라는 함수로 뷰에 데이터를 바인딩하도록 한다. 현재는 껍데기만 정의하고 추후 업데이트하자.

이제 onCreateViewHolder 클래스를 아래와 같이 작성한다.

    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)
        
        return viewHolder
    }

굉장히 간단하다. 뷰를 인플레이트하고 이를 전달하여 뷰홀더를 만들고 리턴한다.

이제 onBindViewHolder 클래스를 아래와 같이 작성한다.

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val todoModel = todoItems[position]
        
        val todoViewHolder = holder as TodoViewHolder
        todoViewHolder.bind(todoModel)
    }

간단하다. 현재 아이템의 포지션에 대한 데이터 모델을 리스트에서 얻고 holder객체를 TodoViewHolder로 형변환한 뒤 bind 메서드에 이 모델을 전달하여 데이터를 바인딩하도록 한다.

뷰홀더는 어떤 프로젝트냐에 따라 굉장히 많아지고 여러가지일 수 있으므로 실제로 위 처럼 하면 위험하다. 이 프로젝트는 샘플이고 항상 TodoViewHolder 이므로 위와 같이 구현하고 별도의 처리를 하지 않는다.

- MainAcitivty에서 어댑터를 활용하여 리스트 만들기

class MainActivity : AppCompatActivity() {

    private lateinit var mTodoListAdater: TodoListAdapter
    private val mTodoItems: ArrayList<TodoModel> = ArrayList()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        initRecyclerView()
    }

    private fun initRecyclerView() {
        
    }
}

 

initRecyclerView 라는 메서드에서 어댑터를 생성하고 RecyclerView를 세팅하도록 할 것이다. 이 메서드를 onCreate 함수에서 호출 할 것이다.

아래처럼 메서드를 작성한다.

    private fun initRecyclerView() {
        mTodoListAdater = TodoListAdapter(mTodoItems)
        
        rl_todo_list.run { 
            setHasFixedSize(true)
            layoutManager = LinearLayoutManager(this@MainActivity)
            adapter = mTodoListAdater
        }
    }

어댑터를 생성한 후, 레이아웃 파일에 정의한 RecyclerView 객체에 접근하여 run 블럭 안에서 필요한 처리를 수행한다. 이제 RecyclerView 생성이 완료 되었다.

- 임의 데이터를 사용하여 리스트 확인 및 뷰홀더 클래스 수정

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mTodoItems.run {
            add(TodoModel("안드로이드 포스팅1", "Kotlin + MVVM + AAC 로 Todo 앱 만들기 - 1", Date().time))
            add(TodoModel("안드로이드 포스팅2", "Kotlin + MVVM + AAC 로 Todo 앱 만들기 - 2", Date().time))
            add(TodoModel("안드로이드 포스팅3", "Kotlin + MVVM + AAC 로 Todo 앱 만들기 - 3", Date().time))
        }

        initRecyclerView()
    }

onCreate 함수에 임의로 위와같은 데이터를 리스트에 넣어보자. 그리고 앱을 실행 해 보면 아래와 같이 나올 것이다.

실행한 애뮬레이터나 기기에 따라 조금 다를 순 있으나 위와 거의 유사한 형태일 것이다. 위에서 보듯 우리가 생성한 데이터와 리스트 아이템의 내용이 다르다. 뷰홀더 클래스를 아래와 같이 수정한다.

    class TodoViewHolder(view: View): RecyclerView.ViewHolder(view) {
        val title = view.tv_todo_title
        val description = view.tv_todo_description
        val createdDate = view.tv_todo_created_date

        fun bind(todoModel: TodoModel) {
            title.text = todoModel.title
            description.text = todoModel.description
            createdDate.text = todoModel.createdDate.toDateString("yyyy.MM.dd HH:mm")
        }
    }

복잡하지 않다. 뷰가 가지고 있는 텍스트뷰 객체들을 가지고 있고 bind 함수는 각각에 맞게 데이터를 세팅한다.

todoModel의 createdDate의 경우 시간값을 Long으로 갖고 있기 때문에 파싱하는 단계가 필요하다. 여러 방법이 있지만 나 같은 경우는 코틀린의 extension 을 활용하여 toDateString(format: String) 함수를 확장하여 사용했다. 아래의 코드를 참고하고 개인이 선호하는 방법으로 사용하자.

fun Long.toDateString(format: String): String {
    val simpleDateFormat = SimpleDateFormat(format)
    return simpleDateFormat.format((Date(this)))
}

그리고 다시 앱을 수정하면 아래와 같이 나타날 것이다.

- 추가 버튼으로 Todo 아이템 추가, 리스트 갱신

이제 추가 버튼을 활용하여 투두 아이템을 생성 해 보자.

Title과 Description은 다이얼로그를 간단하게 띄워서 작성할 것이다. 우선 아래처럼 껍데기를 만든다.

class MainActivity : AppCompatActivity() {
    
    ...
	 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        initRecyclerView()
        initAddButton()
    }

    private fun initRecyclerView() {
        ...
    }
    
    private fun initAddButton() {
        btn_add_todo.setOnClickListener {
            openAddTodoDialog()
        }
    }
    
    private fun openAddTodoDialog() {
        
    }

버튼을 클릭하면 openAddTodoDialog() 가 호출되어 다이얼로그가 띄워지고 이를 통해 입력을 받은 뒤 TodoModel을 생성하여 리스트에 더해주고 리스트를 갱신 할 것이다.

그 전에 어댑터에 아이템을 추가하는 메서드를 어댑터 클래스에 아래와 같이 추가해주자.

    fun addItem(todoModel: TodoModel) {
        todoItems.add(todoModel)
    }

이제 다이얼로그를 커스텀하기 위해 아래와 같은 레이아웃 파일을 생성하자.

<?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"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="8dp">
    
    <EditText
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:id="@+id/et_todo_title"
        android:hint="Input todo title"
        android:textColor="#000000"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <EditText
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:id="@+id/et_todo_description"
        android:hint="Input todo description"
        android:textColor="#000000"
        android:layout_marginTop="16dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/et_todo_title"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Title과 Description 입력을 위한 EditText를 두 개 갖는 레이아웃 파일이다.

이제 AlertDialog를 활용하여 커스텀 다이얼로그를 생성하고 호출하는 메서드를 구현한다.

이 다이얼로그는 확인, 취소 버튼이 존재하고 확인버튼을 누르면 입력된 값과 현재시각을 활용하여 TodoModel을 만들고 어댑터에 추가한 뒤 갱신 함수를 호출 할 것이다.

    private fun openAddTodoDialog() {
        val dialogView = layoutInflater.inflate(R.layout.dialog_add_todo, null)
        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()
                val createdDate = Date().time
                
                val todoModel = TodoModel(title, description, createdDate)
                mTodoListAdater.addItem(todoModel)
                mTodoListAdater.notifyDataSetChanged()
            })
            .setNegativeButton("취소", null)
            .create()
        dialog.show()
    }

커스텀으로 생성한 다이얼로그 뷰를 활용하기 위해 뷰를 생성하고 다이얼로그 빌더에 넘겨준다. 그리고 확인버튼과 취소버튼을 생성하고 리스너를 등록해주고 생성한다.

그리고 생성한 다이얼로그를 보여준다.

이제 앱을 실행하여 추가 버튼으로 TodoModel을 추가해보자.

그림을 나란히 넣고 싶은데 잘 안된다. 흐음.. 아무튼 처음 실행하면 리스트가 비어있어서 아무것도 나타나지 않을 것이다. 추가 버튼을 누르면 다이얼로그가 등장하고 입력 후 확인을 누르면 마지막 사진처럼 내가 입력한 내용으로 투두 아이템이 추가된다. 

- 다음으로

이제 지금까지 작성 한 내용들을 수정하여 MVVM 패턴으로 바꿀 것이고, 이 과정에서 AAC를 적용하는 과정을 진행 할 것이다.

반응형