ページの作成
親となるページを選択してください。
親ページに紐づくページを子ページといいます。
例: 親=スポーツ, 子1=サッカー, 子2=野球
子ページを親ページとして更に子ページを作成することも可能です。
例: 親=サッカー, 子=サッカーのルール
親ページはいつでも変更することが可能なのでとりあえず作ってみましょう!
| この記事の要点 |
|
Android のDB選択肢
| 方式 | 用途 | 現状 |
|---|---|---|
| SQLiteOpenHelper(生 SQL) | 古いアプリの保守 | 非推奨(直接利用は避ける) |
| Room (Jetpack) | ローカル永続化のデファクト | 推奨 |
| SQLDelight | KMP(マルチプラットフォーム) | KMP プロジェクト推奨 |
| SharedPreferences / DataStore | キー・バリュー設定 | 軽量設定用 |
| Firebase Realtime DB / Firestore | クラウド同期 / リアルタイム | 同期が必要なときに |
古いやり方: SQLiteOpenHelper
API レベル 1 から存在する旧来の方式。直接使うことはもう推奨されませんが、既存コードの保守で出会います。
class DBHelper(context: Context) : SQLiteOpenHelper(context, "app.db", null, 1) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL("""
CREATE TABLE users(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER
)
""".trimIndent())
}
override fun onUpgrade(db: SQLiteDatabase, oldVer: Int, newVer: Int) {
db.execSQL("DROP TABLE IF EXISTS users")
onCreate(db)
}
}
// 利用
val helper = DBHelper(context)
val db = helper.writableDatabase
val cv = ContentValues().apply {
put("name", "taro")
put("age", 30)
}
db.insert("users", null, cv)
val cursor = db.rawQuery("SELECT id, name, age FROM users", null)
while (cursor.moveToNext()) {
val id = cursor.getInt(0)
val name = cursor.getString(1)
}
cursor.close()
問題点: 文字列SQL、手動Cursor処理、メインスレッドブロックリスク。
Room の基本(推奨)
Gradle 依存追加
// build.gradle.kts (app)
dependencies {
val roomVer = "2.6.1"
implementation("androidx.room:room-runtime:$roomVer")
implementation("androidx.room:room-ktx:$roomVer") // Coroutine 拡張
ksp("androidx.room:room-compiler:$roomVer") // または kapt
}
plugins {
id("com.google.devtools.ksp")
}
Entity / DAO / Database
// Entity = テーブル
@Entity(tableName = "users")
data class UserEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val name: String,
val age: Int,
val createdAt: Long = System.currentTimeMillis()
)
// DAO = データアクセス
@Dao
interface UserDao {
@Query("SELECT * FROM users ORDER BY id DESC")
fun observeAll(): Flow<List<UserEntity>> // Flow でリアクティブ購読
@Query("SELECT * FROM users WHERE id = :id")
suspend fun findById(id: Long): UserEntity?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(user: UserEntity): Long
@Update suspend fun update(user: UserEntity)
@Delete suspend fun delete(user: UserEntity)
@Query("DELETE FROM users")
suspend fun clear()
}
// Database
@Database(entities = [UserEntity::class], version = 1, exportSchema = true)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
// インスタンス化(DI 使うのが良い)
val db = Room.databaseBuilder(context, AppDatabase::class.java, "app.db").build()
val dao = db.userDao()
Coroutine / Flow で使う
class UserRepository(private val dao: UserDao) {
val users: Flow<List<UserEntity>> = dao.observeAll()
suspend fun add(name: String, age: Int) {
dao.upsert(UserEntity(name = name, age = age))
}
}
// ViewModel
class UserViewModel(repo: UserRepository) : ViewModel() {
val users = repo.users.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
fun addUser(name: String, age: Int) = viewModelScope.launch {
repo.add(name, age)
}
}
// Compose 側
@Composable
fun UserScreen(vm: UserViewModel = hiltViewModel()) {
val users by vm.users.collectAsState()
LazyColumn { items(users) { Text(it.name) } }
}
マイグレーション
// version 1 → 2 で age を nullable に
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE users ADD COLUMN bio TEXT")
}
}
Room.databaseBuilder(ctx, AppDatabase::class.java, "app.db")
.addMigrations(MIGRATION_1_2)
.fallbackToDestructiveMigration() // 開発中の最終手段(既存データ消去)
.build()
TypeConverters(複合型のシリアライズ)
class Converters {
@TypeConverter fun fromDate(d: Date?): Long? = d?.time
@TypeConverter fun toDate(t: Long?): Date? = t?.let(::Date)
@TypeConverter fun fromList(xs: List<String>?): String? = xs?.joinToString("|")
@TypeConverter fun toList(s: String?): List<String>? = s?.split("|")
}
@Database(entities = [UserEntity::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() { ... }
初期データの投入(コールバック)
Room.databaseBuilder(ctx, AppDatabase::class.java, "app.db")
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
db.execSQL("INSERT INTO users(name, age) VALUES ('admin', 30)")
db.execSQL("INSERT INTO users(name, age) VALUES ('guest', 20)")
}
})
.build()
SQLDelight(KMP)
iOS と共通コードでDB操作したい場合、SQLDelight が有力です。.sq ファイルに SQL を書き、コンパイル時に型安全なKotlin APIを生成します。
-- src/commonMain/sqldelight/com/example/User.sq
CREATE TABLE userEntity (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER NOT NULL
);
selectAll:
SELECT * FROM userEntity ORDER BY id DESC;
insertUser:
INSERT INTO userEntity(name, age) VALUES (?, ?);// 生成された型安全API
val users: List<UserEntity> = database.userQueries.selectAll().executeAsList()
database.userQueries.insertUser("taro", 30)
Firebase: Realtime Database / Firestore
// Firestore(NoSQL ドキュメントDB)
val db = Firebase.firestore
// 書き込み
db.collection("users").document("taro").set(mapOf(
"name" to "taro",
"age" to 30
))
// リアルタイム購読
db.collection("users")
.addSnapshotListener { snap, err ->
snap?.documents?.forEach { doc ->
Log.d("Firestore", "${doc.id} => ${doc.data}")
}
}
// オフライン対応はデフォルト ON(端末キャッシュ)
| 選択肢 | 強み | 弱み |
|---|---|---|
| Realtime DB | シンプル、リアルタイム強い | JSONツリーで設計難 |
| Firestore | クエリ柔軟、スケール、サブコレクション | 料金体系が複雑 |
| Room(ローカルのみ) | 同期不要なら最強 | クラウド同期は別途 |
FAQ
Q: SQLiteOpenHelper から Room へ移行したい
A: 既存スキーマを Room の Entity で再現し、createFromAsset や手動 Migration で取り込みます。バージョン継承に注意。
Q: メインスレッドで Room を呼ぶとクラッシュする
A: Room はメインスレッド呼び出しを禁止しています。suspend fun + viewModelScope or allowMainThreadQueries()(テストのみ)を使ってください。
Q: 暗号化したい
A: SQLCipher for Room 拡張で透過的に暗号化可能。鍵管理は AndroidKeyStore 推奨。
ページの作成
親となるページを選択してください。
親ページに紐づくページを子ページといいます。
例: 親=スポーツ, 子1=サッカー, 子2=野球
子ページを親ページとして更に子ページを作成することも可能です。
例: 親=サッカー, 子=サッカーのルール
親ページはいつでも変更することが可能なのでとりあえず作ってみましょう!
子ページ
子ページはありません
同階層のページ
- リソース (Android アプリケーション)
- アクティビティ (Android アプリケーション)
- メッセージの表示
- データベースの操作 (Android アプリケーション)
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 Spring Frameworkのアノテーション一覧
- 7 3Dグラフィックスとは|モデリング/レンダリング/主要ソフトウェア (Blender / Maya)
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- PHPでのファイルアップロード方法|multipart/form-dataと$_FILES 2026-08-07 17:15:57
- PHP $_SERVERとは|DOCUMENT_ROOT・PHP_SELFなど主な要素 2026-08-07 17:15:57
- Scratchのアカウントの作り方|メールアドレスとユーザー名の登録手順 2026-08-07 17:15:57
- djangoにおけるMVCアプリケーション実装例 2026-08-07 17:15:57
- Bluetoothとは|仕組み・BLEとClassicの違い・用途 2026-08-07 17:15:57
- Spring Bootのテスト入門|@SpringBootTestとJUnitでの単体・結合テスト 2026-08-07 17:15:57
- Java とは?言語仕様・JVM・主要フレームワーク一覧 2026-08-07 17:15:56
- インストール方法(Windows版) 2026-08-07 17:15:56
- APIキー取得方法 2026-08-07 17:15:56
- 【PHPフレームワーク】Laravelの使い方 2026-08-07 17:15:56
- Scratch 作品の公開方法 2026-08-07 17:15:56
- Google OAuth 2.0 認証の実装方法 2026-08-07 17:15:56
- データベースとは?RDB / NoSQL / SQL の基礎 2026-08-07 17:15:56
- インストール(eclipseプラグイン) 2026-08-07 17:15:56
- Spring Frameworkの使い方 2026-08-07 17:15:56
コメントを削除してもよろしいでしょうか?