13
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Android Jetpack(Room + Compose)でTodoアプリ作ろう【後編】

Last updated at Posted at 2022-05-19

みなさんこんにちは!この記事ではTodoアプリの続きをRoomを使ってデータベースにデータを保存する部分の実装を行いたいと思います。
前編をご覧になってない方はそちらを先にご覧ください。

今回作成したTodoアプリのソースコードはこちらにあります。(GitHub)

完成イメージ

device-2022-05-15-165226.gif

実装手順

1. Roomを導入する

build.gradledependenciesに下の2行を追加します。

build.gradle
dependencies {

    // for Room
    implementation "androidx.room:room-runtime:2.4.2"
    kapt "androidx.room:room-compiler:2.4.2"

    ....
}

app/build.gradleのpluginsにkotlin-kapt`を追加します。

build.gradle:app
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt' // 追加
}

追加できたら、Sync Nowを押します。
image.png

2. Entityを定義する

データベースのテーブルにあたるEntityを定義します。
Data Classに@Entityとアノテーションをつけるだけで簡単に定義することができます。

新しくTodoクラス用のファイルを生成して、Entityを定義します。
image.png

Todo.kt
@Entity(tableName = "todos")
data class Todo(
    @PrimaryKey(autoGenerate = true)
    val id: Int,
    val title: String = "",
    val created_at: Date = Date()
)

3. Daoを定義する

TodoDao.kt
@Dao
interface TodoDao {
    @Query("select * from todos order by created_at asc")
    fun getAll(): MutableList<Todo>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun post(todo: Todo)

    @Delete
    fun delete(todo: Todo)
}

4. DateTimeConverterクラスを作成する

RoomではDate型を扱えないため、Long型にキャストするコンバーターを作成します。

DateTimeConverter.kt
class DateTimeConverter {
    @TypeConverter
    fun fromTimestamp(value: Long?): Date? {
        return value?.let { Date(it) }
    }

    @TypeConverter
    fun dateToTimestamp(date: Date?): Long? {
        return date?.time?.toLong()
    }
}

5. RoomDatabaseを実装する

AppDatabase.kt
@Database(entities = [Todo::class], version = 1, exportSchema = false)
@TypeConverters(DateTimeConverter::class)
abstract class AppDatabase : RoomDatabase() {
    abstract fun todoDao(): TodoDao
}

6. RoomApplicationクラスを作成する

Applicationクラスを継承したRoomApplicationクラスを作成します。

RoomApplication.kt
class RoomApplication : Application() {
    companion object {
        lateinit var database: AppDatabase
    }

    override fun onCreate() {
        super.onCreate()

        database = Room.databaseBuilder(
            applicationContext,
            AppDatabase::class.java,
            "todos"
        ).build()
    }
}

マニフェストファイルに作成したRoomApplicationクラスを指定します。

AndroidManifest.xml
    <application
        android:name=".RoomApplication"
        
        .... >

7. MainActivityを書き換える

MainActivityを順番に書き換えていきます。

Composable関数を書き換える

MainScreenをMainActivityの中に移動します。

MainActivity.kt
class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ....
    }

    @Composable
    fun MainScreen() {
        ....
    }
}

MainScreenの中のtodoList変数をを削除してtext変数だけにします。
また引数にtodoListを追加します。

MainScreenの中を以下のように書き換えます。

MainActivity.kt
    @Composable
    fun MainScreen(todoList: SnapshotStateList<Todo>) {
        var text: String by remember { mutableStateOf("") }

        Column {
            TopAppBar(
                title = { Text("Todo List") }
            )
            LazyColumn(
                modifier = Modifier.fillMaxWidth().weight(1f)
            ) {
                items(todoList) { todo ->
                    key(todo.id) {
                        TodoItem(todo)
                    }
                }
            }
            Row(
                modifier = Modifier.fillMaxWidth().padding(16.dp)
            ) {
                OutlinedTextField(
                    value = text,
                    onValueChange = { it -> text = it },
                    label = { Text("ToDo") },
                    modifier = Modifier.wrapContentHeight().weight(1f)
                )
                Spacer(Modifier.size(16.dp))
                Button(
                    onClick = {
                        if (text.isEmpty()) return@Button
                        postTodo(text)
                        text = ""
                    },
                    modifier = Modifier.align(Alignment.CenterVertically)
                ) {
                    Text("ADD")
                }
            }

        }
    }

MainScreenの下にTodoItemを追加します。

MainActivity.kt
    @Composable
    fun TodoItem(todo: Todo) {
        val sdf = SimpleDateFormat("yyyy/MM/dd HH:mm:ss")

        Column(
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
                .clickable(onClick = { deleteTodo(todo) })
        ) {
            Text(
                text = todo.title,
                modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp)
            )
            Text(
                text = "created at: ${sdf.format(todo.created_at)}",
                fontSize = 12.sp,
                color = Color.LightGray,
                textAlign = TextAlign.Right,
                modifier = Modifier.fillMaxWidth()
            )
        }
    }

データベースを操作する関数を作成する

Todoを読み込み、作成、削除するための関数を作成する。
データベースの操作はメインスレッドで行えないため、Coroutineを使って実装します。

MainActivityの直下にデータベースを操作するためのTodoDaoクラスのインスタンスとTodoを管理するリストを作成します。
onCreateの下にloadTodopostTododeleteTodoの3つの関数を作成します。

MainActivity.kt
class MainActivity : ComponentActivity() {

    private val dao = RoomApplication.database.todoDao()
    private var todoList = mutableStateListOf<Todo>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        ....
    }

    private fun loadTodo() {
        CoroutineScope(Dispatchers.Main).launch {
            withContext(Dispatchers.Default) {
                dao.getAll().forEach { todo ->
                    todoList.add(todo)
                }
            }
        }
    }

    private fun postTodo(title: String) {
        CoroutineScope(Dispatchers.Main).launch {
            withContext(Dispatchers.Default) {
                dao.post(Todo(title = title))

                todoList.clear()
                loadTodo()
            }
        }
    }

    private fun deleteTodo(todo: Todo) {
        CoroutineScope(Dispatchers.Main).launch {
            withContext(Dispatchers.Default) {
                dao.delete(todo)

                todoList.clear()
                loadTodo()
            }
        }
    }

    ....
}

onCreateを書き換える

onCreateを以下のように書き換えます。

MainActivity.kt
class MainActivity : ComponentActivity() {

    private val dao = RoomApplication.database.todoDao()
    private var todoList = mutableStateListOf<Todo>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            TodoAppTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colors.background
                ) {
                    MainScreen(todoList)
                }
            }
        }

        loadTodo()
    }

    ....
}

アプリを実行してみましょう。
タスクを作成・削除できることを確認してください。

スクリーンショット 2022-05-15 15.59.png

完成

ここまで実装できれば完成です!(下の動画ではアイコンも表示しています。)
テキストフィールドから新しくタスクを追加することができ、タスクをタップすると削除することができます。

アプリを閉じてもデータは保存されています。

device-2022-05-15-165226.gif

参考資料

13
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?