Verwendung von Room
Dauer: 45 Minuten
- Room-Datenbank erstellen
- Verwendung der Room-Datenbank in der Todo-App
Ziel: Datenbankzugriff mit Room in Android verstehen
Room-Datenbank erstellen
Wir erstellen eine abstrakte Klasse data/AppDatabase
, die die
Datenbank konfiguriert:
import androidx.room.Database
import androidx.room.RoomDatabase
@Database(entities = [TodoItem::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun todoDao(): TodoDao
}
Datenbankinstanz initialisieren
Wir initialisieren die Room-Datenbank in der MainActivity
:
class MainActivity : ComponentActivity() {
private lateinit var database: AppDatabase
private lateinit var todoDao: TodoDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Datenbank erstellen
database = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "todo-database"
).build()
todoDao = database.todoDao()
// Restlicher Code bleibt unverändert...
}
}
Todos aus der Datenbank laden und im UI anzeigen
In der MainActivity
ersetzen wir den State todos
durch
die aus der Datenbank geladenen Todos:
setContent {
val todos by todoDao.getAllTodos().collectAsState(initial = emptyList())
// Restlicher Code bleibt unverändert...
}
Nun verwenden wir die data class
namens TodoItem
anstelle von
String
für die Todos. Dies zieht einige Änderungen im UI-Code
in der MainActivity
nach sich:
Speicherung des Todos im AddTodoDialog
mit Hilfe des Todo-DAOs:
if (showDialog) {
AddTodoDialog(
onDismiss = { showDialog = false },
onAdd = { newTodo ->
lifecycleScope.launch {
todoDao.insertTodo(TodoItem(name = newTodo))
}
showDialog = false
}
)
}
Die TodoList
muss nun eine Liste von TodoItem
s als Parameter
annehmen:
@Composable
fun TodoList(todos: List<TodoItem>, modifier: Modifier = Modifier) {
LazyColumn(modifier) {
items(todos) { todo ->
Todo(todo)
}
}
}
Das Todo
-Composable muss ebenfalls angepasst werden (TodoItem
als Parameter mit entsprechenden Zugriffen auf die Properties):
@Composable
fun Todo(todo: TodoItem) {
var done by remember { mutableStateOf(todo.done) }
Row(
modifier = Modifier
.clickable(onClick = { done = !done })
.padding(10.dp)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(checked = done, onCheckedChange = { done = it })
Text(todo.name, fontWeight = FontWeight.Bold, fontSize = 22.sp)
}
}
Die Preview des Todo-Composables (TodoPreview
) löschen wir,
damit wir diese nicht anpassen müssen (Previews sind optional).
Wir können jetzt Todos erstellen, die in der Datenbank gespeichert werden und im UI angezeigt werden.
Wir benötigen lifecycleScope.launch
zum Speichern, weil ein
Datenbank-Zugriffe asynchron sind.
In Android Studio kann auf die Datenbank eines Emulators im
Database Inspector
zugegriffen werden. Dazu View > Tool Windows > App Inspection
öffnen.
Todos in der Datenbank aktualisieren
Wir reichen das todoDao
an Todo
weiter:
// in setContent von onCreate:
TodoList(
todos = todos,
todoDao = todoDao,
modifier = Modifier.padding(innerPadding)
)
// in TodoList:
@Composable
fun TodoList(todos: List<TodoItem>, todoDao: TodoDao, modifier: Modifier = Modifier) {
LazyColumn(modifier) {
items(todos) { todo ->
Todo(todo = todo, todoDao = todoDao)
}
}
}
Das Todo
-Composable wird dann so angepasst:
@Composable
fun Todo(todo: TodoItem, todoDao: TodoDao) {
val coroutineScope = rememberCoroutineScope()
var done by remember { mutableStateOf(todo.done) }
Row(
modifier = Modifier
.clickable(onClick = {
done = !done
// Status in der Datenbank aktualisieren
coroutineScope.launch {
todoDao.updateTodo(todo.copy(done = done))
}
})
.padding(10.dp)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(checked = done, onCheckedChange = {
done = it
// Status in der Datenbank aktualisieren
coroutineScope.launch {
todoDao.updateTodo(todo.copy(done = done))
}
})
Text(todo.name, fontWeight = FontWeight.Bold, fontSize = 22.sp)
}
}
Nun wird der Status eines Todos (erledigt oder nicht) mit der Datenbank synchronisiert.
Die Verwendung von DAOs in Composable mit lifecycleScope.launch
und
coroutineScope.launch
entspricht nicht den best practices in Android.
Wir haben diese hier nur aus Gründen der Einfachheit verwendet. UI-Logik
und Datenbankzugriffe sollten in komplexeren Apps getrennt werden.
Langfristig und in komplexeren Android-Apps sollten hierzu weitere Konzepte wie das Repository-Pattern, ViewModel und LiveData eingesetzt werden, um die Datenbankzugriffe vom UI zu trennen und den Code klarer zu strukturieren. Dazu finden sich Beispiele in der Android-Dokumentation, in Codelabs und in Sample-Apps (siehe unten).
Abschließende Bemerkungen
Der konzeptuelle Aufbau von Room wirkt anfangs etwas komplex (siehe grafische Darstellung des Aufbaus von Room in der Dokumentation).
Sobald Room jedoch eingerichtet ist, können Datenbankoperationen einfach und effizient durchgeführt werden. Room bietet eine umfangreiche API, um Datenbankzugriffe zu verwalten und zu optimieren.
Vertiefendes Material
Guide in Android Developer Docs zum Thema Daten und Dateien https://developer.android.com/guide/topics/data
Room in den Android Developer Docs:
Achtung
Wir verwenden zusätzlich zu app/build.gradle
die Datei
gradle/lib.version.toml
, um die Versionen von Bibliotheken zu verwalten.
In der Dokumentation wird oft noch empfohlen, die Versionen
ausschließlich in app/build.gradle
zu spezifizieren.
Room entwickelt sich ständig weiter… (wie alles andere in Android…)
- Guide
- Release Notes mit Neuigkeiten
- In den Android Courses gibt es Abschnitte zu Room
- Codelab
- Ein paar der Code-Samples verwenden Room