import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.room.*
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import androidx.room.Room
@Entity
data class User(
@PrimaryKey val id: Int,
val name: String,
val age: Int = 0 // Added in version 2 with default 0
)
@Dao
interface UserDao {
@Query("SELECT * FROM User")
fun getAll(): List<User>
@Insert
fun insertAll(vararg users: User)
}
@Database(entities = [User::class], version = 2)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
class MainActivity : ComponentActivity() {
private lateinit var db: AppDatabase
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE User ADD COLUMN age INTEGER NOT NULL DEFAULT 0")
}
}
db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "user-db")
.addMigrations(MIGRATION_1_2)
.fallbackToDestructiveMigration() // For first run
.build()
setContent {
var migrationStatus by remember { mutableStateOf("Not started") }
var users by remember { mutableStateOf(listOf<User>()) }
fun loadUsers() {
// Load users from DB on background thread
Thread {
val loadedUsers = db.userDao().getAll()
runOnUiThread {
users = loadedUsers
}
}.start()
}
LaunchedEffect(Unit) {
// Insert initial data if empty
Thread {
if (db.userDao().getAll().isEmpty()) {
db.userDao().insertAll(
User(1, "Alice"),
User(2, "Bob"),
User(3, "Carol")
)
}
loadUsers()
}.start()
}
Column(modifier = Modifier.padding(16.dp)) {
Text("User List", style = MaterialTheme.typography.titleLarge)
users.forEach { user ->
Text("${user.id}. ${user.name} - Age: ${user.age}")
}
Text("Migration Status: $migrationStatus", modifier = Modifier.padding(top = 16.dp))
Button(onClick = {
migrationStatus = "Migrating..."
Thread {
// Close old DB and reopen with migration
db.close()
db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "user-db")
.addMigrations(MIGRATION_1_2)
.build()
loadUsers()
runOnUiThread {
migrationStatus = "Migration completed"
}
}.start()
}, modifier = Modifier.padding(top = 8.dp)) {
Text("Migrate Database")
}
}
}
}
}We first added the new age field to the User data class with a default value of 0 to keep compatibility.
The migration MIGRATION_1_2 runs SQL to add the new age column with a default value of 0 to existing rows.
The Room database is built with this migration included.
On app start, initial users are inserted if none exist.
The UI shows the list of users with their name and age.
When the user taps the "Migrate Database" button, the database is closed and reopened with the migration applied, then the user list reloads and the migration status updates.
This demonstrates a simple Room database migration adding a new column without losing existing data.