I'm using Realm to provide the database for my application. But...
After login, the server returns the data and I create the account (of AccountManager) and save these datas at the database of the application, like this (at an AsyncTask, of course):
UserRealm userRealm = new UserRealm();
//setter of the userRealm...
Realm realm = Realm.getInstance(context);
realm.beginTransaction();
realm.copyToRealmOrUpdate(userRealm);
realm.commitTransaction();
realm.close();
After, I close the LoginActivity and at the onResume of the MainActivity, I try to load the user, like this(at an AsyncTask, again...):
public static UserRealm getUser(Context context) {
try {
return Realm.getInstance(context).where(UserRealm.class).findFirst();
} catch (Exception e) {
if(DebugUtil.DEBUG) { //enabled
e.printStackTrace();
}
}
return null;
}
But this returns null, I don't know what happens with it.
UserRealm: https://gist.github.com/ppamorim/88f2553a6ff990876bc6
AsyncTask is in a threadpool, and considering you open Realm instances that you never close with your getUser() call, your Realm version becomes locked at the version when you first called getUser().
return Realm.getInstance(context).where(UserRealm.class).findFirst(); // never closed
So even though you commit a transaction on another thread in the threadpool, not all threads will be up to date (because you locked them on an old version by opening Realm instances that are never closed), and sometimes the object will be null.
Solution, close all Realm instances on background threads (or force an update if that's not enough for some reason).
Related
I used Realm in conjunction with RxJava it this way:
public Flowable<List<EventEntity>> getAll() {
try (final Realm realm = Realm.getInstance(mRealmConfiguration)) {
RealmQuery<RealmEvent> query = realm.where(RealmEvent.class);
Flowable<RealmResults<RealmEvent>> result;
if (realm.isAutoRefresh()) {
result = query
.findAllAsync()
.asFlowable()
.filter(RealmResults::isLoaded);
} else {
result = Flowable.just(query.findAll());
}
return result
.unsubscribeOn(AndroidSchedulers.mainThread());
}
}
I use this chain on multiple places in app. For example:
return Observable.merge(
mEventRepository.getAll()
.toObservable(),
subjectNotificationChange
.flatMapMaybe(notification ->
mEventRepository.getAll()
.firstElement()
)
)
Problem is that I obtain exception: java.lang.IllegalStateException: This Realm instance has already been closed, making it unusable.
I looked at implementation method from of RealmObservableFactory and each call of subscribe method should create new instance of Realm. Entire situation looks as problem with references counting.
Do you know where is problem?
Java's try-with-resource closes the resource as soon as you leave the code block, but RxJava being lazy and all, only begins working when you actually subscribe, which happens after your code exits the getAll() function.
Edit: since you build a special Realm instance each time, passing configuration to it, the instance is not shared and therefore definitively closed each time.
Instead, initialize your Realm earlier using Realm.setDefaultConfiguration(config). Then, use Realm.getDefaultInstance() in your function so you access the default shared instance instead of creating a new one each time.
Edit2: the easiest solution is to keep a reference to the Realm instance:
class MyRepository {
private final Realm realm;
public MyRepository(Realm realm) {
this.realm = realm;
}
public Flowable<List<EventEntity>> getAll() {
RealmQuery<RealmEvent> query = realm.where(RealmEvent.class);
// ...
}
}
Realm realm = Realm.getDefaultInstance();
MyRepository repository = MyRepository(realm);
repository.getAll()
// ...
I find solution. It is bug in official example. When you call mentioned chain than must exist other open Realm instance for same thread. In other cases RealmResult is invalidated. Can be used solution mentioned by ESala.
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).
I have an Android app that uses a pretty common design pattern:
The main activity is essentially presenting a list of objects - on small devices it does so by hosting a single fragment that displays a recyclerview of this list. On larger devices it hosts two fragments, one which has the same recyclerview of objects, and another which will host the detail for individual objects when one is selected in the list.
On smaller devices, when a selection from the list is made, an activity is launched that hosts a fragment that utilizes a ViewPager to allow "swiping" through the list of objects, and edit each one in place.
In both cases, the user is allowed to edit only from the detail fragment.
I currently have my realm instance initialized in the application class, then the default instance retrieved in an activity base class I use to hold some housekeeping methods:
public abstract class SingleFragmentActivity extends AppCompatActivity {
private Realm realm;
protected abstract Fragment createFragment();
#LayoutRes
protected int getLayoutResId() {
return R.layout.activity_fragment;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
// Initialize ProfileLab
ProfileLab.get(realm);
setContentView(getLayoutResId());
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragment_container);
if (fragment == null) {
fragment = createFragment();
fm.beginTransaction()
.add(R.id.fragment_container, fragment)
.commit();
}
}
#Override
protected void onDestroy() {
super.onDestroy();
if ( realm != null) {
realm.close();
}
}
}
Note that I am storing this instance of realm in a static class "ProfileLab":
// Initialize ProfileLab
ProfileLab.get(realm);
Then in the various fragments that update data, I am doing stuff like:
mProfile = ProfileLab.get().getProfile(profileId);
*
* do CRUD activities here for example:
*
private void deleteProfile() {
ProfileLab.get().deleteProfile(mProfile);
mCallbacks.onProfileUpdated(mProfile);
}
Then in ProfileLab, it looks like:
public boolean deleteProfile(Profile c) {
boolean retVal = true;
try {
mRealm.beginTransaction();
c.deleteFromRealm();
} catch (Exception e) {
retVal = false;
} finally {
if ( mRealm != null ) {
if (retVal) {
mRealm.commitTransaction();
} else {
mRealm.cancelTransaction();
}
}
}
return (retVal);
}
My question - is this a problem to essentially hold that Realm instance open like that throughout the use of the app? I noticed this paragraph in the docs:
If you get a Realm instance from a thread that does not have a Looper
attached, then objects from such instance will not be updated unless
the waitForChange() method is called. It is important to note that
having to hold on to an old version of your data is expensive in terms
of memory and disk space and the cost increases with the number of
versions between the one being retained and the latest. This is why it
is important to close the Realm instance as soon as you are done with
it in the thread.
The thing is, I am not 'done with it' because this is on the UI thread, which is obviously running throughout the lifetime of my app.
I can't really open/close the realm instance just for the atomic updates, because I need to use the result of the initial query to show the list of objects from which to choose to edit - when I tried that initially (I had realm object open/close within each method in ProfileLab itself) I got an error in my recycler adapters that the realm had been closed...
The example code showing use of the recycler view shows realm being retrieved/used/closed at the individual activity level, if I do that between say the two simple activities (hosting the RecyclerView and hosting the ViewPager), will the data updates be reflected in each other?
Opening and closing the realm within try/catch block is recommended. for an example:
try {
Realm realm = Realm.getDefaultInstance();
//Use the realm instance
}catch(Exception e){
//handle exceptions
}finally {
realm.close();
}
It's a basic example when going to use. If you can close within AsyncTask, it'll be better.
The official documentation refers that if you use minSdkVersion >= 19 and Java >= 7, you won't close it manually.
try (Realm realm = Realm.getDefaultInstance()) {
// No need to close the Realm instance manually
}
Realm will automatically keep Realms on Looper threads up to date. That particular line in the documentation mostly refers to background threads. So your code is fine, even if onDestroy might not be called.
You can also read these relevant sections in the docs:
https://realm.io/docs/java/latest/#closing-realms
https://realm.io/docs/java/latest/#realm-instance-lifecycle
After v0.91.0, all deprecated methods in Realm are removed including refresh().
I've used refresh() like below to make sure that realm instance has up-to-date data.
public static <T> T query(RealmActionWithResult<T> runnable, T defaultValue) {
Realm realm = Realm.getInstance(getConfiguration());
try {
realm.refresh();
return runnable.run(realm);
} catch (Exception e) {
return defaultValue;
} finally {
realm.close();
}
}
If i replace refresh() with waitForChange(), it's blocked until next change happens (of course in non-looper thread).
How can i make it not block current thread, but just pass it when current realm is up-to-date one or block if there is something that should wait?
Or if i use Realm like above pattern (every time close it) in non-looper thread like Schedulers.io in Rx, does Realm.getInstance() return always newest snapshot because per-thread cached instance reference is zero?
You don't have to call refresh right after calling getInstance(). The Realm returned from getInstance() will always be up to date.
I have a situation where I want to handle a realm migration in a lazy fashion. Instead of using the Realm Migration api, which the realm developer's state is cumbersome, I want to delete the realm instance and then re-instantiate it only if I need to do a migration. How would I properly handle a this situation such that I only delete and re-instantiate the database ONLY IF it needs to be upgraded?
Say I have a model
#RealmClass
public class testmodel extends RealmObject {
private String foo;
private int bar;
public int getBar() {
return bar;
}
public void setBar(int bar) {
this.bar = bar;
}
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
}
and I want to add another string bazz into the model with the proper getters and setters.
Currently, I can do this as long as the first time I instantiate realm, I delete it. Like so:
Realm.deleteRealmFile(getApplicationContext());
realm = Realm.getInstance(getApplicationContext());
Now, let's suppose I didn't want to go blowing away my data every time the app starts, but still wanted to do this in the instance when I needed to. I thought a good way would be like this.
try{
Log.d(TAG, "started realm creation");
realm = Realm.getInstance(getApplicationContext());
Log.d(TAG, "successfully created realm");
}
catch (RealmException e){ // I tried RealmMigrationNeededException and IllegalState Exception but neither are caught
Log.d(TAG, "try deleting realm");
Realm.deleteRealmFile(getApplicationContext());
realm = Realm.getInstance(getApplicationContext());
Log.d(TAG, "deleted realm and remade successfully");
}
However, when I try this instead of the exception being caught, my app crashes.
How would I properly handle a this situation such that I only delete and re-instantiate the database ONLY IF it needs to be upgraded?
In Realm 0.81.0 you can use the new RealmConfiguration object:
RealmConfiguration realmConfig = new RealmConfiguration.Builder(context)
.schemaVersion(42)
.deleteRealmIfMigrationNeeded()
.build();
This will do exactly what you are looking for.
Edit:
JavaDoc is here: https://realm.io/docs/java/latest/api/io/realm/RealmConfiguration.Builder.html
And you can read more about setup here:
https://realm.io/docs/java/latest/#configuring-a-realm