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
)
}
}
}
関連記事:
