I am facing an issue while inserting data to Android table. Here is my Dao functions:
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(freight: Foo)
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(freights: MutableList<Foo>)
Here is how it is invoke:
Observable.fromCallable {
db.fooDao().insert(it)
}
}
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe {
Logger.d("Inserted ${it} users from API in DB...")
}
Exception I am getting:
Caused by: java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.next(ArrayList.java:860)
at com.blockgrain.blockgrain.dbmanager.repository.FooRepository$insertDataInDb$1.call(FooRepository.kt:76)
I have created other tables with same logic they are working fine but this one is failing . Please let me know what went wrong.
Update :
Foo.kt
override fun get(): Observable<MutableList<Foo>> {
val observable = getDataFromDb()
return observable.flatMap {
if (it.isEmpty())
getDataFromApi()
else
observable
}
}
override fun getDataFromApi(): Observable<MutableList<Foo>> {
return WebService.createWithAuth().getFooFromWeb()
.doOnNext {
Logger.d(" Dispatching ${it} users from API...")
Observable.fromCallable {
db.fooDao().insert(it)
}
}
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe {
Logger.d("Inserted ${it} users from API in DB...")
}
}
}
As per the given code, It is not directly clear how the array list modification is being called resulting into Caused by: java.util.ConcurrentModificationException .
My guess is, multiple operations are being performed on same list at a time.
Your insert list method in dao is accepting MutableList<Foo> change it to List<Foo> as Room doesn't need mutable list. like this,
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(freights: List<Foo>)
I would recommend to copy the array list to another list before doing any operations on the list like this
// Before performing any operation on list
var newList:List<Foo> = ArrayList<Foo>(otherList)
// Perform operation on newList - for ex.
db.insert(newList)
There is another solution for ArrayList if you want to use it concurrently with CopyOnWriteArrayList. But this will result into significant modification in existing in code. So I would recommend to go with first option.
Related
I have a local database in my Android app. There is a function that takes some data from server and updates local database.
When this function is running, if I collect a list from local database by returning Flow, it takes unusual time to finish.
I don't have any problem with LiveData, it works well but Flow doesn't.
this is my dao :
#Transaction
#Query("SELECT * FROM tbl WHERE id=:id")
fun getData(id: String): Flow<Entity?>
repo :
fun getData(id: String): Flow<Entity?> {
return dao.getData(id).map { it?.toModel() }
}
fragment :
lifecycleScope.launch() {
repo.getData(args.id)
.flowWithLifecycle(viewLifecycleOwner.lifecycle)
.distinctUntilChanged()
.collect { data ->
data?.let {
setData(data)
}
}
}
Try to add .flowOn(Dispatchers.IO) in you repository function after map like this:
Repository
fun getData(id: String): Flow<Entity?> {
return dao.getData(id)
.map { it?.toModel() }
.flowOn(Dispatchers.IO)
}
I think the problem is that you delay the main thread with your heavy function operation (map) on the flow.
I'm using RoomDao with kotlin coroutines and Flow. What I'm trying to do is collect one Training with all its Exercises with all Repetitions per Exercise. Exercises and Repetitions are Flows, cuz this values can be changed and I want to observe them.
The problem is that when I updating exercises, getTrainingExerciseLinksBy doesn't triggers, and I don't know, why. Here is my code in UseCase:
suspend fun getTrainingWithExercisesAndRepetitionsBy(trainingId: Long): Flow<UiTrainingWithExercisesAndRepetitions> {
/// This method returns Flow<List<TrainingExerciseLink>>
return trainingExerciseLinksRepository.getTrainingExerciseLinksBy(trainingId).flatMapConcat { trainingExerciseLinks ->
trainingExerciseLinks.map { trainingExerciseLink ->
/// This method returns Flow<List<ExerciseRepetition>>
repetitionsRepository.getExerciseRepetitionsBy(trainingExerciseLink.id).map { repetitions ->
/// do some other selects for collecting data about exercise in one training
}.flowOn(Dispatchers.IO)
}.zipFlows()
}.flowOn(Dispatchers.IO)
}
In my ViewModel I'm observing this method like this:
viewModelScope.launch {
useCase.getTrainingWithExercisesAndRepetitionsBy(trainingId)
.distinctUntilChanged()
.collect {
_exercisesListLiveData.value = it.exercises
_trainingListLiveData.value = it.trainingData
}
}
What is wrong with this code?
UPD:
In my DAO I'm using Flows for subscribing on database's updates, like this:
#Dao
abstract class TrainingExerciseLinkDao {
#Query("select * from TrainingExerciseLink where trainingId = :trainingId")
abstract fun getTrainingExerciseLinksBy(trainingId: Long): Flow<List<TrainingExerciseLink>>
}
and ExerciseRepetitionsDao:
#Dao
abstract class ExerciseRepetitionDao {
#Query("select * from ExerciseRepetitionEntity where trainingExerciseId = :trainingExerciseId")
abstract fun getExerciseRepetitionsBy(trainingExerciseId: Long): Flow<List<ExerciseRepetitionEntity>>
}
Actually I found the answer, so maybe somebody will jump in the same gap and this thread will be helpful.
The problem in my code was that I used flatMapConcat. This operator waits emits from original Flow and from flatMapped Flow at one time, so in this case it will trigger callback. To fix this, flatMapLatest should be used. You can read more about difference between this operators here.
So my code now looks like this:
suspend fun getTrainingWithExercisesAndRepetitionsBy(trainingId: Long): Flow<UiTrainingWithExercisesAndRepetitions> {
/// This method returns Flow<List<TrainingExerciseLink>>
/// Here is main change: flatMapConcat -> flatMapLatest
return trainingExerciseLinksRepository.getTrainingExerciseLinksBy(trainingId).flatMapLatest { trainingExerciseLinks ->
trainingExerciseLinks.map { trainingExerciseLink ->
/// This method returns Flow<List<ExerciseRepetition>>
repetitionsRepository.getExerciseRepetitionsBy(trainingExerciseLink.id).map { repetitions ->
/// do some other selects for collecting data about exercise in one training
}.flowOn(Dispatchers.IO)
}.zipFlows()
}.flowOn(Dispatchers.IO)
}
You are using it wrong , as when database updates your getTrainingWithExercisesAndRepetitionsBy does not know,
to get over this issue use flows in your dao like this example as Room supports Flow then
viewModelScope.launch {
viewModel.yourFunctionThatGetsDataFromRepository(trainingId)
.distinctUntilChanged()
.collect {
_exercisesListLiveData.value = it.exercises
_trainingListLiveData.value = it.trainingData
}
}
and if more you can refer this example
I can't figure out how to do a "simple" operation with Room and MVVM pattern.
I’m fetching some data with Retrofit. A “proper” response triggers an observer in the activity and a small part of the response itself is inserted in the database using Room library, wiping all previous values stored and inserting the fresh ones. Otherwise old values are retained on DB.
Just after that, I would like to check for a field in the database, but I’m not able to force this operation to wait until the previous one is completed.
Models
#Entity(tableName = "licence")
data class Licence(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "licence_id")
var licenceId: Int = 0,
#Ignore
var config: List<LicenceConfig>? = null,
.......
//all the others attributes )
#Entity(foreignKeys = [
ForeignKey(
entity = Licence::class,
parentColumns = ["licence_id"],
childColumns = ["licence_reference"],
onDelete = ForeignKey.CASCADE
)],tableName = "licence_configurations")
data class LicenceConfig(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "licence_config_id")
var licenceConfigId: Int,
#ColumnInfo(name="licence_reference")
var licenceReference: Int,
Observer in the activity
loginViewModel.apiResponse.observe(this, Observer { response ->
response?.let {
loginViewModel.insertLicences(response.licence)
}
//here I need to wait for the insertion to end
loginViewModel.methodToCheckForTheFieldOnDatabase()
})
ViewModel
fun insertLicences(licences: List<Licence>) = viewModelScope.launch {
roomRepository.deleteAllLicences()
licences.forEach { licence ->
roomRepository.insertLicence(licence).also { insertedLicenceId ->
licence.config?.forEach { licenceConfiguration ->
roomRepository.insertLicenceConfiguration(
licenceConfiguration.apply { licenceReference = insertedLicenceId.toInt() }
)
}
}
}
}
Room Repository
class RoomRepository(private val roomDao: RoomDao) {
val allLicences: LiveData<List<Licence>> = roomDao.getAllLicences()
suspend fun insertLicence(licence: Licence): Long {
return roomDao.insertLicence(licence)
}
suspend fun insertLicenceConfiguration(licenceConfiguration: LicenceConfig){
return roomDao.insertLicenceConfiguration(LicenceConfig)
}
}
RoomDao
#Dao
interface RoomDao {
#Query("select * from licence")
fun getAllLicences(): LiveData<List<Licence>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertLicence(licence: Licence): Long
#Insert
suspend fun insertLicenceConfiguration(licence: LicenceConfig)
#Query("DELETE FROM licence")
suspend fun deleteAllLicences()
}
Set an observer to the "allLicences" LiveData or directly on that field on DB is not an option because the operations will be performed just after the activity creation and I have to wait until the API response to perform them.
In another project, without Room, I have used async{} and .await() to perform sequential operations while working with coroutines but I can't really make it works here. When I pause the debugger just after the insertion method the value of "allLicences" it's always null but after resuming and exporting the DB the data are properly inserted. I also tried adding .invokeOnCompletion{} after the ViewModel method but with the same result.
Basically I would like to wait for this method to end to do another operation.
Any suggestions?
EDIT
I totally forgot to report the models! Each licence have a list of configurations. When I perform a licence insert I take the autogenerated id, I apply it to the licenceConfig and then I perform the insert for each licenceConfig object (the code in the nested forEach loop of the ViewModel method). The problem seems to be that performing this nested loop breaks the "synchronicity" of the operation
To wait until insertion is completed, you need to move the coroutine creation from insertLicences() to your observer and also make the insertLicences() a suspend function.
loginViewModel.apiResponse.observe(this, Observer { response ->
lifecycleScope.launch {
response?.let {
loginViewModel.insertLicences(response.licence)
}
//here I need to wait for the insertion to end
loginViewModel.methodToCheckForTheFieldOnDatabase()
}
})
and
suspend fun insertLicences(licences: List<Licence>) {
roomRepository.deleteAllLicences()
licences.forEach { licence ->
roomRepository.insertLicence(licence).also { insertedLicenceId ->
licence.config?.forEach { licenceConfiguration ->
roomRepository.insertLicenceConfiguration(
licenceConfiguration.apply { licenceReference = insertedLicenceId.toInt() }
)
}
}
}
}
Alternative Solution
You can shift all of the code present in the observer into ViewModel.
loginViewModel.apiResponse.observe(this, Observer { response ->
loginViewModel.refreshLicenses(response)
})
and in ViewModel
fun refreshLicenses(response:Response?){
viewModelScope.launch{
response?.let {
insertLicences(response.licence)
}
methodToCheckForTheFieldOnDatabase()
}
}
and also make insertLicences as suspend function
suspend fun insertLicences(licences: List<Licence>) {
roomRepository.deleteAllLicences()
licences.forEach { licence ->
roomRepository.insertLicence(licence).also { insertedLicenceId ->
licence.config?.forEach { licenceConfiguration ->
roomRepository.insertLicenceConfiguration(
licenceConfiguration.apply { licenceReference = insertedLicenceId.toInt() }
)
}
}
}
}
Edit: Didn't read your conclusion before I reply but, I still think that your answer lies in coroutines
Using callbacks or promises, won't your function be executed when the insert query is finished?
Callbacks
With callbacks, the idea is to pass one function as a parameter to
another function, and have this one invoked once the process has
completed.
fun postItem(item: Item) {
preparePostAsync { token ->
submitPostAsync(token, item) { post ->
processPost(post)
}
}
}
fun preparePostAsync(callback: (Token) -> Unit) {
// make request and return immediately
// arrange callback to be invoked later
}
I would prefer promises to be honest
Promises
The idea behind futures or promises (there are also other terms these
can be referred to depending on language/platform), is that when we
make a call, we're promised that at some point it will return with an
object called a Promise, which can then be operated on.
fun postItem(item: Item) {
preparePostAsync()
.thenCompose { token ->
submitPostAsync(token, item)
}
.thenAccept { post ->
processPost(post)
}
}
fun preparePostAsync(): Promise<Token> {
// makes request an returns a promise that is completed later
return promise
}
Do your work and when the promise is fullfilled, proceed to data validation.
You can read more about coroutines here
I'm studying Rxjava2 and I'm trying to integrate the Room Library with Rxjava2. The problem is: I have a populated table and every time I login in the app, I need to delete this table and then insert a new content in database. Separately, the delete and insert works fine, but when I try to insert new values after I delete the table content, the delete method deletes all the new values.. (some parts of the code is in kotlin and others in java)
I already tried this: RxJava2 + Room: data is not being inserted in DB after clearAllTables() call, but no success..
DAO
#Dao
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(list:List<Something>)
#Query("DELETE FROM SomethingTable")
fun delete()
#Query("SELECT * FROM SomethingTable")
fun getAll(): Flowable<List<Something>>
My class that calls the DAO (CallDao)
//insert
fun insertInDB(list: List<Something>) {
Completable.fromAction {
dbDAO!!.insert(list)
}.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe()
}
//delete
fun clean() {
Completable.fromAction {
dbDAO!!.delete()
}.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.single())
.subscribe()
}
//search all
fun findAll(): Observable<List<Something>>? {
return Observable.create { subscriber ->
dbDAO!!.getAll()
.subscribeOn(Schedulers.io())
.subscribe {it->
subscriber.onNext(it)
}
}
}
Method that is called when I click in login button
private void clearAndInsertInDB() {
CallDao callDao= new CallDao(getActivity());
//delete all table values
callDao.clean();
Something sm = new Something("test1", "test2");
ArrayList<Something> list = new ArrayList<>();
list.add(sm);
list.add(sm);
//insert new values
callDao.insertInDB(list);
//get all new values in DB
callDao.findAll()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(res -> {
//here gives me a IndexOutOfBoundsException
Log.d("logDebug", res.get(0).getCodeExemple());
});
}
Any corrections in my code is also welcome :) , but the main problem is that the delete method deletes all the new insert values and it should delete only the old values.
You are making two asynchronous calls: one to delete the users and another to insert them again. However, even though you call first the callDao.clean(); method and after that you call callDao.insertInDB(list); , it is not guaranteed that the clean() operation will finish before the insertInDB() operation (because that's how asynchronous calls work).
This is what is happening:
Instead, you should chain your async calls , in such a way that the second one gets called as soon as you know that the first one has already finished.
How to achieve that using RxJava and Completable? Using the andThen operator as stated in this answer
You should modify your clean() and insertInDB() methods to return the Completables, use andThen to chain them, and then subscribe.
Simple example using RxJava and andThen()
FakeDatabase db = Room.databaseBuilder(this, FakeDatabase.class, "fake.db")
.fallbackToDestructiveMigration()
.build();
UserDao userDao = db.userDao();
User user1 = new User("Diego", "Garcia Lozano", "diegogarcialozano#fake.com");
User user2 = new User("Juan", "Perez", "juanperez#fake.com");
User user3 = new User("Pedro", "Lopez", "pedrolopez#fake.com");
List<User> users = new ArrayList<>();
users.add(user1);
users.add(user2);
users.add(user3);
Completable deleteAllCompletable = Completable.fromAction(userDao::deleteAll);
Completable insertUserCompletable = Completable.fromAction(() -> userDao.insertAll(users));
deleteAllCompletable
.andThen(Completable.fromAction(() -> System.out.println("Delete finished")))
.andThen(insertUserCompletable)
.andThen(Completable.fromAction(() -> System.out.println("Insert finished")))
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.single())
.subscribe();
Checking the Logcat after execution, you can see that the operations were executed in the proper order:
2018-11-19 16:07:02.056 10029-10047/? I/System.out: Delete finished
2018-11-19 16:07:02.060 10029-10047/? I/System.out: Insert finished
Afterwards, I checked the content of the database using the tool SQLite Browser and saw that the insert worked properly.
Using #Transaction in the DAO
You can get a better solution for your problem without using RxJava at all. Instead, you can define a Transaction in your DAO using the #Transaction annotation, as explained in this post. It would look something like this:
Dao
#Dao
public abstract class UserDao {
#Transaction
public void deleteAndCreate(List<User> users) {
deleteAll();
insertAll(users);
}
#Query("DELETE FROM User")
public abstract void deleteAll();
#Insert
public abstract void insertAll(List<User> users);
}
Activity
Completable.fromAction(() -> userDao.deleteAndCreate(users))
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.single())
.subscribe();
Checking the table
Personally, I would do it with the #Transaction annotation.
I have following #Dao, that provides Flowable<User> stream:
#Dao
interface UsersDao {
#Query("SELECT * FROM users")
fun loadUsers(): Flowable<List<User>>
}
I want the subscriber of the stream to receive updates of the database as soon as some change happens there. Subscribing to Room's Flowable I will get that feature out of the box.
What I want is following: if database is empty I want to perform a web request and save users into database. The subscriber will automatically receive new updates that had just happened.
Now I want the client of the repository not to be aware all of the initialization logics: all he does - he performs usersRepository.loadUsers(). And all of these magic should take place inside the repository class:
class UsersRepository #Inject constructor(
private val api: Api,
private val db: UsersDao
) {
fun loadUsers(): Flowable<List<User>> {
...
}
}
Of course I can use following approach:
fun loadUsers(): Flowable<List<User>> {
return db.loadTables()
.doOnSubscribe {
if (db.getCount() == 0) {
val list = api.getTables().blockingGet()
db.insert(list)
}
}
}
But I would like to construct the stream without using side-effects (doOn... operators). I've tried composing() but that didn't help much. Been stuck on this for a while.
You could apply some conditional flatMaps:
#Dao
interface UsersDao {
#Query("SELECT * FROM users")
fun loadUsers(): Flowable<List<User>>
#Query("SELECT COUNT(1) FROM users")
fun userCount() : Flowable<List<Integer>>
#Insert // I don't know Room btw.
fun insertUsers(List<User> users) : Flowable<Object>
}
interface RemoteUsers {
fun getUsers() : Flowable<List<User>>
}
fun getUsers() : Flowable<List<User>> {
return
db.userCount()
.take(1)
.flatMap({ counts ->
if (counts.isEmpty() || counts.get(0) == 0) {
return remote.getUsers()
.flatMap({ users -> db.insertUsers(users) })
.ignoreElements()
.andThen(db.loadUsers())
}
return db.loadUsers()
})
}
Disclaimer: I don't know Room so please adapt the example above as the features of it allow.
Assuming your insert() call is async and also handles updates, you could do something like this:
fun loadUsers(): Flowable<List<User>> = userDao.getAllUsers().switchIfEmpty { api.getAllUsers().doOnNext { userDao.insert(it) } }
You could also use some:
fun loadUsers(): Flowable<List<User>> = userDao.getAllUsers().flatMap { it-> if (it.isEmpty()) api.getAllUsers().doOnNext { userDao.insert(it) } else Flowable.just(it)}
Advice:
You should consider the case when the data is stale, therefore you need to go another way around, do a network request and database call at the same time. Whichever observable finish first, take the result and display it. Updating database should be right after network call is done.