In my project I'm using Realm for storing data from API.
Before updating objects to Realm I'd like to check which objects are new (doesn't exist in database) and which objects should be deleted (exist in database, but don't exist in API response).
For checking new objects I'm iterating through API response and using simple Realm query to check which object is new
for(Follower follower: results.data){
Follower followerFromDb = realm.where(Follower.class).equalTo("id", follower.id).findFirst();
if(followerFromDb == null){
Log.d("REALM", "Object is not in the DB");
}
}
My problem is - how to efficiently check which objects should be deleted from the database.
I have a pretty nice trick for deleting objects not in API response, which is that I add an indexed field called #Index private boolean isBeingSaved; to my RealmObject:
public class Thingy extends RealmObject {
//...
#Index
private boolean isBeingSaved;
}
Then as I map the API response to RealmObjects, I set this to true:
ApiResponse apiResponse = retrofitService.getSomething();
Thingy thingy = new Thingy();
thingy.set/*...*/;
thingy.setIsBeingSaved(true);
realm.insertOrUpdate(thingy);
Afterwards, you've set each of these to true for the new elements. So you can do a deletion for all that is false.
realm.where(Thingy.class)
.equalTo(ThingyFields.IS_BEING_SAVED, false)
.findAll()
.deleteAllFromRealm();
Then you'll need to iterate the remaining objects and set their boolean field to false
for(Thingy thingy: realm.where(Thingy.class).findAll()) {
thingy.setIsBeingSaved(false);
}
And it works!
I do not know of a more optimized solution unfortunately, I can clearly see that this is O(N) because of iteration at the end. But you can follow https://github.com/realm/realm-java/issues/762 for bulk update support.
In your particular case, the special flag is isBeingSaved, and I guess you don't want to immediately delete them, but this is how I did it when I needed this functionality.
It sounds like you're database only contains data from the API, and local data is defunkt when an api call response is returned. If that's the case then you can simply delete everything in your database, and add everything from the api response into your Realm.
Realm realm = Realm.getDefaultInstance();
realm.executeTransaction(new Realm.Transaction() {
public void execute(Realm realm) {
realm.deleteAll(); //Delete everything
object.delete(); //Delete specific object
realm.delete(RealmModel) //Delete all of specific type
}
}
Remember to close your realm when you're done.
Related
I have the following code that first check record and if found delete that record and flush changes to the database. However, when I debug, I see that it does not reflect changes to the database when debugger hit the next code block (final Stock stock = new Stock();).
#Transactional
public CommandDTO createOrUpdate(StockRequest request) {
stockRepository.findByBrandUuidAndProductUuid(
request.getBrandUuid(),
request.getProductUuid())
.ifPresent(stock -> {
stockRepository.delete(stock);
stockRepository.flush();
});
final Stock stock = new Stock();
if (request.isOutOfStock()) {
stock.setBrandUuid(request.getBrandUuid());
stock.setProductUuid(request.getProductUuid());
stock.save(stock);
}
return CommandDTO.builder().uuid(stock.getUuid()).build();
}
So, what is the mistake in this approach?
JPA doesn't supports final field.
You can use two alternative solution for immutable class.
use #Immutable at entity class.
change entity class fields having only a getter.
I have implemented by project using Spring-Data-Rest. I am trying to do an update on an existing record in a table. But when I try to send only a few fields instead of all the fields(present in Entity class) through my request, Spring-Data-Rest thinking I am sending null/empty values. Finally when I go and see the database the fields which I am not sending through my request are overridden with null/empty values. So my understanding is that even though I am not sending these values, spring data rest sees them in the Entity class and sending these values as null/empty. My question here is, is there a way to disable the fields when doing UPDATE that I am not sending through the request. Appreciate you are any help.
Update: I was using PUT method. After reading the comments, I changed it to PATCH and its working perfectly now. Appreciate all the help
Before update, load object from database, using jpa method findById return object call target.
Then copy all fields that not null/empty from object-want-to-update to target, finally save the target object.
This is code example:
public void update(Object objectWantToUpdate) {
Object target = repository.findById(objectWantToUpdate.getId());
copyNonNullProperties(objectWantToUpdate, target);
repository.save(target);
}
public void copyNonNullProperties(Object source, Object target) {
BeanUtils.copyProperties(source, target, getNullPropertyNames(source));
}
public String[] getNullPropertyNames (Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
PropertyDescriptor[] propDesList = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<String>();
for(PropertyDescriptor propDesc : propDesList) {
Object srcValue = src.getPropertyValue(propDesc.getName());
if (srcValue == null) {
emptyNames.add(propDesc.getName());
}
}
String[] result = new String[emptyNames.size()];
return emptyNames.toArray(result);
}
You can write custom update query which updates only particular fields:
#Override
public void saveManager(Manager manager) {
Query query = sessionFactory.getCurrentSession().createQuery("update Manager set username = :username, password = :password where id = :id");
query.setParameter("username", manager.getUsername());
query.setParameter("password", manager.getPassword());
query.setParameter("id", manager.getId());
query.executeUpdate();
}
As some of the comments pointed out using PATCH instead of PUT resolved the issue. Appreciate all the inputs. The following is from Spring Data Rest Documentation:
"The PUT method replaces the state of the target resource with the supplied request body.
The PATCH method is similar to the PUT method but partially updates the resources state."
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#customizing-sdr.hiding-repository-crud-methods
Also, I like #Tran Quoc Vu answer but not implementing it for now since I dont have to use custom controller. If there is some logic(ex: validation) involved when updating the entity, I am in favor of using the custom controller.
I need to check some data, whether or not to send a tracking info. This data is saved inside the Realm database. Here is the model:
public class RealmTrackedState extends RealmObject {
#PrimaryKey
private int id = 1;
private RealmList<RealmChat> realmChatsStarted;
private boolean isSupportChatOpened;
private boolean isSupportChatAnswered;
/* getters and setters */
}
The idea is - every chat that is not inside the realmChatsStarted should be tracked and then added to this list. Similar thing for isSupportChatOpened boolean - however because of the business logic this is a special case.
So - I've wrapped this inside one Realm object. And I've wrapped this into few shouldTrack() methods, like this:
#Override
public void insertOrUpdateAsync(#NonNull final RealmModel object, #Nullable OnInsertListener listener) {
Realm instance = getRealmInstance();
instance.executeTransactionAsync(realm -> realm.insertOrUpdate(object), () ->
notifyOnSuccessNclose(listener, instance),
error -> notifyOnErrorNclose(listener, error, instance));
}
#Override
public RealmTrackedState getRealmTrackedState() {
try (Realm instance = getRealmInstance()) {
RealmResults<RealmTrackedState> trackedStates = instance.where(RealmTrackedState.class).findAll();
if (!trackedStates.isEmpty()) {
return instance.copyFromRealm(trackedStates.first());
}
RealmTrackedState trackedState = new RealmTrackedState();
trackedState.setRealmChatsStarted(new RealmList<>());
insertOrUpdateAsync(trackedState, null);
return trackedState;
}
}
#Override
public boolean shouldTrackChatStarted(#NonNull RealmChat chat) {
if (getCurrentUser().isRecruiter()) {
return false;
}
RealmList<RealmChat> channels = getRealmTrackedState().getRealmChatsStarted();
for (RealmChat trackedChats : channels) {
if (trackedChats.getId() == chat.getId()) {
return false;
}
}
getRealmInstance().executeTransaction(realm -> {
RealmTrackedState realmTrackedState = getRealmTrackedState();
realmTrackedState.addChatStartedChat(chat);
realm.insertOrUpdate(realmTrackedState);
});
return true;
}
And for any other field inside RealmTrackedState model happens the same.
So, within the presenter class, where I'm firing a track I have this:
private void trackState(){
if(dataManager.shouldTrackChatStarted(chatCache)){
//track data
}
if(dataManager.shouldTrackSupportChatOpened(chatCache)){
//track data
}
if(dataManager.shouldTrackWhatever(chatCache)){
//track data
}
...
}
And I wonder:
a. How much of a performance impact this would have.
I'm new to Realm, but for me opening and closing a DB looks ... heavy.
I like in this implementation that each should(...) method is standalone. Even though I'm launching three of them in a row - in other cases I'd probably use only one.
However would it be wiser to get this main object once and then operate on it? Sounds like it.
b. I see that I can either operate on synchronous and asynchronous transactions. I'm afraid that stacking a series of synchronous transactions may clog the CPU, and using the series of asynchronous may cause unexpected behaviour.
c. #PrimaryKey - I used this because of the wild copy paste session. Assuming that this class should have only instance - is it a correct way to do this?
ad a.
Realm caches instances so opening and closing instances are not that expensive as it sounds. First time an app is opening a Realm file, a number of consistency checks are performed (primarily does model classes match classes on disk) but next time you open an instance, you don't do this check.
ad b.
If your transactions depend on each other, you might have to be careful. On the other hand, why have multiple transactions? An async transaction will notify you when it has completed which can help me to get the behaviour you except.
ad c.
Primary keys are useful when you update objects (using insertOrUpdate()) as the value is use to decide if you are creating/inserting or updating an object.
I have problem working with realm.
findAll and findAllAsync doesnot return any data from realm.
I am updating realm object from main thread like this.
public void updatePhoto(final int ticketID) {
realm.beginTransaction();
RealmResults ticketPhotos = realm.where(TicketPhoto.class).equalTo("TicketID", ticketID).findAll();`
for (TicketPhoto ticketPhoto : ticketPhotos) {
ticketPhoto.IsModified = true;
}
realm.commitTransaction();
} '$'
At same time one background service is running for every five minutes and keeps checking for any objects having IsModified flag as true. From my background service(IntentService), am using AsyncTask and in doInBackground, am trying to get those IsModified records and I assume realm should pick those records and update with my server. Used the below code to get data from realm.
public RealmResults getTicketPhotosToSave (){
return realm.where(TicketPhoto.class)
.equalTo("IsModified", true)
.findAll();
}
When am still in the same Photo activity where I save photo to realm with IsModified flag as true, realm in background service is not picking those records. But when I destroy the app and just run it again, service is now picking those records. Am not sure if am doing something wrong here.
Its working. thanks for your support
Now I understood that unless we are updating the data on same thread, no need to close realm.
We need to close realm always when we need access to those changes in different thread .
Since background thread always needs access to all objects, we have to close and open realm just before accessing data.
So before accessing/querying data, I am refreshing realm as #Zhuinden suggested ( realm not fetching data)
and then realm.Close(). After this I am creating instance (realm = Realm.getDefaultInstance(); )
I really hate recommending this solution, but you should force a refresh with the package-internal methods after Realm.getInstance() on your IntentService's Realm instance. This current solution I provide works for v1.2.0. Use it only on background threads (primarily your periodically running method).
package io.realm; // <---- this is important
public class RealmRefresh {
public static void refreshRealm(Realm realm) {
Message message = Message.obtain();
msg.what = HandlerControllerConstants.LOCAL_COMMIT;
realm.handlerController.handleMessage(msg);
}
}
And then call
try {
mRealm = Realm.getDefaultInstance();
RealmRefresh.refreshRealm(mRealm);
// do things
} finally {
if(mRealm != null) {
mRealm.close();
}
}
As indicated by http://blog.parse.com/2013/05/30/parse-on-android-just-got-classier/ Parse now supports subclassing of ParseObject. My issue arrises when I try to "pin" my retrieved List of Post objects from my query to the cache.
Post is a subclass of ParseObject
ParseQuery<Post> query = ParseQuery.getQuery(Post.class);
query.orderByDescending("updatedAt");
// Gets all posts from Parse.
query.findInBackground(new FindCallback<Post>() {
#Override
public void done(final List<Post> posts, ParseException e) {
if (e != null) {
// There was an error or the network wasn't available.
return;
}
// Nothing to update.
if (posts.size() == 0) {
return;
}
// Add the latest Posts from this query to the local cache.
Post.pinAllInBackground(POST_CACHE_LABEL, posts);
updateMapFromCache();
}
});
ParseObject.pinAllInBackground() only accepts List<ParseObject> not List<?> which is now creating an issue for me. Does anyone have a solution to this besides iterating through List<Post> and pinning them individually?
Thanks for your help!
This is not a answer, but I didn't have enough repo to comment.
I am having this same problem, once a object of subclass of PraseObject is stored in local data store.
I am able to retrieve it while the application is running, once application is restarted, what I get from a query similar to above is a list of ParseObject's and not a list of my custom object.
For now what I did is, created a custom parser to recreate a object of my custom implementation. But this defeats the purpose of what is indicated in "http://blog.parse.com/2013/05/30/parse-on-android-just-got-classier/".