見出し画像

【JetpackCompose】Realmを使ってTodoアプリを作ろう

まっこり

こんにちはマッコリです。今回のnoteはAndroidアプリ開発のチュートリアル記事です。Realmを使ってTodoアプリを作っていきます。Realmを使ったCRUDの実装方法、MVVM+Rアーキテクチャの実装方法、DIライブラリのHiltの使い方を学べる内容となっています。

完成版のソースコードはGitHubにアップロードしてあります。必要な場合は参照ください。

開発環境
Android Studioバージョン: Chipmunk 2021.2.1 Patch 1
Realmバージョン: realm-kotlin1.0.0
Hiltバージョン: 2.40.1
JetpackComposeバージョン: 1.2.0-beta02

1. 新規プロジェクトの作成

では、早速プロジェクトの作成を行なっていきます。

テンプレート選択

プロジェクトテンプレートは「Empty Compose Activity」を選択して、アプリ名は「RealmTodo」と設定しましょう。

不要なテンプレートの削除

プロジェクトが作成されたら、テンプレートに含まれる不要なコードを消していきましょう。

MainActivity.ktを開いて、以下のGreeting関数とDefaultPreview関数を軽して下さい。また、Greetingを呼び出しているコードも削除して下さい。

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    RealmTodoTheme {
        Greeting("Android")
    }
}

依存関係の追加

今回作成するアプリでは、RealmとHiltを使うのでこれらの依存関係を追加します。

Realmの依存関係から追加していきます。Projectレベルのbuild.gradleファイルを開いて、pluginsブロックに以下の一行を追加して下さい。

id 'io.realm.kotlin' version '1.0.0' apply false

また、Moduleレベルのbuild.gradleファイルのpluginsブロックとdependenciesブロックに以下のように記述して下さい。

plugins {
    ...(省略)...
    id 'io.realm.kotlin'
}
...
dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1'
    implementation 'io.realm.kotlin:library-base:1.0.0'
}

これでRealmの依存関係の追加は完了です。次はHiltの依存関係を追加していきます。

Projectレベルのbuild.gradleファイルのbuildscriptブロックに以下のようにコードを追加して下さい。

buildscript {
    ext {
        ...
        hilt_version = '2.40.1'
    }

    dependencies {
        classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
    }
}

Moduleレベルのbuild.gradleにも、以下のようにコードを追加して下さい。

plugins {
    ...
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}
...
dependencies {
    ....
    implementation "com.google.dagger:hilt-android:$hilt_version"
    kapt "com.google.dagger:hilt-compiler:$hilt_version"
}

以上でRealmとHiltの依存関係の追加は完了です。次はエンティティの作成に入っていきます。

ここまでのソースコードはこちら

2. エンティティの作成

この章では、Todo一件分のデータを表すRealmエンティティクラスを作っていきます。Realmではクラスがそのままデータベースのテーブル構造を表します。

Todoエンティティ

Todoリスト一件に必要な項目とデータタイプは以下のようになります

  • id: UUID

  • title: String

  • description: String

エンティティクラスの作成

それでは、Todoエンティティを作っていきます。MainActivity.ktと同じディレクトリにTodo.ktという名前でKotlinクラスファイルを作成して下さい。ファイルを作成したら、以下のようにコードを追加してください。

class Todo: RealmObject {
    @PrimaryKey
    @NotNull
    var id: UUID = UUID.randomUUID()
    var title: String = ""
    var description: String = ""
}

RealmObjectを継承すると、Realmのモデルと認識されます。これで、Todoエンティティの作成は完了です。

3. Hiltによる依存関係注入の設定

データベース構造の定義ができたので、データベースとのやりとりを担当するRepositoryクラスを作っていきたいところですが、その前にRepositoryやViewModelのDI(依存関係注入)をするためのライブラリであるHiltのセットアップを行なっていきます。Hiltについての説明は内容が大きくなってしまうので省略しますが、DIがわからない方は、この章を進める前にDevelopersサイトを参照してみて下さい。

Applicationクラスの追加

Hiltを使うためには、@HiltAndroidAppというアノテーションを付与されたApplicationクラスを作る必要があります。@HiltAndroidAppによって、Hiltの自動コード生成が開始されます。

MainActivityと同じディレクトリにApp.ktという名前でKotlinクラスファイルを作成して下さい。ファイルを追加したら、以下のようにコードを追加して下さい。

@HiltAndroidApp
class App: Application()

@HiltAndroidAppアノテーションを付与したApplicationクラスを作成しただけです。次は、このApplicationクラスをmanifestファイルに登録します。

AndroidManifest.xmlを開いて、applicationタグに以下ようにname属性を追加して下さい。

<application
    android:name=".App"
    ...

これでApplicationの設定は完了です。

MainActivityの変更

Hiltによる依存関係注入を使用するクラスには、それを表すアノテーションを付与する必要があります。今回のアプリでは、MainActivityにViewModelをInjectします。

MainActivityの上に以下のようにアノテーションを追加して下さい。

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    ...

以上でHiltのセットアップは終了です。

この章での変更内容はこちら

4. Repository作成

 RepositoryではTodoのCRUD操作をするための関数を作成します。

MainActivity.ktと同じディレクトリにTodoRepository.ktという名前でKotlinクラスファイルを追加して、以下のようにコードを追加して下さい。

Inject設定とヘルパー関数の追加

class TodoRepository @Inject constructor() {

    private fun getRealmInstance(): Realm {
        val config = RealmConfiguration.Builder(schema = setOf(Todo::class))
            .build()
        return Realm.open(config)
    }
}

要点の説明

class TodoRepository @Inject constructor() {

@Inject constructor()という記述は、HiltにこのRepositoryをインジェクトする方法を知らせるために必要です。今回、TodoRepositoryには特に引数が必要ないので、constructorは空の状態です。

private fun getRealmInstance(): Realm {
    val config = RealmConfiguration.Builder(schema = setOf(Todo::class))
        .build()
    return Realm.open(config)
}

Realmデータベースを操作するにはRealmインスタンスを取得する必要があります。getRealmInstanceはRealmインスタンスを取得するためのヘルパー関数です。

CRUD操作の追加

では、CRUD操作に対おする関数をRepositoryに追加していきます。TodoRepositoryに以下の4つの関数を追加して下さい。

/** Create. */
suspend fun createTodo(todo: Todo) {
    getRealmInstance().apply {
        write { copyToRealm(todo) }
    }.close()
}

/** Read. */
suspend fun getAllTodo(): RealmResults<Todo> {
    getRealmInstance().apply {
        val todos = query<Todo>().find()
        close()
        return todos
    }
}

/** Update. */
suspend fun updateTodo(todo: Todo) {
    getRealmInstance().apply {
        write {
            query<Todo>("id == $0", todo.id).find().first().apply {
                title = todo.title
                description = todo.description
            }
        }
    }.close()
}

/** Delete. */
suspend fun deleteTodo(todo: Todo) {
    getRealmInstance().apply {
        write {
            delete(query<Todo>("id == $0", todo.id).find())
        }
    }.close()
}

要点の説明

/** Create. */
suspend fun createTodo(todo: Todo) {
    getRealmInstance().apply {
        write { copyToRealm(todo) }
    }.close()
}

getRealmInstanceでRealmインスタンスを取得して、引数で受け取っているTodoインスタンスをwriteブロックの中でcopyToRealm関数を使ってDBに保存しています。DBの操作は非同期で実行したいため、coroutineのsuspend修飾子を関数の前に付けています。
DBの操作が終わったら必ずclose()として、Realmを閉じます。

/** Read. */
suspend fun getAllTodo(): RealmResults<Todo> {
    getRealmInstance().apply {
        val todos = query<Todo>().find()
        close()
        return todos
    }
}

CRUDのReadに対応する関数です。
Realmデータベースに保存されているtodoを前取得してきます。

/** Update. */
suspend fun updateTodo(todo: Todo) {
    getRealmInstance().apply {
        write {
            query<Todo>("id == $0", todo.id).find().first().apply {
                title = todo.title
                description = todo.description
            }
        }
    }.close()
}

引数で受け取ったTodoインスタンスのidに対応するデータの更新をする関数です。
writeブロックの中で、queryを使って引数で受け取っているインスタンスのidと同じidのレコードを取得し、titleとdescriptionを更新しています。

/** Delete. */
suspend fun deleteTodo(todo: Todo) {
    getRealmInstance().apply {
        write {
            delete(query<Todo>("id == $0", todo.id).find())
        }
    }.close()
}

レコードの削除を行う関数です。
引数で受け取ったTodoインスタンスと同じidのレコードをqueryを使って取得して、削除します。

以上でRepositoryの作成は完了です。

ここまでのソースコードはこちら

5. ViewModelの作成

この章では、表示するデータの管理とTodoRepositoryとのやりとりを行うViewModelのベースを作成します。

HiltによるRepositoryの注入

MainActivity.ktと同じディレクトリにMainViewModel.ktという名前のKotlinクラスファイルを作成して下さい。
MainViewModelではTodoRepositoryを使用するので、そのための設定を追加してきます。以下のようにコードを追加して下さい。

@HiltViewModel
class MainViewModel @Inject constructor(private val todoRepository: TodoRepository) : ViewModel() {
}

@HiltViewModelアノテーションを付与することで、ViewModelにも依存関係注入が行えます。@Inject constructorの引数にTodoRepositoryを指定することで、MainViewModelのインスタンス作成時にTodoRepositoryのインスタンスがインジェクトされるようになります。

Todoの追加、更新、削除処理の追加

ViewModelにはユーザーのアクション(ボタンクリックなど)に対応する関数を追加していきます。今回のアプリではTodoの追加、更新、削除ボタンのあるUIが想定されるので、それぞれのボタンが押された時の処理を追加します。また、Todoをリスト表示するため、DBからtodo全件取得する処理も追加します。

MainViewModelに以下のようにコードを追加して下さい。

@HiltViewModel
class MainViewModel @Inject constructor(private val todoRepository: TodoRepository) : ViewModel() {
    // ここより下が追加箇所
    private val _todos = MutableLiveData<List<Todo>>()
    val todos: LiveData<List<Todo>> = _todos

    private fun refreshTodos() {
        viewModelScope.launch {
            _todos.value = todoRepository.getAllTodo()
        }
    }

    fun addTodo(todo: Todo) {
        viewModelScope.launch {
            todoRepository.createTodo(todo)
            refreshTodos()
        }
    }

    fun updateTodo(todo: Todo) {
        viewModelScope.launch {
            todoRepository.createTodo(todo)
            refreshTodos()
        }
    }

    fun deleteTodo(todo: Todo) {
        viewModelScope.launch {
            todoRepository.deleteTodo(todo)
            refreshTodos()
        }
    }
}

要点の説明

private val _todos = MutableLiveData<List<Todo>>()
val todo: LiveData<List<Todo>> = _todos

_todosにはTodo全件のデータが入ります。このライブデータの変更をView側で監視することで、TodoリストUIの更新をします。

private fun refreshTodos() {
    viewModelScope.launch {
        _todos.value = todoRepository.getAllTodo()
    }
}

refreshTodos関数はDBからTodoデータ全件を取得して、_todosを更新します。それによって、UIにデータの変更が通知されます。この関数をTodoの追加、更新、削除処理の後に呼び出すことによって、_todosのデータと画面に表示されているTodoリストを最新の状態に更新します。

ViewModelの作成は一旦ここまでで完了です。Viewとの繋ぎ込みを行うときに少し調整を行います。

ここまでのソースコードはこちら

6. Viewの作成 - Todo追加機能

この続きをみるには

この続き: 13,158文字 / 画像4枚

Android開発初心者~初級者向け JetpackComposeの初心者から中級を目指すためのチュートリアル集

この記事が気に入ったら、サポートをしてみませんか?
気軽にクリエイターの支援と、記事のオススメができます!
まっこり
東京工業大学▶︎Android & iOS開発者 AndroidやiOS開発のチュートリアル販売, 個人開発報告, テキトウな学習記録を投稿してます 好きな言語: Kotlin, TypeScript