## Table of contents
## Project description
2 commits, available on GIT
- `App.kt`
- `appContext` hack
- defined in manifest
- `manifest`
- `windowSoftInputMode="adjustResize`
- app `build.gradle`
- dependencies
- `ksp`
- `ksp` schema
- root `build.gradle`
- `ksp`
## Settings
### SharedPrefs
```kotlin=
Column(
modifier = Modifier
.padding(paddingValues)
.padding(12.dp)
.fillMaxSize(),
) {
Text("Name")
Spacer(Modifier.height(4.dp))
TextField(
value = "hello",
onValueChange = { },
modifier = Modifier.fillMaxWidth(),
)
}
```
Save button
```kotlin=
Spacer(Modifier.weight(1f))
Button(
onClick = { viewModel.onSaveClick() },
modifier = Modifier.fillMaxWidth(),
) {
Text("Save")
}
```
```kotlin=
fun onNameChange(name: String) {
_screenStateStream.update { state -> state.copy(name = name) }
}
val state by viewModel.screenStateStream.collectAsStateWithLifecycle()
```
- Survives screen rotation :+1:
ProfileDataSource
```kotlin=
private val prefs = appContext.getSharedPreferences("fit_prefs", Context.MODE_PRIVATE)
private const val NAME_KEY = "name"
fun getName(): String {
}
var name: String
get() = prefs.getString(NAME_KEY, null) ?: ""
set(value) {
prefs.edit { putString(NAME_KEY, value) }
}
```
Profile viewmodel
```kotlin=
fun onSaveClick() {
viewModelScope.launch {
ProfileDataSource.name = screenStateStream.value.name
}
}
_screenStateStream.update { state ->
state.copy(
name = ProfileDataSource.name,
)
}
```
- They are not stream aware...how?
- SharedPrefs listener
- MutableStateFlow
- How to view preferences
- Device file explorer
- Flipper
- Not stream aware
### Datastore
```kotlin=
enum class DarkMode
```
```kotlin=
DarkMode.values().forEach { option ->
Button(
onClick = { },
modifier = Modifier.fillMaxWidth(),
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary),
) {
Text(option.displayName)
}
}
```
```kotlin=
fun onDarkModeSelection(darkMode: DarkMode) {
_screenStateStream.update { state -> state.copy(selectedDarkMode = darkMode) }
}
```
```kotlin=
private val Context.dataStore by preferencesDataStore("fit_datastore")
private val DARK_MODE_KEY = stringPreferencesKey("name")
fun getDarkModeStream(): Flow<DarkMode> {
return appContext.dataStore.data
.map { prefs ->
val savedDarkModeName = prefs[DARK_MODE_KEY]
if (savedDarkModeName != null) {
DarkMode.valueOf(savedDarkModeName)
} else {
DarkMode.SYSTEM
}
}
.distinctUntilChanged()
}
suspend fun setDarkMode(darkMode: DarkMode) {
appContext.dataStore.edit { prefs -> prefs[DARK_MODE_KEY] = darkMode.name }
}
```
- `distinct` needed!
- change VM
```kotlin=
val darkMode by ProfileDataSource.getDarkModeStream().collectAsStateWithLifecycle(null)
darkMode?.let {
Lecture7Theme(isInDarkTheme(it)) {
Navigation()
}
}
```
- What if we wanted to save some complicated object
## Notes
```kotlin=
@Entity(tableName = "notes")
data class Note(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "textik")
val text: String
)
```
```kotlin=
@Entity(primaryKeys = ["id", "text"])
@Entity(tableName = "notes")
@ColumnInfo(name = "textik")
```
- Mention `@Ignore`
```kotlin=
@Dao
interface NotesDao {
@Query("SELECT * FROM notes")
fun getAll(): List<Note>
}
@Database(entities = [Note::class], version = 1)
abstract class CvutDatabase : RoomDatabase() {
abstract fun notesDao(): NotesDao
}
```
```kotlin=
object NotesDataSource {
private val database = Room.databaseBuilder(
appContext,
CvutDatabase::class.java,
"fit_db"
).build()
private val notesDao = database.notesDao()
}
```
- What is ksp
- Show generated: `build -> generated -> ksp`
- Show that schema was generated
### Select basic
```kotlin=
LazyColumn(
contentPadding = PaddingValues(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.padding(paddingValues)
.fillMaxSize(),
) {
items(
state.notes,
key = { note -> note.id },
) { note ->
```
```kotlin=
init {
val notes = NotesDataSource.getAllNotesStream()
_screenStateStream.update { it.copy(notes = notes) }
}
```
- Show coroutine
- Switch to dispatcher
- Use suspend function
DataSource
```kotlin=
fun getAllNotes(): List<Note> {
return notesDao.getAll()
}
```
### Insert
```kotlin=
@Insert
suspend fun insert(note: Note)
suspend fun insertNote(note: Note) {
return notesDao.insert(note)
}
```
```kotlin=
@Insert(onConflict = OnConflictStrategy.REPLACE)
```
- It's all based on the primary key
- Show what happens on rotation
- Database inspector
```kotlin=
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(4.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp)
) {
```
### Select stream
- Can do coroutines, rxjava, livedata
```kotlin=
@Query("SELECT * FROM notes ORDER BY id DESC")
fun getAllStream(): Flow<List<Note>>
fun getAllNotesStream(): Flow<List<Note>> {
return notesDao.getAllStream()
}
NotesDataSource.getAllNotesStream()
.onEach { notes ->
_screenStateStream.update { state -> state.copy(notes = notes) }
}
.launchIn(viewModelScope)
```
```kotlin=
@Query("SELECT * FROM notes WHERE id > :threshold ORDER BY id DESC")
fun getAllStream(threshold: Int): Flow<List<Note>>
```
### Delete
```kotlin=
suspend fun deleteNote(note: Note) {
notesDao.delete(note)
}
@Delete
suspend fun delete(note: Note)
```
### Update
- Same as delete
### Delete all
- Show that you can't use `@Delete`
```kotlin=
@Query("DELETE FROM notes")
suspend fun deleteAll()
suspend fun deleteAllNotes() {
notesDao.deleteAll()
}
```
- We can insert more things
- We can return id of the row or number of affected rows
- Database can do many things, `JOIN`, `@Embedded`, modelling of relations etc
```kotlin=
@Transaction
public void insertAndDeleteInTransaction(Product newProduct, Product oldProduct) {
// Anything inside this method runs in a single transaction.
insert(newProduct);
delete(oldProduct);
}
```
### Migrations
- Whenever schema changes, version needs to be bumped
- Automatic and manual migrations
#### Manual migration
```kotlin=
@Entity(tableName = "notes")
data class Note(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "textik")
val text: String,
val name: String = ""
)
```
- Try to run
```kotlin=
@Database(
entities = [Note::class],
version = 2,
exportSchema = true,
autoMigrations = [AutoMigration(from = 1, to = 2)]
)
```
- Try to run
```kotlin=
.addMigrations(
object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE notes ADD COLUMN subtitle TEXT NOT NULL DEFAULT ''")
}
}
)
```
#### Automatic
- Just mention
#### `fallbackToDestructiveMigration`