【JetpackCompose】Realmを使ってTodoアプリを作ろう
こんにちはマッコリです。今回のnoteはAndroidアプリ開発のチュートリアル記事です。Realmを使ってTodoアプリを作っていきます。Realmを使ったCRUDの実装方法、MVVM+Rアーキテクチャの実装方法、DIライブラリのHiltの使い方を学べる内容となっています。
完成版のソースコードはGitHubにアップロードしてあります。必要な場合は参照ください。
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追加機能
この記事が気に入ったらサポートをしてみませんか?