Genta Hirauchi

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

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

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

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

第5弾の今回は、Repositoryを実装します。

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

目次

Repositoryの実装

ライブラリの導入

Repositoryを実装するにあたりまして、RxJavaを使用したいと思います。

RxJavaを使用するために、build.gradleを修正し、ライブラリの導入を行います。

  • build.gradle
dependencies {
    (省略)
    implementation "androidx.room:room-rxjava2:2.2.5"
    implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
}

Daoの修正

続いて、TaskDaoの修正を行います。

  • TaskDao.kt
@Dao
interface TaskDao {
    @Query("SELECT * FROM task")
    fun getAll(): Single<List<Task>>

    @Insert
    fun insert(task: Task): Completable

    @Update
    fun update(task: Task): Completable

    @Delete
    fun delete(task: Task): Completable
}

getAll()では、Singleを返すようにしました。 処理が成功した場合はonSuccessが、失敗するとonErrorが呼び出し元で呼ばれます。

onSuccessでは、指定した戻り値(サンプルではList<Task>)を受け取ることができ、onErrorでは、Throwableを受け取ることができます。

insert()update()delete()では、Completableを返すようにしました。処理が問題なく実行されるとonCompleteが、失敗するとonErrorが呼び出し元で呼ばれます。

onCompleteには引数はなく、onErrorでは、Throwableを受け取ることができます。

参考:Room 🔗 RxJava – Android Developers – Medium

interfaceの実装

続いて、Repositoryクラスでimplementするinterfaceを実装します。取得、挿入、更新、削除のメソッドと、それらを呼んだ後のCallbackを定義します。

  • TaskDataSource.kt
interface TaskDataSource {

    interface LoadTaskCallback {

        fun onLoadTasks(tasks: List<Task>)

        fun onError(error: Throwable)
    }

    interface Callback {

        fun onSuccess()

        fun onError(error: Throwable)
    }

    fun loadTasks(callback: LoadTaskCallback)

    fun insertTask(task: Task, callback: Callback)

    fun updateTask(task: Task, callback: Callback)

    fun deleteTask(task: Task, callback: Callback)
}

LoadTaskCallbackは、取得処理のCallbackです。loadTasks()の結果通知に使用します。

PresenterクラスにてOverrideしておき、取得処理が成功した場合は、onLoadTasks()でタスク一覧を返し、失敗した場合は、onError()でThrowableを返します。

Callbackは、挿入、更新、削除のCallbackです。loadTasks()、updateTask()、deleteTask()の結果通知に使用します。

PresenterクラスにてOverrideしておき、挿入、更新、削除処理が成功した場合は、onSuccess()を呼び出し、失敗した場合は、onError()でThrowableを返します。

Repositoryの実装

続いて、Repositoryクラスを実装します。Repositoryクラスでは、先ほどのTaskDataSourceをimplementし、取得、挿入、更新、削除処理を実装します。

  • TaskRepository.kt
class TaskRepository(private val taskDao: TaskDao): TaskDataSource {

    val disposables = CompositeDisposable()

    override fun loadTasks(callback: TaskDataSource.LoadTaskCallback) {
        taskDao.getAll()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeBy(
                onSuccess = {
                    callback.onLoadTasks(it)
                },
                onError = {
                    callback.onError(it)
                }
            ).addTo(disposables)
    }

    override fun insertTask(task: Task, callback: TaskDataSource.Callback) {
        taskDao.insert(task)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeBy(
                onComplete = {
                    callback.onSuccess()
                },
                onError = {
                    callback.onError(it)
                }
            ).addTo(disposables)
    }

    override fun updateTask(task: Task, callback: TaskDataSource.Callback) {
        taskDao.update(task)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeBy(
                onComplete = {
                    callback.onSuccess()
                },
                onError = {
                    callback.onError(it)
                }
            ).addTo(disposables)
    }

    override fun deleteTask(task: Task, callback: TaskDataSource.Callback) {
        taskDao.delete(task)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribeBy(
                onComplete = {
                    callback.onSuccess()
                },
                onError = {
                    callback.onError(it)
                }
            ).addTo(disposables)
    }
}

各メソッドでは、RxJavaの処理が実装されておりますが、詳しい解説は省かせて頂きます。TaskDaoの処理が成功すると、onSuccessやonCompleteが、失敗すると、onErrorが呼ばれるという認識をしていただければと思います。

onLoadTasks()では、取得処理が成功すると、TaskDataSource.LoadTaskCallbackのonLoadTasks()を呼ぶように実装しております。”it”は、List<Task>を参照しております。失敗した場合は、onErrorを呼び出します。”it”はThrowableを参照しております。

insertTask()、updateTask()、deleteTask()では、挿入、更新、削除処理が成功すると、TaskDataSource.CallbackのonSuccess()を呼ぶように実装しております。失敗した場合は、onErrorを呼び出します。”it”はThrowableを参照しております。

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

Presenter、Viewの修正

Presenterの修正

これまで、TaskDaoの処理を呼び出し、データベースにアクセスしていた部分を、Repositoryの処理を呼び出すように修正します。

今回、新たにエラーのハンドリングが追加されましたので、まずは、MainFragmentへエラーを通知するためのCallbackを、TasksContractのViewにを追加します。

  • TaskContract.kt
interface TaskContract {

    interface View {

        fun onLoadTasks(tasks: List<Task>)

        fun showError(message: String?)
    }

    (省略)
}

続いて、TaskPresenterを修正していきます。

  • TaskPresenter.kt
class TaskPresenter(private val taskRepository: TaskRepository, private val view: TaskContract.View): TaskContract.Presenter {

    override fun loadTasks() {
        taskRepository.loadTasks(object : TaskDataSource.LoadTaskCallback {
            override fun onLoadTasks(tasks: List<Task>) {
                view.onLoadTasks(tasks)
            }

            override fun onError(error: Throwable) {
                view.showError(error.message)
            }
        })
    }

    override fun insertTask(description: String) {
        val task = Task(0, 0, description)

        taskRepository.insertTask(task, object : TaskDataSource.Callback {
            override fun onSuccess() {
                loadTasks()
            }

            override fun onError(error: Throwable) {
                view.showError(error.message)
            }
        })
    }

    override fun updateTaskState(task: Task) {
        task.state++
        if (task.state > 2) task.state = 0

        taskRepository.updateTask(task, object : TaskDataSource.Callback {
            override fun onSuccess() {
                loadTasks()
            }

            override fun onError(error: Throwable) {
                view.showError(error.message)
            }
        })
    }

    override fun updateTaskDescription(task: Task, description: String) {
        task.description = description

        taskRepository.updateTask(task, object : TaskDataSource.Callback {
            override fun onSuccess() {
                loadTasks()
            }

            override fun onError(error: Throwable) {
                view.showError(error.message)
            }
        })
    }

    override fun deleteTask(task: Task) {

        taskRepository.deleteTask(task, object : TaskDataSource.Callback {
            override fun onSuccess() {
                loadTasks()
            }

            override fun onError(error: Throwable) {
                view.showError(error.message)
            }
        })
    }
}

これまで、コンストラクタで、TaskDaoを受け取っていたのですが、その部分を、TaskRepositoryを受け取るように修正しました。

loadTasks()では、TaskRepositoryのloadTasks()を呼びだしております。引数の設定部分では、object : TaskDataSource.LoadTaskCallback {}という形で、無名クラスによるCallbackの実装を行なっております。

処理が成功した場合は、onLoadTasks()が呼ばれ、TaskContract.ViewのonLoadTasks()を呼び出し、MainFragmentへタスク一覧を渡すように実装しております。

処理が失敗した場合は、onError()が呼ばれ、TaskContract.ViewのshowError()を呼び出し、MainFragmentへエラー内容を渡すように実装しております。

タスクの挿入、更新、削除に関しましては、取得処理と同じような実装であるため、解説を割愛させて頂きます。

Viewの修正

最後に、MainFragmentの修正を行います。

  • MainFragment.kt
class MainFragment : Fragment(), TaskContract.View {

    (省略)

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

        val db = TaskDatabase.getInstance(context)
        presenter = TaskPresenter(TaskRepository(db.taskDao()), this)
        presenter.loadTasks()

        (省略)
    }

    (省略)

    override fun showError(message: String?) {
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
    }

    (省略)
}

TaskPresenterの生成時に、TaskRepositoryを渡すようにしました。

また、新たに追加したCallbackの、showErrorをOverrideし、トーストでエラー内容が表示されるように実装しました。

実行結果

この章の修正内容はPresenter、Viewの修正 | GitHubからも確認頂けます。

今回の実装はここまでとなります。これで、MVPの処理の流れに関する部分は実装が完了しました。
次回は、タスクの追加や更新時に、内容を入力するダイアログ表示の実装していく予定です。おそらくシリーズラストの記事になると思います。
ありがとうございました。