Room Insert Not Persisting between Runs in Kotlin Jetpack Compose Project - kotlin

I'm working on an Android Jetpack Compose project with pre-populated Room Database.
Reading the database works great, however...
When I INSERT a new record, it doesn't PERSIST into the next time I run the application. I'm NOT using inMemoryDatabaseBuilder().
I know it works while the application is in memory because I can see the results on the screen, however, as soon as I stop the application, my insert goes away. I also checked the underlying database file and my insert is never committed to the database either while it's running or after I close it.
What am I doing wrong?
Thank you for your help!
This is the statement in my User Interface that adds a new person to my database:
viewModel.insertMyInfo(newMyInfo)
It works in memory, but the newly added record disappears as soon as I exit the application.
App Inspector LiveData shows my additions.
viewModel Insert Function
fun insertMyInfo(myInfo: MyInfo) {
myInfoRepository.insertMyInfo(myInfo)
}
Only seems to commit to memory, not disk.
DAO #Insert
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertMyInfo(vararg myInfo: MyInfo)
I tried this with suspend and no difference
Repository Function
fun insertMyInfo(newMyInfo: MyInfo) {
coroutineScope.launch(Dispatchers.IO) {
myInfoDAO.insertMyInfo(newMyInfo)
}
}
Database
#Database(entities = [(MyInfo::class)], version = 5)
abstract class MyInfoRoomDatabase: RoomDatabase() {
abstract fun myInfoDao(): MyInfoDAO
companion object {
private var INSTANCE: MyInfoRoomDatabase? = null
fun getInstance(context: Context): MyInfoRoomDatabase {
synchronized(this) {
var instance = INSTANCE
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext,
MyInfoRoomDatabase::class.java,
"MyInfo"
).createFromAsset("MyInfo.db")
.fallbackToDestructiveMigration()
.build() // End Room Database Builder
INSTANCE = instance
} // End If Null
return instance
} // End Synchronized
} // End Get Instance
} // End Companion Object
} // End Database Abstract Class

Your issue is probably that rather than the inserts not being applied, which you see they are, is that your issue is due to the version number being increased, along with .fallbackToDestructiveMigration in conjunction with no Migration being found.
This results in the database being destroyed, then as the database doesn't exist that it is created from the asset file and thus the changes are effectively undone.
As such, if there is a schema change (a change made to an #Entity annotated class that is included in the list of entities defined as part of the #Database annotation), then a Migration should be provided that makes the changes to the existing database, that retains the data, for the version change (in your case that handles the schema change from version 4 to version 5). An alternative could be to utilise AutoMigrations (noting the restrictions and requirements) see https://medium.com/androiddevelopers/room-auto-migrations-d5370b0ca6eb

Related

Handle NPE in Kotlin Flow For Room Database

I want to retrieve single object from Room database, so i have this method in Dao
// in Dao
#Query("SELECT * FROM table_foo ORDER BY RANDOM()")
fun getSingleFoo(): Flow<FooEntity>
That object then will be mapped into others model, let say PlainFoo.
// in Repository
fun getRandomFoo(): Flow<PlainFoo> = dao.getSingleFoo()
.map(FooEntity::asExternalModel)
But in the first launch of this app, the table is empty. It makes the dao function return null and trigger NPE when being mapped. I try to wrap it inside a sealed interface like this.
// Result.kt as wrapper
sealed interface Result<out T> {
data class Success<T>(val data: T) : Result<T>
data class Error(val exception: Throwable? = null) : Result<Nothing>
}
fun <T> Flow<T>.asResult(): Flow<Result<T>> = this
.map<T, Result<T>> {
Result.Success(it)
}
.catch {
emit(Result.Error(it))
}
And then i call this method in the presentation layer like this.
// in ViewModel
val randomFoo = fooRepository.getRandomFoo().asResult()
// in activity, log only for checking
lifecycleScope.launch {
viewModel.randomFoo.collect {
Timber.tag("RandomFooFlow").d("$it")
}
}
It catches the error, which look like this.
Error(exception=java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter <this>)
But when new data is inserted, it does not get updated unless i reopen the app (which means new Flow is being collected, not the old one). So it seems that the flow is cancelled.
Is there any way to handle this without making my Dao return a
nullable object?
Note: if the data is already populated when opening the app, the flow is able to keep consuming new value).
Instead of dealing with exceptions, I would suggest to return nullable types from your Dao. You can then also update your mapper function to handle the type nullability. You won't need to wrap it into any Result class, just a simple null check on the UI end would suffice.
// Dao
#Query("SELECT * FROM table_foo ORDER BY RANDOM()")
fun getSingleFoo(): Flow<FooEntity?>
// Repo
fun getRandomFoo(): Flow<PlainFoo?> = dao.getSingleFoo().map { it?.asExternalModel() }
Could you please call repository getRandomFoo() method from inside coroutine in view model ? And also you need to call response with data observe like LiveData or StateFlow. By the way, you can wrap your result with wrap inside repository. In code example, I do not care about it because your error is not related with mapping.
View Model
private val _stateFlow = MutableStateFlow()
val stateFlow:StateFlow
fun getRandom(){
fooRepository.getRandomFoo().onEach{
if(it is Result.Success){
stateFlow.value = it
}
}.launchIn(viewModelScope)
}
Fragment or activity
viewLifecycleOwner.lifecycle.repeatOnLifecycle{
stateFlow.collect{
// Listen data for your UI
}
}

Android Room - selective query with LiveData

Is it possible to get specific rows from Room with LiveData?
My goal is to retrieve certain items from data base, when each item is conditioned by to columns ("page" and "category").
The problem is that in LiveData observer I always receive the same page - that livedata object had been initiated with it in the beginning.
It has no effect if I change it afterwards in the ViewModel:
private fun requestNextPageFromDB(page: Int) {
filmsListLiveData = interactor.getPageOfFilmsFromDB(page)
}
I create the LiveData object in init block of my ViewModel:
var filmsListLiveData: LiveData<List<Film>>
init {
filmsListLiveData = interactor.getPageOfFilmsFromDB(2)
The method in Interactor class I initiate the livedata object:
fun getPageOfFilmsFromDB(page: Int): LiveData<List<Film>> =
repo.getPageOfFilmsInCategoryFromDB(page, getFilmsCategoryFromPreferences())
Next method in the repository:
fun getPageOfFilmsInCategoryFromDB(page: Int, category: String): LiveData<List<Film>> {
return filmDao.getCachedFilmsByPageAndCategory(page, category)
}
And the last one in the Dao Intertface:
#Query("SELECT * FROM cached_films WHERE page=:requestedPage AND category=:requestedCategory")
fun getCachedFilmsByPageAndCategory(requestedPage: Int, requestedCategory:String): LiveData<List<Film>>
All the methods above are invoked properly when the page number changes in livedata object.
Thanks a lot in advance.
From the description it seems that you are just replacing the filmsListLiveData reference, however the observer remains the original one, that is why it is not notified about any changes, since in the original filmsListLiveData there have been no changes.
You can use a MutableLiveData for the page value and then a Transformations.switchMap to obtain a LiveData that will react to the changes of the page value.
Inside your ViewModel, you can do something like this
// starting page is 2, not sure if it should be 1 or 2, I just copied you logic from init
private val currentPageLiveData = MutableLiveData(2)
var filmsListLiveData = Transformations.switchMap(currentPageLiveData) { page ->
interactor.getPageOfFilmsFromDB(page)
}
private fun requestNextPageFromDB(page: Int) {
currentPageLiveData.value = page
}
init {
// not needed anymore - remove this code
// filmsListLiveData = interactor.getPageOfFilmsFromDB(2)
}
With that, every time requestNextPageFromDB(page: Int) will be called, currentPageLiveData value will change, the switchMap will get called and a new call to interactor.getPageOfFilmsFromDB(page) will be made. That will update the LiveData value and your observer will be called again.
So the switchMap does exactly what you need, it switches the LiveData behind the scenes every time the source LiveData changes (in this case the currentPageLiveData) in such a way, that existing observers stop observing the old LiveData instance, the one switched from, and start observing the new LiveData instance, the one switched to.
At this point you can also change var filmsListLiveData to val filmsListLiveData and remove all code that is trying to replace its reference, since there is no need to replace this LiveData anymore (now it reacts to the page value change on its own).

Android ROOM, insert won't return ID with first insert, but will return with 2nd insert onwards

School project and I'm pretty new to Android development.
The problem
I have a button with a onClick listener in a save person fragment which will save person data to the database. Everything works fine except for some reason with the first click it wont return me the inserted row ID but it will do so with 2nd click onwards.
I really need this ID in before proceeding to the next fragment.
Not sure if this is important but whenever I return (reload) to this save person fragment, the behaviour is always the same that the first click allways fails to capture the inserted row ID.
input data:
first name = John
last name = Smith
Just for demo purpose, if I will try to use this button 3x to insert the person data (returned insert ID is in the log), I will get all 3 rows in database with name John Smith, but the very first inserted row ID is not captured (default initialised value is 0), please see the log below:
Log
2020-10-19 12:49:20.320 25927-25927/ee.taltech.mobile.contacts D/TEST_ADD_PERSON_ID: insertedPersonId: 0
2020-10-19 12:49:40.153 25927-25927/ee.taltech.mobile.contacts D/TEST_ADD_PERSON_ID: insertedPersonId: 5
2020-10-19 12:49:40.928 25927-25927/ee.taltech.mobile.contacts D/TEST_ADD_PERSON_ID: insertedPersonId: 6
EDITED ORIGINAL post
As suggested in the comments, I'm trying to go about the way of using LiveData and observer, but I'm still little bit stuck.
The setup
The below is the current setup.
Entity
#Entity(tableName = "person")
data class Person(
#PrimaryKey(autoGenerate = true)
val id: Int,
DAO
#Dao
interface PersonDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun addPerson(person: Person): Long
Repository
class PersonRepository(private val personDao: PersonDao) {
val readAllPersonData: LiveData<List<Person>> = personDao.readAllPersonData()
suspend fun addPerson(person: Person): Long {
return personDao.addPerson(person)
}
ViewModel
I'm not sure if I'm doing things right at all here. I broke it down here in steps and created separate variables insertedPersonILiveData and insertedPersonId.
How could pass the returned row id to insertedPersonILiveData?
class PersonViewModel(application: Application) : AndroidViewModel(application) {
var insertedPersonILiveData: LiveData<Long> = MutableLiveData<Long>()
var insertedPersonId: Long = 0L
val readAllPersonData: LiveData<List<Person>>
private val repository: PersonRepository
init {
val personDao = ContactDatabase.getDatabase(application).personDao()
repository = PersonRepository(personDao)
readAllPersonData = repository.readAllPersonData
}
suspend fun addPerson(person: Person) = viewModelScope.launch {
insertedPersonId = repository.addPerson(person)
// ****************************************************************
// insertedPersonILiveData = insertedPersonId (what to do here) ???
// ****************************************************************
}
Save person fragment
This is the way I'm calling out the addPerson via modelView.
val person = Person(0, firstName, lastName)
lifecycleScope.launch {
personViewModel.addPerson(person)
}
Log.d("TEST_ADD_PERSON_ID","insertedPersonId: ${personViewModel.insertedPersonId}")
And this is the way I have done the observer (not sure if it's even correct).
val returnedIdListener: LiveData<Long> = personViewModel.insertedPersonILiveData
returnedIdListener.observe(viewLifecycleOwner, Observer<Long> { id: Long ->
goToAddContactFragment(id)
})
private fun goToAddContactFragment(id: Long) {
Log.d("TEST_ADD_PERSON_ID", "id: " + id)
}
Create database
#Database(
entities = [Person::class, Contact::class, ContactType::class],
views = [ContactDetails::class],
version = 1,
exportSchema = false
)
abstract class ContactDatabase : RoomDatabase() {
abstract fun personDao(): PersonDao
abstract fun contactTypeDao(): ContactTypeDao
abstract fun contactDao(): ContactDao
abstract fun contactDetailsDao(): ContactDetailsDao
companion object {
// For Singleton instantiation
#Volatile
private var instance: ContactDatabase? = null
fun getDatabase(context: Context): ContactDatabase {
return instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}
}
private fun buildDatabase(context: Context): ContactDatabase {
return Room.databaseBuilder(context, ContactDatabase::class.java, "contacts_database")
.addCallback(
object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
val request = OneTimeWorkRequestBuilder<SeedDatabaseWorker>().build()
WorkManager.getInstance(context).enqueue(request)
}
}
)
.build()
}
}
}
You're starting a coroutine to run addPerson, and then immediately calling Log with the current value of insertedPersonId in the viewmodel. The coroutine will run, insert the person, and update the VM with the ID of the inserted row, but that will happen long after your Log has run. Probably all of your results are actually the ID of the last record that was inserted.
I'm new to a lot of this too, but just based on what you have now, I think you just need to add
insertedPersonILiveData.value = insertedPersonId
in your addPerson function. That way you're updating that LiveData with a new value, which will be pushed to any valid observers. You've written some code that's observing that LiveData instance, so it should get the update when you set it.
edit your problem is that insertedPersonILiveData is the immutable LiveData type, so you can't set the value on it - it's read-only. You're creating a MutableLiveData object but you're exposing it as a LiveData type.
The recommended pattern for this is to create the mutable one as an internal object, expose a reference to it as an immutable type, and create a setter method that changes the value through the mutable reference (which it can access internally)
class myViewModel : ViewModel() {
// mutable version is private, all updates go through the setter function
// (the _ prefix is a convention for "private versions" of data fields)
private val _lastInsertedPersonId = MutableLiveData<Long>()
// we're making the instance accessible (for observing etc), but as
// the immutable LiveData supertype that doesn't allow setting values
val lastInsertedPersonId: LiveData<Long> = _lastInsertedPersonId
// setting the value on the MutableLiveData instance
// is done through this public function
fun setLastInsertedPersonId(id: Long) {
_lastInsertedPersonId.value = id
}
}
and then your observer would just call lastInsertedPersonId.observe, you don't need to copy the LiveData and observe that (like you're doing with returnedIdListener.
That's the basic pattern right there - internal MutableLiveData, exposed publicly as an immutable LiveData val, with a setter method to update the value. Everything outside the view model either observes the LiveData that's visible, or calls the setter method to update. Hope that makes sense! It's not that complicated once you get your head around what's basically going on

Difference between get() and by lazy

Having a room Dao as below,
#Dao
public abstract class AccountDao {
#Query("SELECT * FROM Account LIMIT 0,1")
public abstract Account readAccount();
}
is there any differences between get() and by lazy in the sample below?
open val account: LiveData<Account>
get() = accountDao.readAccount()
open val account: LiveData<Account> by lazy { accountDao.readAccount() }
The difference is in how many times the function body (accountDao.readAccount()) will be executed.
The lazy delegate will execute the lambda one single time the first time it is accessed and remember the result. If it is called again, that cached result is returned.
On the other hand, defining the getter (get()) will execute the function body every time, returning a new result every time.
For example, let's suppose we have a class called Foo with both a getter and a lazy value:
class Foo {
val getterVal: String
get() = System.nanoTime().toString()
val lazyVal: String by lazy { System.nanoTime().toString() }
}
And then use it:
fun main() {
with(Foo()) {
repeat(2) {
println("Getter: $getterVal")
println("Lazy: $lazyVal")
}
}
}
For me, this prints:
Getter: 1288398235509938
Lazy: 1288398235835179
Getter: 1288398235900254
Lazy: 1288398235835179
And we can see that the getter returns a newly calculated value each time, and the lazy version returns the same cached value.
In addition to Todd's answer:
Yes, there is a difference for LiveData objects as well. Every call of accountDao.readAccount() will result in a different LiveData object. And it does matter, despite the fact that all of the returned LiveData will get updated on every change in the Account entity. Let me explain on these examples:
by lazy
As Todd mentioned, the block inside the lazy delegate will be executed once, at the first time that the account property is accessed, the result will be cached and returned on every next access. So in this case a single one LiveData<Account> object is created. The bytecode generated by Kotlin to achieve this is equivalent to this in Java:
public class Activity {
private Lazy account$delegate
public LiveData<Account> getAccount() {
return account$delegate.getValue();
}
}
get()
By creating a custom account property's getter and calling accountDao.readAccount() inside, you will end up with different LiveData<Account> objects on every access of the account property. Once more, bytecode generated for this case in Kotlin in Java is more or less this:
public class Activity {
public LiveData<Account> getAccount() {
return accountDao.readAccount();
}
}
So you can see, using a lazy property results in generating a backing field for this property, while using a custom getter creates a wrapper method for the accountDao.readAccount() call.
It's up to your needs which approach you should use. I'd say that if you have to obtain the LiveData only once, you should go with get(), because a backing field is needless in that case. However if you're going to access the LiveData in multiple places in your code, maybe a better approach would be to use by lazy and create it just once.

Room migration fails without error

i'm working with Room and suddenly it stops to show errors when i changed one or more entities. For example i add one field (vendor: String) to my Entity and it just clear all data without any error or suggestions. Just clearing all data and stop working. I don't use fallbackToDestructiveMigration.
Please help i really don't know how to avoid this. All of my branches wait for db sync.
Here is the code
#Database(entities = [(ServiceEntity::class), (ConfigEntity::class), (RequestEntity::class), (FaqEntity::class),
(SubscriptionEntity::class), (OrderEntity::class), (DeviceEntity::class), (ProblemEntity::class)], version = 5,
exportSchema = true)
abstract class RoomAppDataSource: RoomDatabase() {
abstract fun serviceDao(): ServiceDao
abstract fun configDao(): ConfigDao
abstract fun requestDao(): RequestDao
abstract fun otherDao(): OtherDao
abstract fun subscriptionsDao(): SubscriptionsDao
abstract fun ordersDao(): OrdersDao
companion object {
private val TAG = RoomAppDataSource::class.java.simpleName
private val Migration_4_5 = object: Migration(4, 5) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE ${RoomContract.TABLE_SERVICES} ADD COLUMN vendor TEXT default '' NOT NULL")
}
}
fun buildDataSource(context: Context): RoomAppDataSource = Room.databaseBuilder(
context.applicationContext, RoomAppDataSource::class.java, RoomContract.DATABASE_APP)
.addMigrations(Migration_4_5)
.build()
}
}
P.S. If i remove new line it works fine, but when i add smth in any entity app starts from tutorial page, because there are no tokens saved
Found an answer. I don't know why, but room just stop asserting new version of db or destructive migrations, so error i can see only when use Dao funcs. May be it will help to someone.