Android Room database not informing me for live data changes - java

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

Related

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)
}
}

Is it possible to place Transformations.switchMap in a repository?

I'm attempting to implement a LiveData reference to data collected via gps and bluetooth, that is also grouped by a foreign key. I don't understand why the Transformations.switchMap doesn't trigger once I create a new foreignkey.
I've moved both, the observer and foreignkey creation around, the LiveData always returns null.
Both, the Database and the Repository are Singletons.
Activity.java
public class Activity extends AppCompatActivity {
...
private ViewModel mViewModel;
#Override
onCreate {
...
mViewModel = ViewModelProviders.of( this ).get( ViewModel.class );
init();
}
private class ObserverManager {
private List<Observer> observers = new ArrayList<>();
private List<LiveData> livedata = new ArrayList<>();
public void registerObserver( TextView view, int hid, int uid ) {
Observer observer = null;
LiveData ld = null;
ld = mViewModel.getLatestData();
observer = ( Observer<Float> ) ( #Nullable final Float data ) -> {
// String formatting...
};
observers.add( observer );
livedata.add( ld );
ld.observeForever( observer );
}
public void logValue( int index ) {
Log.d( "OBSERVERMANAGER", String.valueOf( livedata.get( index ).getValue() ) );
}
}
final ObserverManager mObserverManager = new ObserverManager();
}
During init() The foreignkey is inserted and updated, then the observer is attached dynamically.
The service logs the correct foreignkey and inserts values to eData entity, but the Transformations.swapMap never updates, or shows a value other than null.
ViewModel.java
...
private LiveData<Integer> mLiveActivityId;
private LiveData<Float> mLatestData;
ViewModel( Application application ) {
...
mLiveActivityId = mRepository.getLiveActivityId();
mLatestData = mRepository.getLatestData();
}
public LiveData<Float> getLatestData() {
return mLatestData;
}
Repository.java
...
private LiveData<Integer> mLiveActivityId;
private LiveData<Float> mLatestData;
Repository( Application application ) {
...
mLiveActivityId = mDataDao.getLiveActivityId();
mLatestData = Transformations.switchMap( mLiveActivityId, aid -> mDataDao.getLatestData( aid, 0 ) );
}
...
LiveData<Float> getSpeedGPSLatest() {
return mSpeedGPSLatest;
}
DataDao.java
#Transaction
#Query( "SELECT id FROM eActivity WHERE active = 1" )
LiveData<Integer> getLiveActivityId();
#Transaction
#Query( "SELECT data FROM eData WHERE aid = :aid AND source = :source AND time = " +
"( SELECT MAX(time) time FROM eData WHERE aid = :aid AND source = :source )" )
LiveData<Float> getLatestData( int aid, int source );
Is it even possible apply Transformations in the repository? So far I have only seen examples with them applied in the ViewModel. Unfortunately, due to some data sharing the same entity with a type field, that would mean I have to pass the LiveData objects back from a function in the repository, which I thought is wrong.
I have also read that switchMap creates a new LiveData object, would this somehow affect how ViewModel cannot read the new object in the repository?
If more code is required to understand the problem, just ask. Am totally stumped on this.
Simply put, yes you can. The Transformations.switchMap() was not the issue with LiveData not updating. Still cannot figure out how to communicate to the room db from a service, ie the follow up question Insert to room in service not updating LiveData in activity, but also solved that by doing things differently.

Save and Update in jpa CRUD repository

I have one data already saved in my databse based on my repository and service.i want to save another data with postman by changing only the player id.But it is not create a new entity data.it update the existing entity data.My question is how to update a data by my service when it finds a existing id.But when it finds a new id it will save a new data into databse.
This is my repo:
#Repository
#Transactional
public interface CricketPlayerRepository extends CrudRepository<CricketPlayer,String> {
Optional<CricketPlayer> findCricketPlayerByName(String name);
}
This is my service:
#Service
public class CricketPlayerService {
private CricketPlayerRepository cricketPlayerRepository;
public CricketPlayerService(CricketPlayerRepository cricketPlayerRepository) {
super();
this.cricketPlayerRepository = cricketPlayerRepository;
}
public CricketPlayerService() {
}
public Optional<CricketPlayer> getPlayerByName(String name){
return cricketPlayerRepository.findCricketPlayerByName(name);
}
public CricketPlayer save(CricketPlayer cricketPlayer){
Optional<CricketPlayer> id = cricketPlayerRepository.findById(cricketPlayer.getPlayerId());
if (id.isPresent()){
//code here
}
// if (entityManager.isNew(cricketPlayer)) {
// em.persist(cricketPlayer);
// return cricketPlayer;
// } else {
// return em.merge(cricketPlayer);
// }
return cricketPlayerRepository.save(cricketPlayer);
}
public Iterable<CricketPlayer> findAllPlayers() {
return cricketPlayerRepository.findAll();
}
public Optional<CricketPlayer> findPlayersById(String id) {
return cricketPlayerRepository.findById(id);
}
}
save and update operations in hibernate ( and other frameworks) are based on id value. if an id exists merge (update) entity and otherwise save new instance. So it cannot be done in this context.
1)If PlayerId is primary key id, then you would have called merge(entity). If PlayerId is present it will update else it will create new record.
2)If PlayrerId is not primary key id. Best practice is to avoid PlayerId as primary key.
In postman you should pass database table primary key id along with PlayerId.
Then you call merge(entity). It will take care of create or update based on primary key
id is null or not.
for example below if you have passed primary key id in request.
Entity e = new Entity();
if (entityFromRequest.getId() != null){ //this is the db id from request
//get the entity from db and then change the state
e = entity from db by using id
}
e.setPlayerId = entityFromRequest.getPlayerId
merge(e); // it will create or update record

Android Room Generic DAO

Good day Stack, i'm working on an Android project that uses Android's Room 1.0.0 Alpha 5, the main issue that i'm facing is that every time i need to call one of the DAO from room i need to do something like this:
Activity.java:
...
AppDatabase db = Room.databaseBuilder(context, AppDatabase.class, "Storage").build();
Table1 table = new Table1();
table.setId(1);
table.setName("Hello");
new AccessDB().execute(1);
/* Generic AccessDB needed */
private class AccessDB extends AsyncTask<Integer,Void,List<Table1>> {
#Override
protected List<Table1> doInBackground(Integer... param) {
switch(param[0]) {
case 1:
return db.Table1DAO().create();
case 2:
return db.Table1DAO().read();
}
return new ArrayList<>();
}
#Override
protected void onPostExecute(List<Table1> list) {
processData(list);
}
}
...
I know that i can access Room DB from the main thread, and that would shrink the code, but i think that's not a good practice since that would lock the activity every time it has to handle data.
So if i need to insert or read data from "Table2" i would have to do the same all over again, it would be great if i could turn the entity types into generics like "T" or something like that and then make a generic "AccessDB".
But since i'm not too familiar with Java... I'm currently struggling with this.
Here is some other code of the instances.
AppDatabase.java:
#Database(entities = {Table1.class, Table2.class, Table3.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract Table1DAO Table1DAO();
public abstract Table2DAO Table2DAO();
public abstract Table3DAO Table3DAO();
}
Table1.java:
#Entity
public class Table1 {
/* setters & getters */
#PrimaryKey(autoGenerate = true)
private int id;
private String name;
}
Table1DAO.java:
#Dao public interface Table1DAO {
#Query("SELECT * FROM Table1")
List<Table1> read(Table1 table);
#Insert(onConflict = OnConflictStrategy.REPLACE)
List<Long> create(Table1... table);
}
Thank you all for your help.
You can use inheritance and create a BaseDao which will be implemented by all your child Dao. This way you won't need to write the common methods again and again.
interface BaseDao<T> {
/**
* Insert an object in the database.
*
* #param obj the object to be inserted.
*/
#Insert
fun insert(obj: T)
/**
* Insert an array of objects in the database.
*
* #param obj the objects to be inserted.
*/
#Insert
fun insert(vararg obj: T)
/**
* Update an object from the database.
*
* #param obj the object to be updated
*/
#Update
fun update(obj: T)
/**
* Delete an object from the database
*
* #param obj the object to be deleted
*/
#Delete
fun delete(obj: T)
}
Read more about it: https://gist.github.com/florina-muntenescu/1c78858f286d196d545c038a71a3e864#file-basedao-kt
Original credits to Florina.
I played around a bit with the answer of Akshay Chordiya, but needed two additions:
ability to insert/update List
return values to monitor insert/update success
Here is what I came up with:
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Update
/**
* List of all generic DB actions
* All use suspend to force kotlin coroutine usage, remove if not required
*/
#Dao
interface BaseDao<T> {
// insert single
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(obj: T?): Long
// insert List
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(obj: List<T>?) : List<Long>
// update List
#Update
suspend fun update(obj: List<T>?): Int
}
#Dao
interface MyObjectDao : BaseDao<MyObject> {
#Query("SELECT * from $TABLE_NAME WHERE $COL_ID = :id")
suspend fun getById(id: Long): MyObject
}
Can then be called like:
val ids = MyObjectDao.insert(objectList)

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

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

Categories