La base de dades que utilitza Android per defecte és Sqlite.

Introducció

Projecte de suport: https://gitlab.com/xtec/android/Sqlite

Entorn de treball

Instal.la Android Studio:

> scoop install android-studio android-clt

Crea un projecte tal com s'explica a ...

Per projectes Android, el plugin SQLDelight Gradle selecciona de manera automàtica la versió del dialecte SQLite en base a la configuració minSdkVersion del teu projecte.

Mira en aquesta llista quina versió de Sqlite porta per defecte cada nivell del Android SDK.

build.gradle.kts

Primer, aplica el plugin gradle al fitxer app/build.gradle.kts file:

plugins {
    ...
    id("app.cash.sqldelight") version "2.0.2"
}

dependencies {
    ...
    implementation("app.cash.sqldelight:android-driver:2.0.2")
}

Sincronitza la configuració per tal que es descarreguin les dependències.

Ja pots configurar la base de dades en el fitxer app/build.gradle.kts.

sqldelight {
    databases {
        create("Database") { // The name of the database
            packageName.set("dev.xtec.data") // Package name used for the database class.
            // Additional configuration options if required
            // https://sqldelight.github.io/sqldelight/2.0.2/android_sqlite/gradle/
        }
    }
}

Aplicació

SQL

Crea els fitxers sq corresponents a aquest model de dades en el directori app/src/main/sqldelight/...:

classDiagram
direction LR

class Team { 
  id integer primary key autoincrement
  name text not null unique
  location text not null
}

class Player {
  id integer primary key autoincrement
  name text not null
  country text not null
}

Player --> Team : team

Team.sq

CREATE TABLE IF NOT EXISTS Team (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL UNIQUE,
    location TEXT NOT NULL
);

insert:
INSERT INTO Team(name,location) VALUES(?,?);

select:
SELECT * FROM Team LIMIT :limit OFFSET :offset;

selectCount:
SELECT count(*) FROM Team;

Player.sq

CREATE TABLE IF NOT EXISTS Player (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    country TEXT NOT NULL,
    team INTEGER NOT NULL,
    FOREIGN KEY (team) REFERENCES Team(id)
);

insert:
INSERT INTO Player(name,country,team) VALUES (?,?, ?);

Si no es genera de manera automàtica el codi corresponent, executa la tasca generateSqlInterface des del terminal.

> .\gradlew generateSqlInterface

Base de dades

Modifica el contingut del fitxer MainActivity.kt:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val databaseName = "soccer"
        val production = false

        val sqlDriver: SqlDriver =
            AndroidSqliteDriver(Database.Schema, this.applicationContext, databaseName)
        val database = Database(sqlDriver)

        if (production) {
            Database.Schema.create(sqlDriver)
        } else {
            this.applicationContext.deleteDatabase(databaseName)
            Database.Schema.create(sqlDriver)
            insertDevelopmentData(database)
        }

        val teams = database.teamQueries.select(Long.MAX_VALUE, 0).executeAsList()

        setContent {
            Text("TODO")
        }
    }
}

fun insertDevelopmentData(database: Database) {

    listOf(
        Team(1, "Arsenal", "London"),
        Team(2, "Aston Villa", "Birmingham")
    ).forEach() { team ->
        database.teamQueries.insert(team.name, team.location)
    }

    listOf(
        Player(1, "David Raya", "Spain", 1),
        Player(2, "William Saliba", "France", 1),
        Player(3, "Kieran Tiernay", "Scotland", 1)
    ).forEach { player ->
        database.playerQueries.insert(player.name, player.country, player.team)
    }
}

Executa l'aplicació en un dispositiu.

Obre la base de dades en el Database Inspector:

  1. Des de la barra del menú, selecciona View > Tool Windows > App Inspection.
  2. Selecciona el Database Inspector tab.
  3. Les bases de dades de l'aplicació que s'està executant apareixen en el panell Databases
  4. Expandeix els nodes de la base de dades soccer.

Fes doble-clic a Player per veure tots els jugadors:

Recorda que la base de dades està en el dispositiu, no en el teu Windows!

Tens més informació en aquest enllaç: Debug your database with the Database Inspector

Consultar dades

A continuació has de crear una "Screen" per mostrar tots els equips que estan a la base de dades.

1.- Crea les funcions "composables" TeamView i TeamViewList.

    @Composable
fun TeamView(team: Team) {
    
    // ...
}

@Composable
fun TeamListView(teams: List<Team>) {
    
    // ...
}

2.- Modifica el setContent de la classe MainActivity:

class MainActivity : ComponentActivity() {

        // ...

        val teams = db.teamQueries.select(Long.MAX_VALUE, 0).executeAsList()

        setContent {
            TeamListView(teams)
        }
}

3.- Crea la funció "composable" TeamListScreen que té la base de dades com a paràmetre:

@Composable
fun TeamListScreen(database: Database) {
    val teams = database.teamQueries.select(Long.MAX_VALUE, 0).executeAsList()
    TeamListView(teams)
}

4.- Modifica el setContent de la classe MainActivity:

class MainActivity : ComponentActivity() {

        // ...

        setContent {
            TeamListScreen(database)
        }
}

Afegir dades

A continuació has de crear una "Screen" per insertar un nou equip a la base de dades

1.- Completa la funció "composable" TeamInsert per afegir un equip nou.

Has d'afegir un TextField per location!

@Composable
fun TeamInsert(handle: (Team) -> Unit) {

    val team = remember { mutableStateOf(Team(0,"","")) }

    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,
        modifier = Modifier
            .fillMaxSize()
            .padding(10.dp)
    ) {
        Text(
            text = "Team",
            style = MaterialTheme.typography.titleLarge,
            color = MaterialTheme.colorScheme.primary
        )
        Spacer(modifier = Modifier.height(10.dp))
        TextField(
            value = team.value.name,
            placeholder = { Text("name") },
            onValueChange = { team.value = team.value.copy(name = it) })

        Button(
            onClick = {
                message.value = handle(team.value)
            },
            modifier = Modifier.padding(vertical = 40.dp)
        ) {
            Text("Insert")
        }
    }
}

@Preview
@Composable
fun TeamInsertPreview() {
    TeamInsert {
        println(it)
    }
}

2.- Crea la funció "composable" TeamInsertScreen.

La funció ha d'insertar el registre a la base de dades.

@Composable
fun TeamInsertScreen(database: Database) {
    TeamInsert { team ->
        database.teamQueries.insert(team.name,team.location)
    }
}

3.- Verifica que pots insertar equips a la base de dades.

4.- Si insertes un equip amb un nom que ja existeix l'aplicació fa "crash".

El motiu és que el nom de l'equip ha de ser únic en la taula Team.

Modifica les funcion TeamInsert i TeamInsertScreen perqué gestioni l'excepció, i mostri a l'usuari un missatge d'error dient que l'equip ja existeix.

@Composable
fun TeamInsert(handle: (Team) -> String) {

    val team = remember { mutableStateOf(Team(0, "", "")) }
    val message = remember { mutableStateOf("") }

    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center,
        modifier = Modifier
            .fillMaxSize()
            .padding(10.dp)
    ) {
        Text(
            text = "Team",
            style = MaterialTheme.typography.titleLarge,
            color = MaterialTheme.colorScheme.primary
        )
        Spacer(modifier = Modifier.height(10.dp))
        TextField(
            value = team.value.name,
            placeholder = { Text("name") },
            onValueChange = { team.value = team.value.copy(name = it) })
        Spacer(modifier = Modifier.height(10.dp))
        TextField(
            value = team.value.location,
            placeholder = { Text("location") },
            onValueChange = { team.value = team.value.copy(location = it) })

        Button(
            onClick = {
                message.value = handle(team.value)
            },
            modifier = Modifier.padding(vertical = 40.dp)
        ) {
            Text("Insert")
        }
        Text(
            text = message.value,
            modifier = Modifier.padding(10.dp),
            color = MaterialTheme.colorScheme.error
        )
    }
}

@Preview
@Composable
fun TeamInsertPreview() {
    // Bournemouth, Brentford
    TeamInsert {
        "Error"
    }
}

@Composable
fun TeamInsertScreen(database: Database) {

    TeamInsert { team ->
        try {
            database.teamQueries.insert(team.name, team.location)
            "Fet!"
        } catch (e: Exception) {
            e.message ?: "Error al insertar el registre"
        }
    }
}