データの一元管理を行うためデータモジュール(:core:data)を作成します。
また、データの永続化はRoomで行います。
バージョンカタログファイルにRoomのバージョン定義を追記します。
[versions]
:
room = "2.8.2" # https://mvnrepository.com/artifact/androidx.room/room-runtime
[libraries]
:
room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
追記後、『Sync Now』で内容をプロジェクトに反映させます。
データを管理するモジュール(:core:data)を作成します。
uiモジュールの作成と同様に、トップディレクトリで:core:dataを作成し、javaディレクトリ名の変更を行います。
:core:dataモジュールのbuild.gradle.ktsにLibraryConfigurePluginを適用します。
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
id("build.logic.library.configure")
}
android {
namespace = "jp.co.progress_llc.portal.core.data"
compileSdk {
version = release(36)
}
defaultConfig {
minSdk = 28
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
implementation(libs.room.runtime)
implementation(libs.room.ktx)
ksp(libs.room.compiler)
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)
適用後、『Sync Now』で内容をプロジェクトに反映させます。
エンティティ(テーブル)定義クラスを作成します。
経路データRoute.ktを作成します。
package jp.co.progress_llc.portal.core.data import androidx.room.Entity import androidx.room.PrimaryKey import androidx.room.ColumnInfo @Entity(tableName = "route") data class Route( @PrimaryKey(autoGenerate = true) val id: Int = 0, @ColumnInfo(name = "order_index") val orderIndex: Int = 0, // 路線の並び順 @ColumnInfo(name = "from_station") val fromStation: String = "", // 発駅コード @ColumnInfo(name = "to_station") val toStation: String = "", // 着駅コード @ColumnInfo(name = "via_station") val viaStation: String = "" // 経由駅コード )
データベースにアクセスするためのインターフェースオブジェクト(Data Access Object)を作成します。
package jp.co.progress_llc.portal.core.data
import androidx.room.Dao
import androidx.room.Query
import androidx.room.Insert
import androidx.room.OnConflictStrategy
@Dao
interface RouteDao {
@Query("SELECT * FROM route ORDER BY order_index")
suspend fun getAllRoute(): List<Route>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertRoute(route: Route): Long
}
アプリケーションのビジネスロジックで直接DAOを触らせると、将来データベースをRoom以外にしたとき(例えばJOSNファイル)にビジネスロジックを修正する必要があります。
ビジネスロジック側からデータソースを意識させないようにするため、リポジトリを作成してDAOを隠蔽します。
※今回のアプリケーション規模では冗長です。
RouteDao用に、repositoryディレクトリ(新規に作成)内にリポジトリインターフェースRouteRepository.ktを作成します。
package jp.co.progress_llc.portal.core.data.repository
import jp.co.progress_llc.portal.core.data.Route
/**
* Routeのリポジトリインターフェース
*/
interface RouteRepository {
/**
* Routeを全件取得
*/
suspend fun getAllRoute(): List<Route>
/**
* Routeレコードを追加
*/
suspend fun insertRoute(route: Route): Long
}
リポジトリインターフェースの実装RouteRepositoryImpl.ktを作成します。
package jp.co.progress_llc.portal.core.data.repository
import javax.inject.Singleton
import javax.inject.Inject
import jp.co.progress_llc.portal.core.data.Route
import jp.co.progress_llc.portal.core.data.RouteDao
/**
* Routeのリポジトリ実装
*/
@Singleton
class RouteRepositoryImpl @Inject constructor(
private val routeDao: RouteDao
) : RouteRepository {
override suspend fun getAllRoute(): List<Route> {
return routeDao.getAllRoute()
}
override suspend fun insertRoute(route: Route): Long {
return routeDao.insertRoute(route)
}
}
:core:dataにAppDatabaseクラスを作成します。
package jp.co.progress_llc.portal.core.data
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import android.content.Context
@Database(
entities = [Route::class],
version = 1,
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
abstract fun RouteDao(): RouteDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database"
)
.build()
INSTANCE = instance
instance
}
}
}
}
RoomのDatabaseとDAO、およびリポジトリをDI(Hilt)で注入可能にするため、DI(Hilt)モジュールを作成します。
DI(Hilt)モジュールは、diディレクトリ内に作成します。
package jp.co.progress_llc.portal.core.data.di
import javax.inject.Singleton
import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import jp.co.progress_llc.portal.core.data.AppDatabase
import jp.co.progress_llc.portal.core.data.RouteDao
import jp.co.progress_llc.portal.core.data.repository.RouteRepository
import jp.co.progress_llc.portal.core.data.repository.RouteRepositoryImpl
@Module
@InstallIn(SingletonComponent::class)
object DataModule {
@Provides
@Singleton
fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
return AppDatabase.getDatabase(context)
}
@Provides
fun provideRouteDao(database: AppDatabase): RouteDao {
return database.RouteDao()
}
@Provides
@Singleton
fun provideRouteRepository(
routeDao: RouteDao
): RouteRepository {
return RouteRepositoryImpl(routeDao)
}
}