SQLite APIでデータベースを構築

投稿日:  更新日:

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を発行し、データベースにアクセスします。

SQLiteのプログラム構成

データベース構築の方法が分かるように、簡単なサンプルを示します。

スポンサーリンク

サンプルデータベース

サンプルはゲームの情報を格納するデータベース(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);
    }
	    :
}
引数概要
contextContextコンストラクタ
nameStringデータベース名(ファイル名)
factoryCursorFactory
versionintデータベースのバージョン
前バージョンと異なる値の時、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概要
insertINSERT挿入
querySELECT検索、返答はCursorクラス
deleteDELETE削除
updateREPLACE更新
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をリスト表示します。

また、「+ボタン」でプレーヤー(ランダムに作成)をデータベースへ追加します。

補助関数(randomPlayer)
private fun randomPlayer(): Player {
    val _name = randomName()
    val _score = randomScore(6)
    return Player(name = _name, score = _score)
}
private fun randomScore(length: Int): Int {
    return (Math.random() * 10.0.pow(length)).toInt()
}
private fun randomName(): String {
    val _charPos = (Math.random() * 26).toInt()
    return "%s-San".format('A' + _charPos)
}
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
            )
        }
    }
}
スポンサーリンク

関連記事:

Androidは標準でSQLiteというRDBMS(Relational Database Management System)を扱います。 SQLiteは少し個性的なデータベースです。 データベース全般の説明を通して、SQLiteと他の違いをまとめます。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 2     Kotlin 2.0.0 ...
データベースを扱うアプリケーションの開発で、動作を確認するためにデータベースの閲覧が出来ると便利です。 データベースは、マネージメントシステムを介してアクセスされるため、プログラムからブラックボックスに見えます。 ですので、データベース内部の問題は見つけ難いです。 デバックおいてデータベースの閲覧が出来れば、内部の問題を用意に確認できます。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 2 ...
Androidが標準で扱うデータベースはSQLiteです。 ※詳細は「Androidで扱うデータベース」を参照 データベースを構築する方法は「SQLite API(Android SDK)」と「Room(Android Jetpack)」の2通りがあります。 今回は「Room」でデータベースを構築する方法を紹介します。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 2     Kotlin 2.0.0     androidx.sqlite:sqlite:2.4.0     androidx.room:room-*:2.6.1 ...
アプリケーションが始めて起動した直後に、データの事前取り込み(初期状態の設定)を必要とする場合があります。 事前取り込みの方法ついて、まとめました。 SQLite API(Android SDK)でデータベースを構築した場合です。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 2     Kotlin 2.0.0 ※サンプルの全体像は「SQLite APIでデータベースを構築」を参照 ...
SQLite API(Android SDK)でデータベースを構築した場合、アプリのパフォーマンスを考えると、識別子の共有が必要になってきます。 この点について、まとめました。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 2     Kotlin 2.0.0 ※サンプルの全体像は「SQLite APIでデータベースを構築」を参照 ...
SQLite API(Android SDK)でデータベースを構築した場合、データベースへアクセスする際に、データベースのOpenとCloseという処理を伴います。 この点について、まとめました。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 2     Kotlin 2.0.0 ※サンプルの全体像は「SQLite APIでデータベースを構築」を参照 ...
SQLite API(Android SDK)でデータベースを構築した場合、アクセスはスレッドセーフになります。 この点について、まとめました。 ※環境:Android Studio Ladybug | 2024.2.1 Patch 2     Kotlin 2.0.0 ※サンプルの全体像は「SQLite APIでデータベースを構築」を参照 ...
スポンサーリンク