I'm using Realm 3.4.0 and having one object that should be a singleton. The database is synced.
Here is a simplified version of the code: test if that object exist, add it if it doesn't exist. What is the correct way to do that? (copyToRealmOrUpdate shouldn't be needed or is there any other reason why the instance becomes null?)
#PrimaryKey
public long id = 1;
public static PlannerManager getInstance(Realm realm) {
PlannerManager ourInstance = null;
if (instanceLock == null)
instanceLock = new ReentrantLock();
try {
instanceLock.lock();
realm.refresh(); // Force getting all data from online database
ourInstance = realm.where(PlannerManager.class).findFirst();
if (ourInstance == null) { // The item doesn't exist
realm.beginTransaction();
ourInstance = realm.copyToRealm(new PlannerManager()); // Crashes sometimes with the error that an object with primary ID already exists
realm.commitTransaction();
}
} finally {
instanceLock.unlock();
}
return ourInstance;
}
Relevant part of the stacktrace
2:9.446 Primary key value already exists: 1 .
(/Users/cm/Realm/realm-java/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp:189) io.realm.exceptions.RealmPrimaryKeyConstraintException: Primary key value already exists: 1 .
(/Users/cm/Realm/realm-java/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp:189)
at io.realm.internal.OsObject.nativeCreateNewObjectWithLongPrimaryKey(Native Method)
at io.realm.internal.OsObject.createWithPrimaryKey(OsObject.java:198)
at io.realm.Realm.createObjectInternal(Realm.java:1052)
at io.realm.PlannerManagerRealmProxy.copy(PlannerManagerRealmProxy.java:1279)
at io.realm.PlannerManagerRealmProxy.copyOrUpdate(PlannerManagerRealmProxy.java:1268)
at io.realm.DefaultRealmModuleMediator.copyOrUpdate(DefaultRealmModuleMediator.java:438)
at io.realm.Realm.copyOrUpdate(Realm.java:1660)
at io.realm.Realm.copyToRealm(Realm.java:1072)
at com.myapp.internal.PlannerManager.getInstance(PlannerManager.java:857)
Thanks!
Your logic is actually slightly wrong. By doing the query outside the transaction, the background sync thread might put data into Realm between you do the query and begin the transaction. Transactions will always move the Realm to the latest version, so calling refresh() is also not needed. Your logic should be something like:
realm.beginTransaction();
ourInstance = realm.where(PlannerManager.class).findFirst();
if (ourInstance == null) {
ourInstance = realm.createObject(PlannerManager.class, 1);
realm.commitTransaction();
} else {
realm.cancelTransaction();
}
Note, that using realm.copyToRealm() will cause changes from other devices to be overridden, so for initial data like this, it is safer to use createObject as changes to individual fields will then merge correctly. Using copyToRealm() is the same as actually setting all fields to the initial value.
E.g if you have two devices A and B that are both offline:
A starts app and creates the default PlannerManager.
A modifies a field in PlannerManager.
B starts the app, but since A is offline, it doesn't know that PlannerManager is already created, so it also creates the default PlannerManager.
A and B both go online.
Due to how Realm uses "last-write-wins", B will now override all changes done by A, since using copyToRealm is the equivalent of setting all fields manually.
Using Realm.createObject() uses a special "default" instruction for all fields, that automatically loses to any explicit set like the one used when using normal Java setters (and which copyToRealm uses).
Related
I used the lifecycle callback onCreate to fetch data like below
mWeOutViewModel.getPlaceListLiveData()
.observe(this, weOutItemViewModels -> {
AppLogger.i(getCustomTag() + "adding items " + weOutItemViewModels.size());
if (weOutItemViewModels != null && weOutItemViewModels.size() > 0)
mWeOutListAdapter.addToExisting(weOutItemViewModels);
});
As you can see the AppLogger output the initial size which is 0 when the fragment is displayed, then I fetch the data and call postValue (setValue crashes the app and it expected because I fetch data from the internet using a background thread). So I call post value like below :
private void updatePlaces(List<WeOutGroupedViewModels> weOutGroupedViewModels) {
List<WeOutGroupedViewModels> oldList = placeMutableLiveData.getValue();
oldList.addAll(weOutGroupedViewModels);
AppLogger.i(TAG +" updating places "+oldList.size());
placeMutableLiveData.postValue(oldList);
}
As you can see the other AppLogger before postValue, the size of the list is displayed(not empty), but nothing happens until the app crashes and nothing is shown in the logs. I have no ways of debugging since even on debug mode nothing happens. The post value doesn't trigger the observer.
I initialize the mutableLivedata like this :
private final MutableLiveData<List<WeOutGroupedViewModels>> placeMutableLiveData = new MutableLiveData<>();
and access like this :
public LiveData<List<WeOutGroupedViewModels>> getPlaceListLiveData() {
return placeMutableLiveData;
}
Event when I make the livedata public to access directly the livedata, there is no change (just in case someone thinks that's is where the issue comes from)
Instead of placeMutableLiveData.postValue(oldList);
I recommend using
placeMutableLiveData.postValue(Collections.unmodifiableList(new ArrayList<>(newList));
That way, the next time you access this list, you won't be able to mutate it in place, which is a good thing. You're not supposed to mutate the list inside a reactive state holder (MutableLiveData).
So theoretically it should look like this:
private void updatePlaces(List<WeOutGroupedViewModels> weOutGroupedViewModels) {
List<WeOutGroupedViewModels> newList = new ArrayList<>(placeMutableLiveData.getValue());
newList.addAll(weOutGroupedViewModels);
AppLogger.i(TAG +" updating places "+newList.size());
placeMutableLiveData.postValue(Collections.unmodifiableList(newList));
}
I need to delete elements from the database.
That's my code and I don't know if I am right
realm.executeTransaction(
realm1 -> {
RealmResults<UserWordRealm> result = realm1.where(UserWordRealm.class).equalTo("id",id).findAll();
result.deleteAllFromRealm();
}
);
}
In the RealmObject class, the id(PrimaryKey) field must uniquely identify the object. Therefore, there cannot be more than one element with the same id. Using findFirst() instead of findAll() may solve your problem.
To delete an object from a realm, use either the dynamic or static versions of the deleteFromRealm() method of a RealmObject subclass.
realm.executeTransaction(r -> {
UserWordRealm userWordObject = r.where(UserWordRealm.class).equalTo("id", id).findFirst();
userWordObject.deleteFromRealm();
// discard the reference
userWordObject = null;
});
I have Boolean booleanCheckAvailabilityData to check availability data in my activity to create add/remove favorite. then i create
dataFavoriteMovieById = favoriteMovieViewModel.getAllFavoriteMovieById(idMovie);
to get data by id. so i make conditional statement to check avaiability data then put the result to boolean and i use the boolean later to add or remove the favorite.
if (dataFavoriteMovieById == null) {
booleanCheckAvailabilityData = false;
} else {
booleanCheckAvailabilityData = true;
}
In the first run, it work. my dataFavoriteMovieById is null
But, after i add or remove favorite. it always always contains data (RoomTrackingLiveData).
How can i solve this...
my code link : https://github.com/komangss/Submission-Menjadi-Android-Developer-Expert/blob/master/app/src/main/java/com/dicoding/submissionmade2_1/activity/DetailMovieActivity.java
I played with your app (thanks for providing a github link) and here are my results.
Latest app version
Your latest implementation doesn't produce an NPE anymore since you use getAllFavoriteMovieById in a more consistent way. You no longer initialize a LiveData instance in FavoriteMovieRepository by yourself but delegate it to Room to do it for you. So, you won't get an NPE since Room will always create a list to return results. If there're no items, it will return an empty list. So, you can safely remove a try/catch here:
try {
favoriteMovieViewModel.getAllFavoriteMovieById(idMovie).observe(this, new Observer<List<FavoriteMovie>>() {
#Override
public void onChanged(List<FavoriteMovie> favoriteMovies) {
booleanCheckAvailabilityData = favoriteMovies.size() != 0;
}
});
} catch (NullPointerException e) {
Log.d("ini bug nya", e.getMessage());
}
Original app version
In addition to what #Paul Ost said about how favoriteMovieViewModel should be used properly (by listening to it, not using it directly), I will explain why you actually had an NPE.
In that version, you were running into a NullPointerException because you returned the favoriteMovieById LiveData before it was actually initialized in your GetFavoriteMovieByIdAsyncTask.
So, here what was happening in detail. First, once your DetailMovieActivity had been created, favoriteMovieViewModel called getAllFavoriteMovieById() as below:
DetailMovieActivity.java
...
favoriteMovieViewModel = ViewModelProviders.of(this).get(FavoriteMovieViewModel.class);
dataFavoriteMovieById = favoriteMovieViewModel.getAllFavoriteMovieById(idMovie);
...
FavoriteMovieViewModel.java
FavoriteMovieViewModel instance, in turn, delegated the call to FavoriteMovieRepository instance as below:
public LiveData<List<FavoriteMovie>> getAllFavoriteMovieById(int idMovie) {
return repository.getFavoriteMovieById(idMovie);
}
FavoriteMovieRepository.java
Finally, getFavoriteMovieById started a GetFavoriteMovieByIdAsyncTask and returned favoriteMovieById:
public LiveData<List<FavoriteMovie>> getFavoriteMovieById(int id_movie) {
new GetFavoriteMovieByIdAsyncTask(favoriteMovieDao).execute(id_movie);
return favoriteMovieById;
}
But that's wrong, since your favoriteMovieById was set to null by default, and so on the first run, you were always getting it.
Your AsyncTask was eventually setting a non-null value, but it was too late:
...
private static class GetFavoriteMovieByIdAsyncTask extends AsyncTask<Integer, Void, Void> {
...
#Override
protected Void doInBackground(Integer... integers) {
FavoriteMovieRepository.favoriteMovieById = favoriteMovieDao.getFavoriteMovieById(integers[0]);
return null;
}
}
...
From what I can see in your code - getAllFavoriteMovieById works as expected. The thing is - you are using LiveData as a return type of getAllFavoriteMovieById thus it returns not the value itself but a LiveData wrapper. But if you will try to observe this LiveData object you will(presumably since I haven't seen relevant code) receive null instead of favourite value. The only correct place to assign value to your booleanCheckAvailabilityData inside this observer(depending on your DAO code of course).
favouriteMovieViewModel.getAllFavoriteMovieById().observe(this, Observer { data ->
if (data == null) {
booleanCheckAvailabilityData = false;
} else {
booleanCheckAvailabilityData = true;
}
})
Something like that(once again it depends on your DAO code and getAllFavoriteMovieById implementation)
Hope it helps.
In ROOM Try Deleting the old TABLE before inserting the new data. In that case the old data will be deleted as we are deleting the old data
I have an ObjectBoxLiveData object with a query that is set at runtime:
private ObjectBoxLiveData<MyObject> myObjectLiveData;
public ObjectBoxLiveData<MyObject> getMyObjectLiveData(Box<MyObject> myObjectBox, String filterTerm)
{
if (myObjectLiveData == null)
myObjectLiveData = new ObjectBoxLiveData<>(myObjectBox.query().equal(MyObject_.filterProperty, filterTerm).build());
return myObjectLiveData;
}
But I also need to be able to change the filterTerm at runtime. My thinking is that I can make a private String currentFilterTerm; object in MyViewModel to see if I need to update the filter term in the LiveData object, but is there a correct way to update the filter term? I worry that setting myObjectLiveData = new ObjectBoxLiveData<> again will leave a memory leak for the previously defined myObjectLiveData or anything tied to it, but I don't see any graceful way to dispose of it or update the query once defined. Is there a way to redefine my query once defined?
I'm using Couchbase Lite with Android. I have a document on which I need to add a Document.ChangeListener (to update UI whenever this document change).
I add it but it seems to be removed "automagically" and I can't find out why. I have a method "attachDocumentChangeListener" that I call :
public void attachDocumentChangeListener(String documentId, final DocumentChangeListener<TData> listener) {
getDefaultDatabase().getDocument(documentId).addChangeListener(listener);
}
I don't touch my device anymore and I make an update on the document via another device. This should trigger the ChangeEvent but does not seem to happen.
I noticed that when the document is updated, the document has no ChangeListener (even if I added one juste before). To check when does the ChangeListener is removed I added a breakpoint to the function removeChangeListener(ChangeListener changeListener) from Document class but is has never been called and the field changeListeners was still empty when the revision was updated.
I added another breakpoint, a field watchpoint on changeListeners. Here is a screenshot of the configuration of this breakpoint :
And I repeated the operation (updating the document with another device) but I didn't see anything but the logs that show a ChangeListener has been removed (without knowing how)
{com.couchbase.lite.Document#6.888}.changeListeners will be accessed at com.couchbase.lite.Document.addChangeListener(Document.java:169)
size : 0, ID : 8980450c-5ddb-4181-a7bf-3e162c67a68f
{com.couchbase.lite.Document#6.888}.changeListeners will be accessed at com.couchbase.lite.Document.addChangeListener(Document.java:169)
size : 1, ID : 8980450c-5ddb-4181-a7bf-3e162c67a68f
{com.couchbase.lite.Document#9.975}.changeListeners will be accessed at com.couchbase.lite.Document.addChangeListener(Document.java:169)
size : 0, ID : 8980450c-5ddb-4181-a7bf-3e162c67a68f
{com.couchbase.lite.Document#10.189}.changeListeners will be accessed at com.couchbase.lite.Document.revisionAdded(Document.java:598)
size : 0, ID : 8980450c-5ddb-4181-a7bf-3e162c67a68f
{com.couchbase.lite.Document#10.189}.changeListeners will be accessed at com.couchbase.lite.Document.revisionAdded(Document.java:598)
size : 0, ID : 8980450c-5ddb-4181-a7bf-3e162c67a68f
We can see that removeChangeListener has never been called but changeListeners size has been set to 0.
Anyone could help?
Document.ChangeListener is associated with the Document instance. I believe following stuff could be happened.
Obtain Document instance (Document#6.888) with doc ID 8980450c-5ddb-4181-a7bf-3e162c67a68f.
Added Document.ChangeListener to the document instance.
Document instance is released (garbage collected).
Obtain Document instance (Document#9.975) with same doc ID.
But this instance does not have Document.ChangeListener, and unable to notify the changes.
I didn't find out why this was happening but I managed to fix this issue by listening to changes through Database.ChangeListener instead of Document.ChangeListener. When I call attachDocumentChangeListener(Document.ChangeListener), I add the listener into a Map<String,Document.ChangeListener> (where the key document id) and when a document is updated, it goes through the Database.ChangeListener.changed() method and here is how I override it :
#Override
public void changed(Database.ChangeEvent event) {
for (DocumentChange change : event.getChanges()) {
List<DocumentChangeListener> documentChangeListeners = this.documentChangeListeners.get(change.getDocumentId());
if(documentChangeListeners != null){
for (DocumentChangeListener changeListener : documentChangeListeners)
changeListener.changed(change);
}
}
}
And here is my 2 methods attachDocumentChangeListener() & detachDocumentChangeListener().
public void attachDocumentChangeListener(String documentId, final DocumentChangeListener listener){
List<DocumentChangeListener> documentChangeListeners = this.documentChangeListeners.get(documentId);
if(documentChangeListeners == null)
documentChangeListeners = new ArrayList<>();
documentChangeListeners.add(listener);
this.documentChangeListeners.put(documentId, documentChangeListeners);
}
public void detachDocumentChangeListener(#NonNull DocumentChangeListener listener){
String documentId = listener.getDocumentId();
List<DocumentChangeListener> documentChangeListeners = this.documentChangeListeners.get(documentId);
if(documentChangeListeners != null){
documentChangeListeners.remove(listener);
if(documentChangeListeners.isEmpty())
this.documentChangeListeners.remove(documentId);
else
this.documentChangeListeners.put(documentId, documentChangeListeners);
}
}
And those 3 methods are inside a Singleton.