Androidが標準で扱うデータベースはSQLiteです。
※詳細は「Androidで扱うデータベース」を参照
データベースを構築する方法は「SQLite API(Android SDK)」と「Room(Android Jetpack)」の2通りがあります。
今回は「SQLite API」でデータベースを構築する方法を紹介します。
※環境:Android Studio Ladybug | 2024.2.1 Patch 2
Kotlin 2.0.0
目次 [非表示]
SQLite APIとは
「SQLite API」はAndroid SDKで提供されているAPIです。API Level 1の頃からあります。
データベースと接続を確立(Open)した時に得られる識別子(SQLiteDatabase)を介して、マネージメントシステムへSQLを発行し、データベースにアクセスします。
データベース構築の方法が分かるように、簡単なサンプルを示します。
サンプルデータベース
サンプルはゲームの情報を格納するデータベース(Game.db)です。
データベースはplayerテーブルを持ち、columnにプレイヤーの「id, name, score」が並び、rowにプレイヤーのデータが登録された順に並びます。
idはプライマリーキーで、プレイヤーを一意に識別するための番号です。
SQLiteOpenHelperの継承
データベース毎にSQLiteOpenHelperを継承したクラス(GameDb)を作ります。
1 2 3 4 5 6 7 8 | public abstract class SQLiteOpenHelper implements AutoCloseable { : public SQLiteOpenHelper( @Nullable Context context, @Nullable String name, @Nullable CursorFactory factory, int version) { this (context, name, factory, version, null ); } : } |
引数 | 概要 | |
---|---|---|
context | Context | コンストラクタ |
name | String | データベース名(ファイル名) |
factory | CursorFactory | |
version | int | データベースのバージョン 前バージョンと異なる値の時、onUpgradeをコールバック |
このSQLiteOpenHelper(GameDb)のインスタンスが、識別子(SQLiteDatabase)を持ちます。
1 2 3 4 5 | data class Player( val id: Int = - 1 , val name: String = "XXXXXXXX" , val score: Int = 0 ) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | class GameDb(context: Context, version: Int = DB_VERSION) : SQLiteOpenHelper(context, DB_NAME, null , version) { companion object { const val DB_NAME = "Game.db" const val DB_VERSION = 1 } val tableName = "player" // // Table Parameter // val idField = "id" val nameField = "name" val scoreField = "score" val columns = arrayOf(idField, nameField, scoreField) val idIndex = columns.indexOf(idField) val nameIndex = columns.indexOf(nameField) val scoreIndex = columns.indexOf(scoreField) // // SQL Command // private val createTable = "" " CREATE TABLE ${tableName} ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, score INTEGER NOT NULL ) "" ".trimIndent() private val dropTable = "DROP TABLE IF EXISTS ${tableName}" // // Lifecycle operation // override fun onCreate(db: SQLiteDatabase?) { db?.execSQL(createTable) } override fun onUpgrade(db: SQLiteDatabase?, oldVer: Int, newVer: Int) { db?.execSQL(dropTable) onCreate(db) } // // Helper function (PlayerDbのインスタンス内に限定) // fun Cursor.toPlayerList(): List<Player> { val _list = ArrayList<Player>(count) try { if (moveToFirst()) { do { val _id = getInt(idIndex) val _name = getString(nameIndex) val _score = getInt(scoreIndex) _list.add(Player(_id, _name, _score)) } while (moveToNext()) } } finally { close() } // closeは必須 return _list } } |
抽象関数のonCreate/onUpgradeは、実装が必須です。
onCreate
onCreateは、SQLiteOpenHelper(GameDb)インスタンスから、始めて識別子(SQLiteDatabase)が取り出された時に呼び出されます。つまり、データベースの利用を開始した時に一度だけ呼ばれます。
この関数で、SQLコマンド「CREATE TABLE」を用いて、テーブルの新規作成を行います。
onUpgrade
onUpgradeは、バージョン番号の異なるSQLiteOpenHelper(GameDb)インスタンスから、始めて識別子(SQLiteDatabase)が取り出された時に呼び出されます。つまり、バージョンを更新した時に一度だけ呼ばれます。
この関数で、バージョン変更に伴ったデータベースの構成を書き換えます。
ちなみにサンプルは、SQLコマンド「DROP TABLE」を用いて、データベースの削除と再作成を行っています。
データベースへアクセス
SQLiteOpenHelper#writableDatabaseは識別子(SQLiteDatabase)を保持するプロパティです。
SQLiteDatabaseは、マネージメントシステムへSQL(問い合わせ)を発行するためのアクセス関数が実装されています。
これらを使って、データベースへアクセスします。
アクセス関数 (SQLiteDatabase#XXX) | 対応するSQL | 概要 |
---|---|---|
insert | INSERT | 挿入 |
query | SELECT | 検索、返答はCursorクラス |
delete | DELETE | 削除 |
update | REPLACE | 更新 |
exeSQL | 全SQLコマンド | 引数で指定した文字列がそのままSQLになる |
※代表的なアクセス関数のみ、派生した関数が存在する |
以下は、登録(INSERT)と検索(SELECT)を行う拡張関数です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | fun GameDb.insert(player: Player) { // プレーヤーの追加登録 val values = ContentValues().apply { put(nameField, player.name) put(scoreField, player.score) } writableDatabase.insert(tableName, null , values) } fun GameDb.fetchTop10(): List<Player> { // スコアトップ10の検索 return writableDatabase.query( tableName, // テーブル名 columns, // 取得対象のカラムリスト null , // 検索の条件 null , // 検索の値 null , // グループ化関連 null , // 〃 "${scoreField} DESC" , // ソート対象のカラム(降順) "10" // 取得する行数 ).toPlayerList() } |
サンプルの実行
サンプルはスコアランキングのトップ10をリスト表示します。
また、「+ボタン」でプレーヤー(ランダムに作成)をデータベースへ追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | class MainActivity : ComponentActivity() { private lateinit var gameDb: GameDb private lateinit var playerFlow: MutableStateFlow<List<Player>> override fun onCreate(savedInstanceState: Bundle?) { super .onCreate(savedInstanceState) enableEdgeToEdge() setContent { GamebTheme { Scaffold( modifier = Modifier.fillMaxSize(), floatingActionButton = { FloatingActionButton( onClick = { gameDb.insert(randomPlayer()) playerFlow.value = gameDb.fetchTop10() }, modifier = Modifier.padding( 15 .dp) ) { Icon(Icons.Default.Add, contentDescription = "Add" ) } } ) { innerPadding -> Box( modifier = Modifier.padding(innerPadding).fillMaxSize(), contentAlignment = Alignment.Center ) { RankingPanel(playerFlow.asStateFlow()) } } } } gameDb = (application as MyApplication).gameDb playerFlow = MutableStateFlow<List<Player>>(playerDb.fetchTop10()) } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Composable fun RankingPanel(playerFlow: StateFlow<List<Player>>) { val _players = playerFlow.collectAsState().value LazyColumn { items(items = _players, key = { it.id }) { Text( text = "%-8s %06d" .format(it.name, it.score), fontSize = 20 .sp, fontFamily = FontFamily.Monospace, fontWeight = FontWeight.Bold ) } } } |
関連記事: