KotlinでAndroidのTODOアプリを作る　Part1

# はじめに

本記事は[ジーズアカデミー 技術記事書いてみた編 Advent Calendar 2023](https://qiita.com/advent-calendar/2023/gsacademy)の5日目の記事です。


僕はちょっとした作業のやり忘れが多く、例えば「洗濯機のスタートを押して洗濯を始める」「電気を切ってから仕事に行く」「自転車の鍵をかける」なんかを良くやり忘れてしまう。
ついに妻から軽いADHDなんじゃないかと言われてしまったが、自分でもそうだと思うくらい抜けていると思う。

こういった時の対処法としては
1) 忘れ物をしないように意識する
2) TODOアプリを作る
の２択と相場は決まっているが、(1)は無理なので実質ToDoアプリを作るしか対処法はない。

28年の我が人生を振り返って意識改で忘れ物がなくなるわけがないと胸を張って言える。
自分はandroidを使っているのでKotlinでTodoアプリを作る。
(Flutterも興味あるが、Kotlinがやりたい)
 
# 想定読者
* Androidアプリ初心者
* Androidアプリ開発に興味がある

# 参考にした教材
Androidアプリ初心者のため以下のUdemyの教材をもとに作りました。
以下の講座はめちゃくちゃ分かりやすかったのでおすすめです！
この記事は要点を絞って記載します。
また説明も不十分なことがあるので気になる方は下記講座の受講おすすめします。

https://www.udemy.com/course/android_jetpack_compose_beginner/


# Part1のスコープと次の記事の予定
Part1ではRoomのインストールからデータベース周りの設定を書きたいと思います。

Part2ではHiltのインストール
Part3ではScaffold、ViewModel
Part4ではUI周りの作成
ということを書いて完成させたいと思います。

**記事書いてたらボリュームが増えすぎたのとアドベントカレンダーの締切が過ぎそうなので分割します^^;**



# Android　Studioのインストールと新規プロジェクト作成
まず、Androidを作成するためにAndroid Studioをインストールします。
インストールは以下からできます。

https://developer.android.com/studio?hl=ja

インストール方法は以下の公式サイトに書いています。クリック繰り返せばできるので省略します

https://developer.android.com/studio/install?hl=ja

AndroidStudioのインストールが完了したらNew Projectから今回はEmpty Compose Activityを選びます。
新規プロジェクト作成はこれで完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322871/38950676-bf79-2afe-bcd6-77a983210f33.png)


# Roomのインストール 
次にデータベース周りの設定をするためにRoomのインストールをします。
RoomとはSQLiteを簡単に使えるようにしてくれるライブラリです。

https://developer.android.com/jetpack/androidx/releases/room?hl=ja

SQLite APIを直接使う方法もあるよう(以下のページより)ですが、公式サイトでもRoomの使用を推奨されています。

https://developer.android.com/training/data-storage/sqlite?hl=ja

SQLiteとはサーバにデータを保存するのではなく、アプリ内部にデータを保存するときに使うデータベースです。
Webアプリを作ったことがある方は、サーバではなく、ブラウザのLocalStorageにデータを保存するのと似たイメージを持てば良いと思います。

Roomを使うためにbuild.gradle(app)に以下を追記してください
<details><summary>build.gradle(app)</summary>

```deff_gradle
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
+    id 'kotlin-kapt'   /*追加箇所*/

}

android {
    namespace 'com.example.todo'
    compileSdk 32

    defaultConfig {
        applicationId "com.example.todo"
        minSdk 25
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary true
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    buildFeatures {
        compose true
    }
    composeOptions {
        kotlinCompilerExtensionVersion '1.1.1'
    }
    packagingOptions {
        resources {
            excludes += '/META-INF/{AL2.0,LGPL2.1}'
        }
    }
}

dependencies {

    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
    implementation 'androidx.activity:activity-compose:1.3.1'
    implementation "androidx.compose.ui:ui:$compose_ui_version"
    implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version"
    implementation 'androidx.compose.material:material:1.1.1'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_ui_version"
    debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version"
    debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version"

+    def room_version = "2.5.0"　　　/*追加箇所*/
+    implementation "androidx.room:room-runtime:$room_version"　　　/*追加箇所*/
+    annotationProcessor "androidx.room:room-compiler:$room_version"　　　/*追加箇所*/
+    kapt "androidx.room:room-compiler:$room_version"　　　/*追加箇所*/
}
```

</details>



# データベース周りの設定を追加
Roomのインストールが完了したのでデータベースを作成していきます。
テーブルの型を定義するデータエンティティ、
データを操作するDao、
エンティティとDaoを管理してアプリケーションで使えるようにするAppDatabaseの３つを作成していきます。


## データ エンティティの作成
データエンティティとはデータベースのテーブルの型のことです。
つまりテーブルの定義を行います。

公式サイトの説明は以下になります。

https://developer.android.com/training/data-storage/room/defining-data?hl=ja


java/com/example/[アプリ名]配下にTaskクラスを新規作成し、そこでTaskテーブルの型を作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322871/d4b506de-6013-ca55-b96b-e52ed65c0c50.png)

ソースコードは以下になります。
<details><summary>Task</summary>

```Kotlin
package com.example.team_todo

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity
data class Task(
    //自動で生成するID
    @PrimaryKey(autoGenerate = true) val  id:Int = 0,
    var title:String,
    var description :String,
    var isFinish :Boolean,
    // アプリの仕様から不要と判断
    // var week : Array<String>,
    var deadLine : Int
)
```
</details>

## データにアクセスするDAOの作成
次にDAOを作成します。DAOはデータアクセスオブジェクトの略で、データベースにアクセスするためのメソッドを備えています。
公式サイトの説明は以下になります。

https://developer.android.com/training/data-storage/room/accessing-data?hl=ja


TaskDaoというクラスをTaskクラスと同じ階層にTaskテーブルにアクセスして
* 新しいタスクを登録するメソッド＝insertTask()
* タスクを全件取得するメソッド＝loadAllTasks()
* 登録済みのタスクを修正、更新するメソッド=updateTask(task:Task)
* 登録済みのタスクを削除するメソッド=deleteTask(task:Task)

を行うメソッドを定義したTaskDaoクラスを新規作成します。

ソースコードは以下になります。（自己理解用のコメントが多いですが、）
<details><summary>TaskDao</summary>

```Kotlin
package com.example.team_todo

import androidx.room.*
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow

//Room DAOの詳細はこちら
//https://developer.android.com/training/data-storage/room/accessing-data?hl=ja

//各DAOは、インターフェースまたは抽象クラスとして定義できます。
//基本的なユースケースでは、通常はインターフェースを使用します。
//いずれの場合も、DAO には常に @Dao アノテーションを付ける必要があります。
//DAO にプロパティはありませんが、アプリのデータベース内のデータを操作する 1 つ以上のメソッドを定義します。

//非同期DAOクエリを作成するの詳細はこちら
//https://developer.android.com/training/data-storage/room/async-queries?hl=ja

@Dao/**/
interface TaskDao {


    //  @Insert アノテーションを使用すると、
    //  データベース内の適切なテーブルにパラメータを挿入するメソッドを定義できます。
    @Insert

    //  ワンショット クエリは、1 回だけ実行され、実行時にデータのスナップショットを取得するデータベース オペレーションです。非同期ワンショット クエリの例を次に示します。
    suspend fun insertTask(task: Task)

    //    @Queryアノテーションを使用すると、SQL ステートメントを記述して
    //    DAOメソッドとして公開できます。
    //    これらのクエリメソッドは、アプリのデータベースからデータをクエリする場合や、
    //    より複雑な挿入、更新、削除を行う必要がある場合に使用できます。
    @Query("SELECT * FROM Task")

    //    オブザーバブルクエリは、クエリで参照されるテーブルが変更されるたびに
    //    新しい値を出力する読み取りオペレーションです。
    //    使用方法としては、基となるデータベースのアイテムが挿入、更新、削除されたときに、
    //    表示されているアイテムのリストを最新の状態に保つことが挙げられます。
    fun loadAllTasks():Flow<List<Task>>

    @Update
    suspend fun updateTask(task: Task)

    @Delete
    suspend fun deleteTask(task: Task)

}
```
</details>


## Room Databaseを作成してDaoで作成したメソッドを使えるようにする
DAOで定義したメソッドを使えるようにするために、今度はRoomDatabaseを作成します。
データエンティティでテーブルを作成し、DAOでデータを操作するメソッドを作成しました。
それらをまとめてアプリケーションが使用できるようにしたクラスがRoom Databaseです。

公式サイトは以下になります。

https://developer.android.com/training/data-storage/room?hl=ja#database


上で説明したもののうまく説明できていない気がして、Bardに聞いたところ以下の回答がきました。
特にDAOメソッドをそのまま使えばいいやん。と思ったのでそこを聞いてみました。

> DAOはデータベースへのアクセスを抽象化したインターフェースで、RoomDatabaseはDAOを実装したクラスです。DAOで定義したメソッドをそのまま使用すれば良いと思われるのは、DAOがデータベースへのアクセスを抽象化しているためです。しかし、DAOを直接使用すると、データベースのオープンやクローズ、トランザクションの開始やコミットなどの処理を自分で行う必要があります。RoomDatabaseを使用することで、これらの処理を自動的に行うことができます。また、RoomDatabaseを使用することで、データベースのバージョン管理や、マルチスレッドでのアクセスの安全性などの機能が提供されます。

ソースコードは以下になります。（自己理解用のコメントが多いですが、）
TaskクラスTaskDaoクラスと同じ階層に新規作成します。

<details><summary>AppDatabase</summary>

```Kotlin
package com.example.todo

import androidx.room.Database
import androidx.room.RoomDatabase
import com.example.team_todo.Task
import com.example.team_todo.TaskDao

@Database(entities = [Task::class], version=1)
abstract class AppDatabase : RoomDatabase {
    abstract fun taskDao() : TaskDao
}
```

</details>

# 振り返り
エンティティ、Dao、AppDatabaseを作成してデータベースと操作するメソッドが作成できました。

あとは画面を作ってこのボタンを押したらデータベースにタスクを登録して〜というところを実装すればOKです。
ただ、そのためにHiltやViewModelのことも書きたいので本記事はここまでとします。

# 参考にした記事
記事を書くにあたり参考にした記事です。
## Roomについて

https://qiita.com/iTakahiro/items/7e0d63140ae4dac10d18


https://developer.android.com/training/data-storage/sqlite?hl=ja


## SQLiteについて

https://qiita.com/ko1nksm/items/87d27a287e1b6005d11c

# 最後に
アドベントカレンダーで１話で完結しない記事を投稿してしまいました。^^;
でも記事を書きながら理解が深まったのでヨシとします。
続きはできれば今年中に書き切りたいと思います！