Irbisa · Шпаргалка
Хранилище Android
Room, DataStore, SharedPreferences, EncryptedSharedPreferences, файлы
Какие способы хранения данных есть в Android и когда использовать каждый?
Чем отличаются директории приложения
filesDir,cacheDir,noBackupFilesDirиgetExternalFilesDir()?SharedPreferences: чем отличаются
commit()иapply(), и потокобезопасны ли они?Что такое Room, из каких частей он состоит и зачем он вместо «голого» SQLite?
@Entity(tableName = "note")data class NoteEntity( @PrimaryKey(autoGenerate = true) val id: Long = 0, val title: String, val createdAt: Long)…После
add()новая задача появляется, только если уйти с экрана и вернуться. Что не так с этим кодом?@Daointerface TaskDao { @Query("SELECT * FROM task ORDER BY dueAt") suspend fun getAll(): List<TaskEntity> @Insert suspend fun insert(task: TaskEntity)} class TaskViewModel(private val dao: TaskDao) : ViewModel() { val tasks = MutableStateFlow(emptyList<TaskEntity>()) init { viewModelScope.launch { tasks.value = dao.getAll() } } fun add(title: String) = viewModelScope.launch { dao.insert(TaskEntity(title = title)) }}Зачем заменять SharedPreferences на DataStore, и когда выбирать Preferences DataStore, а когда Proto DataStore?
@Serializabledata class UserSettings(val theme: String = "system", val notifications: Boolean = true) object UserSettingsSerializer : Serializer<UserSettings> { override val defaultValue = UserSettings()…Этот код читает тему из DataStore перед показом UI. Что с ним не так и как сделать правильно?
val Context.settings by preferencesDataStore(name = "settings")val THEME = stringPreferencesKey("theme") class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val theme = runBlocking { settings.data.first()[THEME] } ?: "system" setContent { AppTheme(theme) { App() } } }}Найдите баг: приложение падает, когда пользователь открывает экран настроек во второй раз.
class SettingsRepository(context: Context) { private val dataStore = PreferenceDataStoreFactory.create { context.preferencesDataStoreFile("settings") } val theme: Flow<String> = dataStore.data.map { it[THEME] ?: "system" } suspend fun setTheme(value: String) { dataStore.edit { it[THEME] = value } }} class SettingsViewModel(app: Application) : AndroidViewModel(app) { private val repo = SettingsRepository(app) val theme = repo.theme.stateIn(viewModelScope, SharingStarted.Eagerly, "system")}Почему Room запрещает запросы на главном потоке и на каких потоках выполняются
suspend-методы DAO,Flow-запросы иwithTransaction?Что такое
TypeConverterв Room и какие у них ограничения?class Converters { @TypeConverter fun fromInstant(value: Instant?): Long? = value?.toEpochMilli() @TypeConverter fun toInstant(value: Long?): Instant? = value?.let(Instant::ofEpochMilli) // Fine to read back, impossible to query on @TypeConverter fun fromTags(tags: List<String>): String = Json.encodeToString(tags)…Как смоделировать связи один-ко-многим и многие-ко-многим в Room и зачем такому запросу
@Transaction?@Entity( tableName = "book", foreignKeys = [ForeignKey( entity = AuthorEntity::class, parentColumns = ["id"], childColumns = ["authorId"], onDelete = ForeignKey.CASCADE )],…Что на самом деле делает
OnConflictStrategy.REPLACEи почему@Upsertобычно лучше?@Entity(tableName = "contact")data class ContactEntity( @PrimaryKey val remoteId: String, // the server id is the key, so @Upsert can match it val name: String, val isFavourite: Boolean = false // local only)…Как работают миграции Room и что означает падение «Room cannot verify the data integrity»?
@Database( entities = [TaskEntity::class], version = 5, exportSchema = true, autoMigrations = [AutoMigration(from = 4, to = 5, spec = AppDatabase.RenameDue::class)])…Как протестировать DAO Room и как протестировать миграцию?
@RunWith(AndroidJUnit4::class)class NoteDaoTest { private val db = Room.inMemoryDatabaseBuilder( ApplicationProvider.getApplicationContext(), AppDatabase::class.java ).build()…Во время синхронизации
Flowсписка постоянно переэмитит и экран мерцает, хотя изменилось несколько строк. Почему и как это исправить?suspend fun applyRemote(rows: List<TaskEntity>) { rows.forEach { dao.upsert(it) }} @Daointerface TaskDao { @Upsert suspend fun upsert(task: TaskEntity) @Query("SELECT * FROM task ORDER BY dueAt") fun observeAll(): Flow<List<TaskEntity>>}Найдите баг: при быстрых нажатиях на
+количество товара в корзине иногда оказывается меньше числа нажатий.@Daointerface CartDao { @Query("SELECT * FROM cart_item WHERE productId = :id") suspend fun find(id: Long): CartItemEntity? @Upsert suspend fun upsert(item: CartItemEntity)} class CartRepository(private val dao: CartDao) { suspend fun addOne(productId: Long) { val current = dao.find(productId)?.quantity ?: 0 dao.upsert(CartItemEntity(productId, quantity = current + 1)) }}// CartViewModel: fun onPlusClick(id: Long) = viewModelScope.launch { repo.addOne(id) }Как безопасно хранить токен авторизации на Android и что пришло на смену
EncryptedSharedPreferences?private const val ALIAS = "auth" private fun key(): SecretKey { val ks = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } (ks.getEntry(ALIAS, null) as? KeyStore.SecretKeyEntry)?.let { return it.secretKey } return KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore").run {…Что такое scoped storage и как сохранить экспортированный PDF так, чтобы пользователь его нашёл, без
WRITE_EXTERNAL_STORAGE?// Option 1: the user picks the destination, no permission at allval createPdf = registerForActivityResult( ActivityResultContracts.CreateDocument("application/pdf")) { uri -> if (uri != null) lifecycleScope.launch(Dispatchers.IO) { contentResolver.openOutputStream(uri)?.use { exporter.writeTo(it) }…JSON-файл, который пишет приложение, после падения иногда оказывается обрезанным. Почему и как безопасно писать файлы?
// wrong: opening the target truncates it firstFile(context.filesDir, "cart.json").writeText(json) // right: backup + sync, the last good bytes survive a failed writeprivate val cartFile = AtomicFile(File(context.filesDir, "cart.json"))…Как построить офлайн-кеш на Room и Retrofit, где база служит единственным источником правды?
class ArticleRepository( private val api: ArticleApi, private val dao: ArticleDao, private val db: AppDatabase) { // The only thing the UI ever reads…Пользователь выходит из аккаунта. Что нужно очистить помимо токена и в каком порядке?
Что такое write-ahead logging (WAL) в SQLite и что он меняет для приложения на Room?
Как работают индексы в SQLite и как исправить запрос Room, который замедлился по мере роста таблицы?
@Entity( tableName = "message", indices = [ Index(value = ["chatId", "createdAt"]), // filter by chat, order by time Index(value = ["remoteId"], unique = true) ]…Почему данные приложения возвращаются после переустановки и почему восстановленное приложение может падать при расшифровке своих же данных?
private val sealed = File(context.noBackupFilesDir, "session.bin") // never backed up fun loadRefreshToken(): String? = try { if (!sealed.exists()) null else crypto.decrypt(sealed.readBytes()).decodeToString()} catch (e: GeneralSecurityException) { // key missing: data restored from another device sealed.delete()…