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)を作ります。
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)を持ちます。
data class Player( val id: Int = -1, val name: String = "XXXXXXXX", val score: Int = 0 )
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)を行う拡張関数です。
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をリスト表示します。
また、「+ボタン」でプレーヤー(ランダムに作成)をデータベースへ追加します。
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()) } }
@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 ) } } }
関連記事: