Android : How to OnConflictStrategy.REPLACE but preserve one particular field - java

I have a DAO in Android Room, with OnConflictStrategy.REPLACE on insert, there's a boolean field downloaded, which gets changed to true if this object was downloaded by the user, I want to replace the whole object onConflict, but preserve the state of this field(downloaded) in the db.
public interface DAOTemplate<T> {
#Insert(onConflict = OnConflictStrategy.REPLACE)
#NonNull
void insert(T... messages);
#Delete
void delete(T message);
#Update
void update(T message);
}

A bit late for the answer but I just did the same in my code. Your approach should be not to replace the object but update the existing one with the fields you need to update and just keep the downloaded.
For this use OnConflictStrategy.FAIL. Surround your insert() with try/catch block and then in catch put your update() call. I am using RXJava + Kotlin to so but you can implement this logic with any other async approach.
fun saveItem(item: Item): Single<Unit>? =
Single.fromCallable {
try {
dao.save(item)
} catch (exception: SQLiteConstraintException) {
dao.updateModel(item.id)
} catch (throwable: Throwable) {
dao.updateModel(item.id)
}
}.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread())
And in DAO I am implementing update as follows:
#Query("UPDATE name_of_your_table SET quantity = quantity + 1 WHERE id = :id")
fun updateModel(id: String)
Here I am increasing the quantity but you can pass fields to update as params and use SET to set all fields you need to update.

I also needed a solution to preserve some fields when updating room db. Thank you very much #Rainmaker, I extended #Rainmaker solution. I know it is too late but I want to drop my solution here for future needs.
override suspend fun upsertItem(item: YourItem) {
try{
itemDao.insertItem(item)
}catch (exception: SQLiteConstraintException){
val oldItem = itemDao.getItemByIdOneShot(item.id)
itemDao.updateItem(item.apply {
downloaded = oldItem.downloaded
// you can add more fields here
})
}catch (throwable: Throwable){
val oldItem = itemDao.getItemByIdOneShot(item.id)
itemDao.updateItem(item.apply {
downloaded = oldItem.downloaded
// you can add more fields here
})
}
}
Here is my Dao
#Insert(onConflict = OnConflictStrategy.ABORT)
abstract suspend fun insertItem(item: YourItem)
#Update
abstract suspend fun updateItem(item: YourItem)
#Query("select * from items where id= :id")
abstract fun getItemByIdOneShot(id: Int): YourItem

Related

How to save data in room with coroutines in java?

I have response from backend in java file:
this is kotlin file
class ResponseData {
val data: List<DataRoom>? = null
}
this is my code in java, saving response
repo.getValue().saveDataToRoom(response.body().getData());
saveDataToRoom
GlobalScope.future {
insertAllDataUseCase.build(data)
}
class InsertAllDataUseCase (private val dataDao: DataDao):
BaseUseCase<List<DataRoom>, Unit>() {
override suspend fun create(params: List<DataRoom>) {
dataDao.setNewDataListWithDelete(params)
}
}
dao
#Transaction
open suspend fun setNewDataListWithDelete(datas: List<DataRoom>) {
deleteAllData()
insertAllData(data)
}
#Query("DELETE FROM data")
abstract suspend fun deleteAllData()
#Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insertAllData(dataItems:List<DataRoom>)
It doesn't work, it doesn't save there. I check with getting the data, checking App Inspection, it is just empty. As well I can log response from backend, it works. Why it does not work?
Try removing the abstract and the open, your DAO is an interface, right? If not, that might be the issue, but I don't see any problem with your #Transaction there. Check this example.
Also, what's the context behind the usage of GlobalScope.future? I think you should be using GlobalScope.launch, but even so, if you are doing this in a ViewModel I would recommend that you use the actual correct way that is viewModelScope.launch {...

Get user by userId in ROOM

I want to take event by id but it returns unit.
Entity:
#Dao
interface EventDao {
#Query("SELECT * FROM event_table WHERE eventId= :myeventId")
fun getEventByEventId(myeventId: Int): LiveData<Event>
}
Repository:
class EventRepository(private val eventDao: EventDao){
fun getEventById(myeventId: Int): LiveData<Event>{
return eventDao.getEventByEventId(myeventId = myeventId)
}
}
Viewmodel:
class EventViewModel (application: Application): AndroidViewModel(application) {
private val readEventById = MutableLiveData<LiveData<Event>>()
fun getEventById(eventId: Int) {
viewModelScope.launch(Dispatchers.IO) {
readEventById.postValue(eventRepository.getEventById(eventId))
}
}
}
I am calling it on user fragment:
lifecycleScope.launch {
val event = eventViewModel.getEventById(currentUserId)
}
but it returns unit. How can i return event by userId?
In your ViewModel class, you should include a public LiveData<Event> value that returns the readEventById live data object:
val selectedEvent: LiveData<Event>
get() = readEventById
Then, in the user Fragment, you should instead add an Observer on eventViewModel.selectedEvent. When you call eventViewModel.getEventById(currentUserId), you don't worry about the result. Instead, the Observer will let you know the LiveData<Event> was updated and you can handle the value this way.
This is the proper approach since you're getting the data from the database asynchronously.
We can use withContext for returning values from a coroutine scope, it is defination from documentation : "Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns the result." for details visit the documentation withContext
in your case you can use like this
suspend fun getEventById(eventId: Int): LiveData<Event> {
return withContext(Dispatchers.IO) {
eventRepository.getEventById(eventId)
}
}

"Show deleted" checkbox not working as intended

I've implemented a soft delete behavior in my imaginary Video rental app, and now I am trying to implement a way to reactivate my "deleted" customers, but I can't get my approach to work, surely something simple, but google did not let me find the answer, so here I am.
Here is an excerpt from my repo interface (JpaRepository):
#Query("select m from Movie m where m.isDeleted = true")
List<Movie> findAllIsDeleted();
#Override
#Query("select m from Movie m where m.isDeleted=false")
List<Movie> findAll();
#Modifying
#Transactional
#Query("update Movie m set m.isDeleted=true where id=?1")
void softDelete(Long id);
In my service class I have:
public List<Movie> findAllMovies(String filterText) {
if (filterText == null || filterText.isEmpty()) {
return movieRepository.findAll();
} else {
return movieRepository.search(filterText);
}
}
public List<Movie> findAllDeletedMovies() {
return movieRepository.findAllIsDeleted();
}
And an excerpt from my listview class looks like:
...
Checkbox showDeleted = new Checkbox("Show deleted movies", e -> {
updateList();
Notification.show(e.getValue().toString());
});
...
private void updateList() {
if (showDeleted.getValue() == true) {
grid.setItems(service.findAllDeletedMovies());
}
grid.setItems(service.findAllMovies(filterText.getValue()));
}
But obviously there is something wrong in the listener part, or there is a silent "sure we want to help feature" that I am not aware of. Because the updateList function is not executed. What have I missed?
The problem lies in the implementation of your updateList method.
No matter if the value of the checkbox is true, at the end it always sets the items again that are returned by service::findAllMovies.
move the last statement into an else block and it should work.
private void updateList() {
if (showDeleted.getValue()) { // btw, if(boolValue == true) is redundant. just do if(boolValue)
grid.setItems(service.findAllDeletedMovies());
} else {
grid.setItems(service.findAllMovies(filterText.getValue()));
}
}
I Don’t Know you data table design,
but you can try this
"select m.* from Movie m where m.isDeleted = true"

Android Room database not informing me for live data changes

I am using Room Database in my app. I am binding a list with a table in db and looking for update this list in realtime when any changes (insert/update) occur in specific table of room database.
My Dao class
#Dao
interface RecordsDao {
#Query("Select * from Records WHERE app_uuid = :appUUID")
fun getRecords(appUUID: String): List<Records>
#Insert
fun insertRecord(records: Records): Long
#Query("UPDATE Records SET detect_count = detect_count + 1 WHERE time_slot = :timeSlot and other_uuid = :otherUUID and app_uuid = :appUUID")
fun updateRecord(timeSlot: Int, appUUID: String, otherUUID: String)
#Query("Select * from Records WHERE app_uuid = :appUUID Order by detect_count desc")
fun getLiveRecords(appUUID: String): LiveData<List<Records>>
}
Class for binding list
dbHelper?.recordsDao()?.getLiveRecords(sharedPrefUtil?.getAppStringPrefByKey(R.string.key_uuid)!!)
?.observe(this, object : Observer<List<Records>> {
override fun onChanged(t: List<Records>?) {
Log.e("MyApp", "Chaged fired");
}
})
DBhelper Class
#Database(entities = arrayOf(Records :: class), version = 1)
abstract class DBHelper : RoomDatabase() {
abstract fun recordsDao(): RecordsDao
companion object {
fun getDBHelper(context: Context): DBHelper {
return Room.databaseBuilder(context, DBHelper::class.java, "MyAppPackageName")
.allowMainThreadQueries().build()
}
}
}
Foreground service
if (!dbHelper.recordsDao().isRecordExist(
slot,
sharedPrefUtil?.getAppStringPrefByKey(R.string.key_uuid)!!,
entry
)
) {
Log.e("MyApp", entry + " record does not exist in db. INserting")
val record = Records(
0,
slot,
sharedPrefUtil?.getAppStringPrefByKey(R.string.key_uuid)!!,
entry,
1,
currentDt
)
dbHelper.recordsDao().insertRecord(record)
} else {
Log.e("MyApp", entry + " record exist in db. UPdating")
dbHelper.recordsDao().updateRecord(
slot,
sharedPrefUtil?.getAppStringPrefByKey(R.string.key_uuid)!!,
entry
)
}
}
Here in in service i am updating/inserting records in table and listening for data changed in another class where i am binding list to table. Data insert/update successfully occurring but class that listen for changes in table does not get any response.
what else i am missing ? Please suggest

Fetching data from local and remote simultaneously using RxJava

So I'm a beginner with RxJava but here's what I want to accomplish:
MainViewModel talks to the Repository. Repository has both LocalDataStore (that talks with the database) and RemoteDataStore (Retrofit) Both are different implementations of interface DataStore).
What I want to achieve is have a single call fetchData from the Repository that returns an Observable but:
it takes it from the RemoteDataStore at first
after fetching every single thing (onNext()), it inserts it into the database
if it fails, it returns results from the LocalDataStore.
However, I don't know how to implement this logic. Subscription happens on the ViewModel's end, but I cannot really change the observable to LocalDataStore from Repository end (?). Upserting data into the database also returns an Observable (Single to be precise) and for it to work it needs a subscription.
Could someone explain it to me or point me in a good direction?
My code (problem in repository comments):
Remote data store
override fun getData(): Observable<SomeData> = api
.getData(token)
.flatMapIterable { x -> x }
Local data store
override fun saveData(data: SomeData): Single<SomeData> {
return database.upsert(data)
}
Repository
fun getData(): Observable<SomeData> {
return
remoteDataStore.getData()
.doOnError {
localDataStore.getData() //? this will invoke but nothing happens because I'm not subscribed to it
}
.doOnNext {
saveData(it) //The same as before, nothing will happen
}
}
ViewModel
override fun fetchData() {
repository.getData()
.observeOn(androidScheduler)
.subscribeOn(threadScheduler)
.subscribe(
{ data: SomeData ->
dataList.add(data)
},
{ throwable: Throwable? ->
handleError(throwable)
},
{
//send data to view
},
{ disposable: Disposable ->
compositeDisposable.add(disposable)
}
)
}
Thank you for your time.
You need to use one of onErrorResumeNext methods. I would also suggest to change your stream type from Observable to Single as nature of your data seems like Get data once or throw error. It's just a good API design.
In your particular case I would implement the repository this way:
class RepositoryImpl #Inject constructor(private val localRepository: Repository, private val remoteRepository: Repository) : Repository {
override fun getData(): Single<Data> = remoteRepository.getData()
.onErrorResumeNext { throwable ->
if (throwable is IOException) {
return localRepository.getData()
}
return Single.error(throwable)
}
}
You might ask why only catch IOException? I usually handle only this exception to not miss anything critical but only unimportant network errors. If you will catch every exception you might miss, for example, a NullPointerException.
onErrorResumeNext is what you're looking for. doOnError invokes a side-effecting action, doesn't replace the original Observable with another one.

Categories