Genta Hirauchi

公開日:2020/04/19
更新日:2020/08/03

【Kotlin MVP】MVPアーキテクチャで、TODOアプリを作成する #2

アプリ開発におけるアーキテクチャ(設計)の1つに、MVPというものがあります。

本記事は、ToDoアプリの作成を通して、MVPについて学習するシリーズの第2弾です。

第2弾の今回は、タスクをリスト表示するListViewを実装します。

なお、本シリーズで実装するTODOアプリのソースコードは、todo-sample | GitHubにて公開しております。

目次

ListViewの実装

データクラスの実装

まずは、一覧表示するタスクのデータクラスを作成します。

  • Task.kt
data class Task(
        val id: Int,
        val state: Int,
        val description: String
)

Taskは、id(インデックス)、state(状態)、description(タスク内容)をパラメータとして持つようにしました。

stateはInt型で、0がTODO、1がDOING、2がDONEとして実装していきます。

Adapterの実装

続いて、ListViewのAdapterを実装します。

  • MainFragment.kt
  • task_item.xml
class MainFragment : Fragment() {

    (省略)

    private class TasksAdapter(private val tasks: List<Task>): BaseAdapter() {

        private val stateTexts = listOf(R.string.todo, R.string.doing, R.string.done)
        private val stateColors = listOf(R.color.todo, R.color.doing, R.color.done)

        override fun getCount() = tasks.size

        override fun getItem(i: Int) = tasks[i]

        override fun getItemId(i: Int) = i.toLong()

        override fun getView(i: Int, view: View?, viewGroup: ViewGroup): View {
            val task = getItem(i)
            val rowView = view ?: LayoutInflater.from(viewGroup.context).inflate(R.layout.task_item, viewGroup, false)

            rowView.findViewById<TextView>(R.id.taskState).apply {
                text = context.getString(stateTexts[task.state])
                setTextColor(ContextCompat.getColor(context, stateColors[task.state]))
            }

            rowView.findViewById<TextView>(R.id.taskDescription).apply {
                text = task.description
            }
            return rowView
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <TextView
        android:id="@+id/taskState"
        android:textSize="18sp"
        android:layout_width="80dp"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/taskDescription"
        android:textSize="18sp"
        android:textColor="@color/black"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"/>

    <ImageView
        android:id="@+id/taskDeleteButton"
        android:src="@drawable/ic_delete"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

TasksAdapterというAdapterクラスを作成しました。別ファイルとして作成してもいいのですが、今回は、MainFragment内に実装いたしました。

各タスクのViewは、
TODOやDONEなどの状態を表示するためのTextView、
タスクの内容を表示するためのTextView、
タスクを削除するためのボタンとなるImageView
で構成されております。

ListViewの実装

最後に、MainFragmentにListViewを実装し、そのListViewのAdapterに、TasksAdapterを設定します。

  • MainFragment.kt
  • task_item.xml
class MainFragment : Fragment() {

    (省略)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        addTaskButton.setOnClickListener {
            Toast.makeText(activity, "Add Tapped", Toast.LENGTH_SHORT).show()
        }

        // TODO ダミーデータ
        val tasks = arrayListOf<Task>()
        val task1 = Task(0, 0, "Task1")
        val task2 = Task(1, 0, "Task2")
        val task3 = Task(2, 1, "Task3")
        val task4 = Task(3, 2, "Task4")
        val task5 = Task(4, 2, "Task5")
        tasks.add(task1)
        tasks.add(task2)
        tasks.add(task3)
        tasks.add(task4)
        tasks.add(task5)

        listView.adapter = TasksAdapter(tasks)
    }

    (省略)
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/addTaskButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/ic_add"
            android:layout_alignParentEnd="true"
            android:layout_alignParentBottom="true"
            android:layout_margin="24dp" />

</RelativeLayout>

ダミーデータとして、Taskのリストを作成し、TasksAdapterの生成時に引数として渡しております。

将来的には、データベースから取得したTaskのリストを渡すように修正していきます。

実行結果

この章の修正内容はListViewの実装 | GitHubからも確認頂けます。

イベントの実装

Interfaceの実装

続いて、リストの状態表示、内容、削除ボタンがタップされた際のイベントをそれぞれ実装していきます。まずは、Interfaceの実装からです。

  • MainFragment.kt
class MainFragment : Fragment() {

    (省略)

    interface TaskItemListener {

        fun onStateClick(task: Task)

        fun onDescriptionClick(task: Task)

        fun onDescriptionClick(task: Task)
    }
}

TaskItemListenerというInterfaceを実装しました。

TaskItemListenerには、onStateClick()onDescriptionClick()onDescriptionClick()というCallbackが実装されております。

Callbackの呼び出し処理の実装

Interfaceの実装ができましたら、TaskAdapterにて、Interfaceに実装されているCallbackを呼び出す処理を実装します。

  • MainFragment.kt
class MainFragment : Fragment() {

    (省略)

    private class TasksAdapter(private val tasks: List<Task>, private val listener: TaskItemListener): BaseAdapter() {

        (省略)

        override fun getView(i: Int, view: View?, viewGroup: ViewGroup): View {
            val task = getItem(i)
            val rowView = view ?: LayoutInflater.from(viewGroup.context).inflate(R.layout.task_item, viewGroup, false)

            rowView.findViewById<TextView>(R.id.taskState).apply {
                text = context.getString(stateTexts[task.state])
                setTextColor(ContextCompat.getColor(context, stateColors[task.state]))
                setOnClickListener {
                    listener.onStateClick(task)
                }
            }

            rowView.findViewById<TextView>(R.id.taskDescription).apply {
                text = task.description
                setOnClickListener {
                    listener.onDescriptionClick(task)
                }
            }

            rowView.findViewById<ImageView>(R.id.taskDeleteButton).setOnClickListener {
                listener.onDeleteClick(task)
            }

            return rowView
        }
    }

    (省略)
}

状態、内容、削除ボタンがタップされた際に、Callbackが呼ばれるように実装しました。

また、コンストラクタで、TaskItemListenerを受け取るように実装を加えております。

CallbackのOverride

最後に、MainFragmentにて、TaskItemListenerをimplementしたobjectを作成し、Callbackをoverrideして、イベント発生時に行いたい処理を実装します。

  • MainFragment.kt
class MainFragment : Fragment() {

    (省略)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        (省略)

        listView.adapter = TasksAdapter(tasks, itemListener)
    }

    val itemListener = object : TaskItemListener {
        override fun onStateClick(task: Task) {
            Toast.makeText(activity, "onStateClick : " + task.description, Toast.LENGTH_SHORT).show()
        }

        override fun onDescriptionClick(task: Task) {
            Toast.makeText(activity, "onDescriptionClick : " + task.description, Toast.LENGTH_SHORT).show()
        }

        override fun onDeleteClick(task: Task) {
            Toast.makeText(activity, "onDeleteClick : " + task.description, Toast.LENGTH_SHORT).show()
        }
    }
}

イベント発生時には、Callbackのメソッド名と、タスクの内容が、トーストで表示されるように実装しました。将来的には、タスクの状態変更処理、タスクの内容変更処理、タスクの削除処理を実装する予定です。

このobject(itemListener)は、TasksAdapterの生成時に引数として受け渡し、2-2. Callbackの呼び出し処理の実装 で実装した処理に繋がっていきます。

実行結果

この章の修正内容はイベントの実装 | GitHubからも確認頂けます。

今回の実装はここまでとなります。
次回は、Roomライブラリを使用したデータベースの実装していく予定です。
ありがとうございました。