Android Room SQLiteReadOnlyDatabaseException - android

I have converted my app to use Android Room for SQLite DB. There are some crashes on different devices with my implementation.
Fatal Exception: android.database.sqlite.SQLiteReadOnlyDatabaseException: attempt to write a readonly database (code 1032 SQLITE_READONLY_DBMOVED)
at android.database.sqlite.SQLiteConnection.nativeExecute(SQLiteConnection.java)
at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:707)
at android.database.sqlite.SQLiteConnection.setLocaleFromConfiguration(SQLiteConnection.java:473)
at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:261)
at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:205)
at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:505)
at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:206)
at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:198)
at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:918)
at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:898)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:762)
at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:751)
at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:373)
at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:316)
at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:145)
at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:106)
at androidx.room.SQLiteCopyOpenHelper.getWritableDatabase(SQLiteCopyOpenHelper.java:97)
at androidx.room.RoomDatabase.internalBeginTransaction(RoomDatabase.java:482)
at androidx.room.RoomDatabase.beginTransaction(RoomDatabase.java:471)
at com.luzeon.MyApp.sqlite.ViewLogDao_Impl$5.call(ViewLogDao_Impl.java:94)
at com.luzeon.MyApp.sqlite.ViewLogDao_Impl$5.call(ViewLogDao_Impl.java:91)
at androidx.room.CoroutinesRoom$Companion$execute$2.invokeSuspend(CoroutinesRoom.kt:61)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at androidx.room.TransactionExecutor$1.run(TransactionExecutor.java:47)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)
I have created the apps Room DB
#Database(entities = [ViewLogModel::class], version = 4)
abstract class MyAppDatabase : RoomDatabase() {
abstract fun viewLogDao(): ViewLogDao
companion object {
// For Singleton Instance
#Volatile
private var INSTANCE: MyAppDatabase? = null
fun getAppDataBase(context: Context): MyAppDatabase {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: Room.databaseBuilder(context.applicationContext, MyAppDatabase::class.java, "MyAppDatabase")
.createFromAsset(“myapp.db")
.setJournalMode(RoomDatabase.JournalMode.TRUNCATE) // disable WAL
.fallbackToDestructiveMigration()
.build()
}
}
fun destroyDataBase(){
INSTANCE = null
}
}
}
And have a DB Helper class
class MyAppDatabaseHelper(private val context: Context, private val coroutineScope: CoroutineScope) {
fun updateViewLog(viewLogModel: ViewLogModel) {
try {
// get the database
val db = MyAppDatabase.getAppDataBase(context)
coroutineScope.launch {
// store in db
db.viewLogDao().insertOrUpdateViewLog(viewLogModel)
}
} catch (e: Exception) {}
}
suspend fun getViewLog(memberId: Int): JSONArray {
try {
val jsonArray = JSONArray()
// get the database
val db = MyAppDatabase.getAppDataBase(context)
val viewLog = db.viewLogDao().getViewLog(memberId)
for (view in viewLog) {
// create the object
val jsonObject = JSONObject()
try {
jsonObject.put("mid", view.mid)
} catch (e: JSONException) {
}
try {
jsonObject.put("uts", view.uts)
} catch (e: JSONException) {
}
jsonArray.put(jsonObject)
}
// clear log (current user records or records older than 24hrs)
db.viewLogDao().deleteViewLog(memberId, Utilities.getUtsYesterday().toFloat())
// return the array
return jsonArray
} catch (e: Exception) {
return JSONArray()
}
}
}
With ViewLogDao
#Dao
interface ViewLogDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrUpdateViewLog(viewLog: ViewLogModel)
#Update
suspend fun updateViewLog(viewLog: ViewLogModel)
#Delete
suspend fun deleteAllViewLog(viewLog: ViewLogModel)
#Query("DELETE FROM viewlog WHERE VID= :vid OR UTS < :uts")
suspend fun deleteViewLog(vid: Int, uts: Float)
#Query("SELECT * FROM viewLog")
suspend fun getAll(): List<ViewLogModel>
#Query("SELECT * FROM viewLog WHERE vid = :vid")
suspend fun getViewLog(vid: Int): List<ViewLogModel>
}
And ViewLogModel
#Entity(tableName = "viewLog")
data class ViewLogModel(
#ColumnInfo(name = "VID") val vid: Int,
#ColumnInfo(name = "UTS") val uts: Float,
#ColumnInfo(name = "MID") #PrimaryKey val mid: Int)
I have not been able to find how to catch the SQLiteReadOnlyDatabaseException in the rare occurrences when the DB is read only. Or is there a way to ensure the ROOM Db is read/write?

I have not been able to find how to catch the SQLiteReadOnlyDatabaseException in the rare occurrences when the DB is read only. Or is there a way to ensure the ROOM Db is read/write?
The message code 1032 SQLITE_READONLY_DBMOVED :-
The SQLITE_READONLY_DBMOVED error code is an extended error code for SQLITE_READONLY. The SQLITE_READONLY_DBMOVED error code indicates that a database cannot be modified because the database file has been moved since it was opened, and so any attempt to modify the database might result in database corruption if the processes crashes because the rollback journal would not be correctly named.
If the message is to be believed then the database has been moved/renamed. From the message it would appear that the (one of the two being handled) database is being renamed whilst it is open.
In the log many of the entries are similar so it looks like two databases are being managed i.e. it is the stage of creating the database from the asset.
This may well be an issue with the createFromAsset handling which I understand to not necessarily be rock solid. e.g. at present there are issues with the prePackagedDatabaseCallback.
As such by using createFromAsset that you can do nothing other than raise an issue.
I would suggest circumventing the issue and pre-copying the asset yourself before passing control to Room.
to undertake the copy you do not need to open the database as a database just as a file.
The other alternative, could be to see if exclusively using WAL mode, overcomes the issue. As you are disabling WAL mode, then I guess that you have no wish to do so (hence why suggested as the last).
this would not only entail not disabling WAL mode but also having the asset set to WAL mode before distribution.

Related

Unable to Insert values in Room Database in Android [Kotlin]

I am trying to insert values inside my room database but the App Inspection is showing nothing. I have a complex app structure with lot of tables and one database. I am able to perform CRUD operations on all of the tables except for one. I am not able to identify what I am doing wrong.
Code -->
Entity
#Entity(tableName = "add_time")
data class TimeEntity(
#PrimaryKey(autoGenerate = true)
var id: Int? = null,
#ColumnInfo(name = "start_time")
var startTime: String? = "",
#ColumnInfo(name = "end_time")
var endTime: String? = "",
#ColumnInfo(name = "running_time")
var runningTime: String? = "",
)
DAO
#Dao
interface FloorTimeDao {
//Insert time
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertTimeForScheduling(timeEntity: TimeEntity) {
}
}
REPOSITORY
class AddTimeRepository(private val timeDao: FloorTimeDao) {
//insert
#WorkerThread
suspend fun insertTime(time: TimeEntity) = timeDao.insertTimeForScheduling(time)
}
VIEWMODEL
class AddTimeViewModel(private val repository: AddTimeRepository) : ViewModel() {
//insert
fun insertTime(timeEntity: TimeEntity) = viewModelScope.launch {
repository.insertTime(timeEntity)
}
}
VIEWMODEL FACTORY
#Suppress("UNCHECKED_CAST")
class AddTimeViewModelFactory(private val repository: AddTimeRepository) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
if (modelClass.isAssignableFrom(AddTimeViewModel::class.java)) {
return AddTimeViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel Class")
}
}
CODE INSIDE FRAGMENT FOR ADDING VALUE
//Inserting new time in the table
binding.btnAddTime.setOnClickListener {
try {
timeRunning = timeEnd - timeStart
timeRunning = abs(timeRunning)
Timber.d("Final Time start : $timeStart")
Timber.d("Final Time end : $timeEnd")
Timber.d("Final Time running : $timeRunning")
val timeEntity = TimeEntity(
startTime = timeStart.toString(),
endTime = timeEnd.toString(),
runningTime = timeRunning.toString()
)
Timber.d("Time Entity: $timeEntity")
addTimeViewModel.insertTime(timeEntity)
} catch (e: Exception) {
Timber.d("Exception : $e")
}
}
Everything seems to be added correctly and I still cannot seem to find out what I am doing wrong here. Any help would be appreciated. If you need more code I can provide just let me know.
NOTE: With this code basically I am trying to store the time in room database which is taken from the user using Time Picker Dialog.
Edit 1: Found something which I don't know if it is related to the issue or anything. But for the tables in which I am able to insert and read the data the function says something like this :
and for the table (TimeEntity) the function says this :
The difference is that (for the one in which it is working) the functions says Choose Implementation and have a green symbol on left side. But for the table for which it is not working the function says Choose overridden method.
Update: I was able to fix the issue by creating a new Dao Interface for the table. I am still not very sure what might have been the issue as both DAO files looks same. But I have some doubt that it might be due to the room automatic implementations when we create new DAO's or table.
#PrimaryKey(autoGenerate = true)
var id: Int? = null,
Try to change id Int? - To non nullable type Int or Long.
Why do you need to have predefined (initialized) values in entity constructor?
I was able to fix the issue by creating a new Dao file with same functions. The issue may have been due to the automatic implementation of DAO's provided by Room Library.

What happens when room android db gets corrupted?

In a large scale app, there are chances of my db file (created through android room lib), getting corrupted. For such a user whats the fallback ?
Will room delete the db file and re-create from scratch in production mode or will I have to handle that myself ?
For such a user whats the fallback ?
Backups and restore, noting that backups should not be taken when there are any open transactions. It is best to ensure that if in WAL mode (which is the default mode with room) that the database is fully committed (i.e. the WAL file is empty or doesn't exist).
The backup could be a simple file copy or you can use the VACUUM INTO the latter having the advantage of potentially freeing space but the disadvantage is that it could be more resource intensive.
You could maintain additional databases where changes are applied to other databases as and when the main database is changed. Obviously there would be an overhead. This would have the advantage that if corruption were to occur that it would be less likely to occur in the other databases.
You could maintain a log that allowed roll back and roll forward of changes. The latter being used to roll forward from a backup to the point of corruption or close to the point of corruption.
Will room delete the db file and re-create from scratch in production mode or will I have to handle that myself ?
if detected (when opening) then yes :-
onCorruption
The method invoked when database corruption is detected. Default implementation will delete the database file.
https://developer.android.com/reference/androidx/sqlite/db/SupportSQLiteOpenHelper.Callback#onCorruption(androidx.sqlite.db.SupportSQLiteDatabase)
What happens when room android db gets corrupted?
here's an example of file corruption
2021-10-27 10:36:19.281 7930-7930/a.a.so69722729kotlinroomcorrupt E/SQLiteLog: (26) file is not a database
2021-10-27 10:36:19.285 7930-7930/a.a.so69722729kotlinroomcorrupt E/SupportSQLite: Corruption reported by sqlite on database: /data/user/0/a.a.so69722729kotlinroomcorrupt/databases/thedatabase
2021-10-27 10:36:19.286 7930-7930/a.a.so69722729kotlinroomcorrupt W/SupportSQLite: deleting the database file: /data/user/0/a.a.so69722729kotlinroomcorrupt/databases/thedatabase
2021-10-27 10:36:19.306 7930-7930/a.a.so69722729kotlinroomcorrupt D/TAG: onCreate Invoked.
2021-10-27 10:36:19.312 7930-7930/a.a.so69722729kotlinroomcorrupt D/TAG: onOpen Invoked.
As can be seen by logging from the callbacks that after the file is deleted that onCreate is invoked thus creating a new empty database.
The code used for the above, which you may wish to adapt for testing, is :-
A simple #Entity Something :-
#Entity
data class Something(
#PrimaryKey
val id: Long?=null,
val something: String
)
A Simple #Dao AllDao
#Dao
abstract class AllDao {
#Insert
abstract fun insert(something: Something)
#Query("SELECT * FROM something")
abstract fun getAllFromSomething(): List<Something>
}
The #Database TheDatabase
#Database(entities = [Something::class],version = 1)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDao(): AllDao
companion object {
const val DATABASENAME = "thedatabase"
const val TAG = "DBINFO"
private var instance: TheDatabase? = null
var existed: Boolean = false
fun getInstance(context: Context): TheDatabase {
existed = exists(context)
if (exists(context)) {
Log.d(TAG,"Database exists so corrupting it before room opens the database.")
corruptDatabase(context)
}
if (instance == null) {
instance = Room.databaseBuilder(context,TheDatabase::class.java, DATABASENAME)
.allowMainThreadQueries()
.addCallback(cb)
.build()
}
instance!!.openHelper.writableDatabase // Force open
return instance as TheDatabase
}
object cb: Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
Log.d("TAG","onCreate Invoked.")
}
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
super.onDestructiveMigration(db)
Log.d("TAG","onDestructiveMigration Invoked.")
}
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
Log.d("TAG","onOpen Invoked.")
}
}
fun exists(context: Context): Boolean {
val db = File(context.getDatabasePath(DATABASENAME).path)
return db.exists()
}
/**
* Corrupt the database by
* copying the file via buffered reads and write
* BUT only write part of a buffer
* AND skip the 3rd block
* Furthermore, delete the -wal file (possible that this )
* Note that often it is the -wal file deletion that corrupts
*/
fun corruptDatabase(context: Context) {
Log.d("TAG","corruptDatabase Invoked.")
var db: File = File(context.getDatabasePath(DATABASENAME).path)
logFileInfo(db,"Initial")
var tempdb = File(context.getDatabasePath("temp" + DATABASENAME).path)
logFileInfo(tempdb,"Initial")
val blksize = 128
var buffer = ByteArray(blksize)
var i: InputStream
var o: FileOutputStream
try {
i = FileInputStream(db)
o = FileOutputStream(tempdb)
var blocks = 0;
var writes = 0;
while (i.read(buffer) > 0) {
if(blocks++ % 2 == 1) {
writes++
o.write(buffer,buffer.size / 4,buffer.size / 4)
}
}
Log.d(TAG,"${blocks} Read ${writes} Written")
o.flush()
o.close()
i.close()
db = File(context.getDatabasePath(DATABASENAME).path)
logFileInfo(db,"After copy")
tempdb = File(context.getDatabasePath("temp${DATABASENAME}").path)
logFileInfo(tempdb,"After copy")
} catch (e: IOException) {
e.printStackTrace()
}
db.delete()
//(context.getDatabasePath(DATABASENAME+"-wal")).delete()
logFileInfo(db,"After delete")
tempdb.renameTo(context.getDatabasePath(DATABASENAME))
logFileInfo(tempdb,"After rename")
logFileInfo(context.getDatabasePath(DATABASENAME),"After rename/new file")
}
fun logFileInfo(file: File, prefix: String) {
Log.d(TAG,"${prefix} FileName is ${file.name}\n\tpath is ${file.path}\n\tsize is ${file.totalSpace} frespace is ${file.freeSpace}")
}
}
}
Finally an invoking Activity MainActivity
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this);
dao = db.getAllDao()
dao.insert(Something(something = "Something1 " + TheDatabase.existed))
dao.insert(Something(something = "Something2 " + TheDatabase.existed))
for(s: Something in dao.getAllFromSomething()) {
Log.d(TheDatabase.TAG,"Something with ID " + s.id + " is " + s.something)
}
}
}
Note it's the -wal deletion that corrupts for the database above. It is quite resilient in regards to blocks being deleted removed, at least for smaller databases where the WAL file is of sufficient size to recover as such by applying the changes stored within. However, testing based upon the above but with a second table and 100000 rows inserted, then the partial block writes and missed block writes do corrupt the database.

How to get the id of inserted row in android room databse in Kotlin?

I am trying to get the user ID from the newest user. How can I make the insert method spit the ID when the ID is autogenerated?
in Model
#PrimaryKey(autoGenerate = true)
val userId: Int
in Dao
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun addUserWithLong(user: User): LiveData<Long>
in Repository
fun addUserWitLong(user: User): LiveData<Long> {
return userDao.addUserWithLong(user)
}
in ViewModel
fun addUserWithLong(user: User): LiveData<Long> {
return repository.addUserWitLong(user)
}
in Fragment
val id: Long? = userViewModel.addUserWithLong(user).value
I have read in the docs that #Insert returns Long as the row ID but I do not know how to program it. Now the error is "Not sure how handle insert method return type." Is there some way to make with LiveData and not with Rxjava. That is without the need to download more dependecies.
As per the documentation here
If the #Insert method receives a single parameter, it can return a
long value, which is the new rowId for the inserted item. If the
parameter is an array or a collection, then the method should return
an array or a collection of long values instead, with each value as
the rowId for one of the inserted items. To learn more about returning
rowId values, see the reference documentation for the #Insert
annotation, as well as the SQLite documentation for rowid tables
So you can use it like
#Insert(onConflict = OnConflictStrategy.REPLACE)
long addUserWithLong(user: User)
or if you are inserting a list
#Insert(onConflict = OnConflictStrategy.REPLACE)
long[] addUserWithLong(user: List<User>)
Edit-1
After checking answers from this post.
No, you can't. I wrote an answer to the issue. The reason is, that
LiveData is used to notify for changes. Insert, Update, Delete won't
trigger a change.
I just created a test project and successfully received Id of last inserted item in activity. Here is my implementation.
Dao
#Insert
suspend fun addUser(user: Users): Long
Repo
suspend fun insertUser(context: Context, users: Users): Long {
val db = AppDatabase.getInstance(context)
val dao = db.userDao()
return dao.addUser(users)
}
ViewModel
fun addUser(context: Context, users: Users) = liveData {
//you can also emit your customized object here.
emit("Inserting...")
try {
val userRepo = UsersRepo()
val response = userRepo.insertUser(context, users)
emit(response)
} catch (e: Exception) {
e.printStackTrace()
emit(e.message)
}
}
Activity
viewModel.addUser(applicationContext, user).observe(this, Observer { userId ->
Log.d("MainActivity", "Inserted User Id is $userId")
})
Check test application here.

Android Room Database - Batch Insert is not working

I am using Room database in Android and trying to do batch insert in ENTITY or TABLE created in Room database Andorid.
I am trying as below :
suspend fun batchInsertDataToTable(listObjDetails: ArrayList<Data>) {
try {
appDatabase.myDao().insertAll(*listObjDetails.toTypedArray())
} catch (e: Exception) {
e.printStackTrace()
}
}
Where, myDao().insertAll() method is as below :
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertAll(vararg objData: Data)
But, when I have checked the database, there is not entry in the table.
NOTE : I can do normal Insert in this table using for loop.
What might be the issue? Thanks.

java.util.ConcurrentModificationException while inserting data into room database

I am trying to download data from firebase firestore and insert it into the room DB for some offline use and avoid time-lag using the MVVM architecture pattern but when I do that I get an java.util.ConcurrentModificationException error I am inserting the data into the room DB inside a coroutine.
My code
class HomeFragmentViewModel(application: Application): AndroidViewModel(application) {
private var mDatabase: AppDatabase = AppDatabase.getInstance(application)!!
private val postListRoom: MutableList<PostRoomEntity> = mutableListOf()
private val postList: LiveData<MutableList<PostRoomEntity>>? = getPostList2()
private val firebaseAuth: FirebaseAuth = FirebaseAuth.getInstance()
private val db: FirebaseFirestore = FirebaseFirestore.getInstance()
private val myTAG: String = "MyTag"
#JvmName("getPostList")
fun getPostList(): LiveData<MutableList<PostRoomEntity>>? {
return postList
}
#JvmName("getPostList2")
fun getPostList2(): LiveData<MutableList<PostRoomEntity>>? {
var postsDao: PostsDao? = null
Log.d(myTAG, "postDao getPost is " + postsDao?.getPosts())
return mDatabase.postsDao()?.getPosts()
// return postList
}
fun loadDataPost() {
val list2 = mutableListOf<PostRoomEntity>()
db.collection("Posts")
.addSnapshotListener { snapshots, e ->
if (e != null) {
Log.w(myTAG, "listen:error", e)
return#addSnapshotListener
}
for (dc in snapshots!!.documentChanges) {
when (dc.type) {
DocumentChange.Type.ADDED -> {
dc.document.toObject(PostRoomEntity::class.java).let {
list2.add(it)
}
postListRoom.addAll(list2)
viewModelScope.launch(Dispatchers.IO) {
mDatabase.postsDao()?.insertPost(postListRoom)
}
// mDatabase.let { saveDataRoom(postListRoom, it) }
}
DocumentChange.Type.MODIFIED -> {
}
DocumentChange.Type.REMOVED -> {
Log.d(myTAG, "Removed city: ${dc.document.data}")
}
}
}
}
}
}
PostsDao
#Dao
interface PostsDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertPost(PostEntity: MutableList<PostRoomEntity>)
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAllPosts(PostEntity :List<PostRoomEntity>)
#Query("Select * from PostRoomEntity")
fun getPosts(): LiveData<MutableList<PostRoomEntity>>
// #Query("SELECT * FROM notes WHERE id= :id")
// open fun getNoteById(id: Int): NoteEntity?
}
It is very error-prone to use MutableLists with asynchronous tasks or to expose them to outside functions. You are doing both, and this can result in them being modified from two different places in code simultaneously, which can cause a ConcurrentModificationException.
You should use read-only Lists to eliminate this risk. For example, use vars of type List instead of vals of type MutableList.
Some other issues with your code:
You are adding the whole contents of the list to the main list on each step of iteration, so the last item is added once, the second-to-last item is added twice, and so on. You are also inserting that whole exploded list in your local database on each step of iteration, so it is even more exponentially multiplied with redundancies. If you are just trying to update your local database with changes, you should only be inserting a single row at a time anyway.
Unnecessary nullability used in a few places. There's no reason for the DAO or your LiveData to ever be null.
Unnecessary intermediate variables that serve no purpose. Like you create a variable var postsDao: PostsDao? = null and log the null value and never use it.
Redundant and non-idiomatic getters for properties you could expose as public directly.
Redundant backing property for the value that's already held in a LiveData.
You can make your DAO functions suspend so you don't have to worry about which dispatchers you're using to call them.
There's no reason for the DAO to have an insert overload for a MutableList instead of a List. I think the parameter should just be a single item.
You can have a single coroutine iterate the list of changes instead of launching separate coroutines to handle each individual change.
I also recommend not mixing Hungarian and non-Hungarian member names. Actually I don't recommend using Hungarian naming at all, but it's a matter of preference.
And it's a little confusing that you have two databases, but there is nothing about their names to distinguish them.
Fixing these problems, your code will look like this, but there might be other issues because I can't test it or see what it's hooked up to. Also, I don't use Firebase, but I feel like there must be a more robust way of keeping your local database in sync with Firestore than trying to make individual changes with a listener.
class HomeFragmentViewModel(application: Application): AndroidViewModel(application) {
private val localDatabase: AppDatabase = AppDatabase.getInstance(application)!!
private val mutablePostList = MutableLiveData<List<PostRoomEntity>>()
val postList: LiveData<List<PostRoomEntity>> = mutablePostList
private val firebaseAuth: FirebaseAuth = FirebaseAuth.getInstance()
private val firestore: FirebaseFirestore = FirebaseFirestore.getInstance()
private val myTAG: String = "MyTag"
fun loadDataPost() {
db.collection("Posts")
.addSnapshotListener { snapshot, e ->
if (e != null) {
Log.w(myTAG, "listen:error", e)
return#addSnapshotListener
}
mutablePostList.value = snapshot!!.documents.map {
it.toObject(PostRoomEntity::class.java)
}
viewModelScope.launch {
for (dc in snapshot!!.documentChanges) {
when (dc.type) {
DocumentChange.Type.ADDED -> {
val newPost = dc.document.toObject(PostRoomEntity::class.java)
localDatabase.postsDao().insertPost(newPost)
}
DocumentChange.Type.MODIFIED -> {
}
DocumentChange.Type.REMOVED -> {
Log.d(myTAG, "Removed city: ${dc.document.data}")
}
}
}
saveDataRoom(postListRoom, localDatabase) // don't know what this does
}
}
}
}
#Dao
interface PostsDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertPost(postEntity: PostRoomEntity)
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAllPosts(postEntity: List<PostRoomEntity>)
#Query("Select * from PostRoomEntity")
fun getPosts(): LiveData<MutableList<PostRoomEntity>>
// #Query("SELECT * FROM notes WHERE id= :id")
// open fun getNoteById(id: Int): NoteEntity?
}

Categories

Resources