ページの作成
親となるページを選択してください。
親ページに紐づくページを子ページといいます。
例: 親=スポーツ, 子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 と SSR/Node.js 対応
- 4 YouTube Data API v3 エラー一覧|403/400/404 の主要原因と切り分け
- 5 Spring Frameworkのアノテーション一覧
- 6 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 7 3Dグラフィックスとは|モデリング/レンダリング/主要ソフトウェア (Blender / Maya)
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- IPv6とは|128bitアドレス・コロン16進表記/::省略・リンクローカル・SLAAC・デュアルスタック NEW 2026-06-22 12:34:44
- MAC アドレスフィルタリングの仕組みと限界 | ネットワーク入門 NEW 2026-06-22 12:19:10
- VPNとは|暗号トンネル・サイト間/リモートアクセス・IPsec/SSL-VPN/WireGuardを解説 NEW 2026-06-22 12:19:10
- WebSocket とは 全二重リアルタイム通信 ws/wss | ネットワーク入門 NEW 2026-06-22 12:17:25
- HTTP/2 とは 多重化・HPACK・バイナリフレーム | ネットワーク入門 NEW 2026-06-22 12:17:25
- Web通信プロトコル入門 HTTP/2・HTTP/3・WebSocket・gRPC・WebRTC | ネットワーク入門 NEW 2026-06-22 12:17:25
- gRPC とは HTTP/2 + Protocol Buffers の高速 RPC | ネットワーク入門 NEW 2026-06-22 12:17:25
- HTTP/3 (QUIC) とは UDP ベースの低遅延 Web 通信 | ネットワーク入門 NEW 2026-06-22 12:17:25
- WebRTC とは ブラウザ間 P2P の音声・映像・データ通信 | ネットワーク入門 NEW 2026-06-22 12:17:25
- TLS/SSLの仕組み|ハンドシェイク・暗号スイート・前方秘匿性・証明書検証をわかりやすく解説 NEW 2026-06-22 12:17:24
- ファイアウォールとは|パケットフィルタ・ステートフル・DMZ・次世代FW(L4/L7)を解説 NEW 2026-06-22 12:17:24
- iptables/nftablesとは|テーブル・チェーン・ルール例・永続化をLinux視点で解説 NEW 2026-06-22 12:17:24
- HAProxy とは frontend/backend と設定例 | ネットワーク入門 NEW 2026-06-22 12:17:24
- CDN とは エッジキャッシュ・TTL・Cloudflare/CloudFront | ネットワーク入門 NEW 2026-06-22 12:17:24
- 証明書と認証局(CA)とは|X.509・信頼チェーン・DV/OV/EV・失効(CRL/OCSP)を解説 NEW 2026-06-22 12:17:24
コメントを削除してもよろしいでしょうか?