Realm managing subobjects - android

can you help me out?
For my current context in an app I'm trying to manage a - parsed from json - object through multiple Realms. Due to my requirements I'm bound to use a Realm for my raw converted POJOs, a Realm for an app-session instance and a Realm for a subobject related session (since I'm managing Object copies per session).
The question: How do I properly manage (managed or unmanaged) RealmObjects while copying them from one Realm to another?
Heres my current code:
Realm.getInstance(RealmManager.sessionsConfig)?.also{ sessionsRealm ->
try{
sessionsRealm.executeTransaction{
// Make a clean setup
realm.deleteAll()
val copyRootObject = sessionsRealm.copyToRealm(originalRootObject)
// Ignore flat hierarchy if only one place with one campaign is provided
if(copyRootObject.place?.children?.size!! == 0){
copyRootObject.campaign?.apply {
parameters = copyRootObject.parameters
dynamicfields = copyRootObject.dynamic_fields
survey_topics = copyRootObject.survey_topics
}
copyRootObject.apply {
place?.campaigns = RealmList()
place?.campaigns?.add(copyRootObject.campaign)
}
}
}
}catch(e: Exception){
e.printStackTrace()
}finally {
realm.close()
openStartFragment()
}
}
During the process of opening my main activity which displays the POJOs contents I'm trying to copy the original whole RealmObject from my raw Realm to my "instances" Realm. While this is happening I'm also trying to move some subobjects in my copied objects into a different field because the REST API I'm using is a bit broken.
Problem is though, the shown code doesn't seem to work. While I'm able to find the copied object, the reassigned subobjects are not being moved to the specified fields.
Anybody have an idea what I'm doing wrong?

Related

Mutablelivedata variable instance size issue

Let's say we have the stock of items stored in the JSON txt file. To access the data we load JSON file using a stringBuilder class, then populate StokItems into:
List<StockItem> stock;
like this:
stock.clear();
Gson gson = new Gson();
stock = gson.fromJson(stringBuilder.toString(), new TypeToken<List<StockItem>>() {
}.getType());
as we would like to take advantage of the LiveData in Android we introduced:
MutableLiveData<List<StockItem>> stockLiveData = new MutableLiveData<>();
To make it working we have to post data into it:
public LiveData<List<StockItem>> getLiveStockList() {
stockLiveData.postValue(stock);
return stockLiveData;
}
So here is the question, if the line:
stockLiveData.postValue(stock);
consumes memory for creating another copy of
List<StockItem> stock
and if so, could be it's a good idea to keep "original data" inside the
stockLiveData
variable only to reduce the memory impact? (If is it possible ...)
No need to use global variable to hold temporary data. either you can use local variable or directly post to LiveData.
public LiveData<List<StockItem>> getLiveStockList() {
return stockLiveData;
}
And then either
List stock = gson.fromJson(stringBuilder.toString(), new TypeToken<List<StockItem>>() { }.getType());
stockLiveData.postValue(stock);
or
stockLiveData.postValue(gson.fromJson(stringBuilder.toString(), new TypeToken<List<StockItem>>() { }.getType()))
You can also access the list inside LiveData like below:
stockLiveData.getValue();
Which return the original list data.
There are couple of things about your code I want to point out here:
You do not necessarily need to call postValue in your getter function. As far as there are observers observing from your getter function, you can post value from anywhere.
Even if you use mutable live data that does not necessarily mean that you're allocating memory for or creating a copy of List. When you set or post value on your Mutable Live Data, you're basically referencing the existing list on to value in your Mutable Live Data.

Secondary .realm file getInstance taking too long

By secondary .realm file I mean a realm file which is not the default.realm file.
I have two .realm files - one being the standard default.realm and the other being say aux.realm.
Things work as they should under normal circumstances, but when I perform a heavy operation (multiple tables undergo .deleteAllFromRealm() and re-sync everything) while this happens on a worker thread, the user is still free to perform any UI activities, whenever any interaction is performed involving the aux.realm instance, the app shuts with an ANR.
With some extensive debugging I found that the getAuxRealmInstance takes a lot of time to pass the instance, even though the value for it should be cached. This is in spite of the fact that its configuration already loaded lazily. Hence, it is unclear as to why it takes so much time?
I also though it might be an issue of transactions as there can be only one active transaction at a time, but what i'm not sure is that is the rule valid also through files, like can two realm files have their own transactions running in parallel?
My aux.realm file:
private const val FILE_NAME = "auxiliary.realm"
private val auxiliaryConfiguration = lazy {
RealmConfiguration.Builder()
.name(FILE_NAME)
.schemaVersion(AuxiliarySchemaVersionMappings.CURRENT_SCHEMA_VERSION)
.modules(AuxiliaryRealmModule())
.initialData {
Log.d("AuxRealm", "running initial data migration: ")
// initial version..
// migrate the AppMetaData table from base realm to aux realm
}
}
.migration(AuxiliaryRealmMigration())
.build().also { Log.d("AuxRealm", "configuration created: ") }
}
fun getAuxiliaryRealmInstance(): Realm{
return Realm.getInstance(auxiliaryConfiguration.value)
}
fun getAuxiliaryRealmInstanceAsync(callback: Realm.Callback): RealmAsyncTask{
return Realm.getInstanceAsync(auxiliaryConfiguration.value, callback)
}
PS: The ANR goes away if I load the aux realm instance in async, which as mentioned above, points to the same problem.
Env variables: Realm: 5.4.2, Kotlin 1.2.51
I solved this problem by caching the realm instance:
private Realm auxiliaryRealmInstance;
fun getAuxiliaryRealmInstance(): Realm{
return auxiliaryRealmInstance == null ? Realm.getInstance(auxiliaryConfiguration.value) : auxiliaryRealmInstance;
}
This workaround should not be necessary because here it's written that caching will not make anything more efficient. But I did not notice any disadvantages so far.

Realm: working with Clean-Architecture and RxJava2

A bit of context, I’ve tried to apply some clean-architecture to one of my projects and I’m having trouble with the (Realm) disk implementation of my repository. I have a Repository which pulls some data from different DataStores depending on some conditions (cache). This is the theory, the problem comes when mixing all of this with UseCases and RxJava2.
First I get the list of objects from Realm and then I manually create an Observable of it. But the subscribe (as expected) is executed on a different thread so realm ends up crashing… (second block of code)
This is the code I use to create the Observables (from an abstract class DiskStoreBase):
Observable<List<T>> createListFrom(final List<T> list) {
return Observable.create(new ObservableOnSubscribe<List<T>>() {
#Override
public void subscribe(ObservableEmitter<List<T>> emitter) throws Exception {
if (list != null) {
emitter.onNext(list);
emitter.onComplete();
} else {
emitter.onError(new ExceptionCacheNotFound());
}
}
});
}
How can I deal with this scenario?
More code of DiskStoreForZone:
#Override
public Observable<List<ResponseZone>> entityList() {
Realm realm = Realm.getDefaultInstance();
List<ResponseZone> result = realm.where(ResponseZone.class).findAll();
return createListFrom(result);
}
The exact crash:
E/REALM_JNI: jni: ThrowingException 8, Realm accessed from incorrect thread.
E/REALM_JNI: Exception has been thrown: Realm accessed from incorrect thread.
It doesn't work because despite using Rx, your data layer is not reactive.
Realm by its nature is a reactive datasource, and its managed objects by nature are also mutable (updated in place by Realm), and thread-confined (can only be accessed on the same thread where the Realm was opened).
For your code to work, you'd need to copy out the data from the Realm.
#Override
public Single<List<ResponseZone>> entityList() {
return Single.fromCallable(() -> {
try(Realm realm = Realm.getDefaultInstance()) {
return realm.copyFromRealm(realm.where(ResponseZone.class).findAll());
}
});
}
I took the liberty and represented your Single as a Single, considering it's not an Observable, it does not listen for changes, there is only 1 event and that is the list itself. So sending it through an ObservableEmitter doesn't really make sense as it does not emit events.
Therefore, this is why I said: your data layer is not reactive. You are not listening for changes. You are just obtaining data directly, and you are never notified of any change; despite using Rx.
I drew some pictures in paint to illustrate my point. (blue means side-effects)
in your case, you call a one-off operation to retrieve the data from multiple data-sources (cache, local, remote). Once you obtain it, you don't listen for changes; technically if you edit the data in one place and another place, the only way to update is by "forcing the cache to retrieve the new data manually"; for which you must know that you modified the data somewhere else. For which you need a way to either directly call a callback, or send a message/event - a notification for change.
So in a way, you must create a cache invalidation notification event. And if you listen to that, the solution could be reactive again. Except you're doing this manually.
----------------------------------------------------------------------
Considering Realm is already a reactive data source (similarly to SQLBrite for SQLite), it is able to provide change notifications by which you can "invalidate your cache".
In fact, if your local data source is the only source of data, and any write from network is a change that you listen to, then your "cache" can be written down as replay(1).publish().refCount() (replay latest data for new subscribers, replace data with new if new data is evaluated) which is RxReplayingShare.
Using a Scheduler created from the looper of a handler thread, you can listen to changes in the Realm on a background thread, creating a reactive data source that returns up-to-date unmanaged copies that you can pass between threads (although mapping directly to immutable domain models is preferred to copyFromRealm() if you choose this route - the route being clean architecture).
return io.reactivex.Observable.create(new ObservableOnSubscribe<List<ResponseZone>>() {
#Override
public void subscribe(ObservableEmitter<List<ResponseZone>> emitter)
throws Exception {
final Realm observableRealm = Realm.getDefaultInstance();
final RealmResults<ResponseZone> results = observableRealm.where(ResponseZone.class).findAllAsync();
final RealmChangeListener<RealmResults<ResponseZone>> listener = results -> {
if(!emitter.isDisposed()) {
if(results.isValid() && results.isLoaded()) {
emitter.onNext(observableRealm.copyFromRealm(results));
}
}
};
emitter.setDisposable(Disposables.fromRunnable(() -> {
if(results.isValid()) {
results.removeChangeListener(listener);
}
observableRealm.close();
}));
results.addChangeListener(listener);
// initial value will be handled by async query
}
}).subscribeOn(looperScheduler).unsubscribeOn(looperScheduler);
Where looper scheduler is obtained as
handlerThread = new HandlerThread("LOOPER_SCHEDULER");
handlerThread.start();
synchronized(handlerThread) {
looperScheduler = AndroidSchedulers.from(handlerThread.getLooper());
}
And that is how you create reactive clean architecture using Realm.
ADDED:
The LooperScheduler is only needed if you intend to actually enforce Clean Architecture on Realm. This is because Realm by default encourages you to use your data objects as domain models and as a benefit provides lazy-loaded thread-local views that mutate in place when updated; but Clean Architecture says you should use immutable domain models instead (independent from your data layer). So if you want to create reactive clean architecture where you copy from Realm on a background thread any time when Realm changes, then you'll need a looper scheduler (or observe on a background thread, but do the copying from a refreshed Realm on Schedulers.io()).
With Realm, generally you'd want to use RealmObjects as your domain models, and rely on lazy-evaluation. In that case, you do not use copyFromRealm() and you don't map the RealmResults to something else; but you can expose it as a Flowable or a LiveData.
You can read related stuff about this here.

Realm Find Queries Result in Empty Objects [duplicate]

This question already has answers here:
Cannot retrieve field values from realm object, values are null in debugger
(5 answers)
Closed 5 years ago.
When doing find queries for objects I'm getting "empty" objects (non-null, but not populated). However, in the debugger I can see the data for the object in the object description (see below). I've also verified the data is there using the Realm Browser. I've tried different find queries, querying with filter criteria, using the same Realm object for inserts/queries, using different Realm objects for inserts/queries, refreshing the Realm, etc.
If I Log fields in the RealmObject I see the proper data print out. However, I'm trying to convert these models into other models for use in RxJava per https://realm.io/news/using-realm-with-rxjava/.
Here's some sample code where reproduced the issue. Below that is a screenshot when breaking at verifyRealm.close().
RealmTester realmTester1 = new RealmTester();
realmTester1.setFirstName("Tester1");
realmTester1.setLastName("ABC");
RealmTester realmTester2 = new RealmTester();
realmTester2.setFirstName("Tester2");
realmTester2.setLastName("XYZ");
Realm insertRealm = Realm.getDefaultInstance();
insertRealm.refresh();
insertRealm.beginTransaction();
insertRealm.copyToRealm(realmTester1);
insertRealm.copyToRealm(realmTester2);
insertRealm.commitTransaction();
insertRealm.close();
Realm verifyRealm = Realm.getDefaultInstance();
RealmResults<RealmTester> verifyTesters = verifyRealm.where(RealmTester.class).findAll();
verifyRealm.close();
I have a screenshot of the debugger at: http://i.stack.imgur.com/1UdRr.png
I'm using v0.82.1. Any thoughts on why the models here aren't populating?
The idea behind realm-java is that we are generating Proxy class inherits from user's model class, and override the setters and getters there.
It is totally normal that you see null values for the model's field in the debugger, since the Realm are not setting them. (zero-copy, Realm is trying to reduce the memory usage by managing the data in the native code and sharing them whenever it is possible.)
Because of this, when you want to access a Realm model's field, please always use setters and getters. Checking the generated Proxy class will help you to understand this, it is quite simple actually. It is located in the build directory named like MyModelRealmProxy.java
And also check this section of the documents, it would give you some idea about the standalone object and how to write them to Realm.

I try to createOrUpdate a realm object from json. the json is in the object but all fields are null

My code:
AppCFG appCFG = new AppCFG();
if(jsonToParse != null) {
Realm realm = Realm.getInstance(AppController.getInstance());
appCFG.setOid(ParserJsonMethods.getOid(jsonToParse));
appCFG.setBaseResourceUrl(jsonToParse.optString(AppCFGContract.BASE_RESOURCE_URL));
appCFG.setClientName(jsonToParse.optString(AppCFGContract.CLIENT_NAME));
appCFG.setBucketName(jsonToParse.optString(AppCFGContract.BUCKET_NAME));
appCFG.setConfigUpdatedOn(StringConvertions.stringDateToMillis(jsonToParse.optString(AppCFGContract.CONFIGURATION_UPDATED_ON)));
appCFG.setDefaultOutputVideoMaxFps(jsonToParse.optInt(AppCFGContract.DEFAULT_OUTPUT_VIDEO_MAX_FPS));
appCFG.setLatestPackagePublishedOn(StringConvertions.stringDateToMillis(jsonToParse.optString(AppCFGContract.LATEST_PACKAGE_PUBLISHED_ON)));
appCFG.setOnboardingPassed(jsonToParse.optBoolean(AppCFGContract.ONBOARDING_PASSED));
appCFG.setOnboardingUsingPackage(jsonToParse.optString(AppCFGContract.ONBOARDING_USING_PACKAGE));
appCFG.setPrefferedFootageOID(jsonToParse.optString(AppCFGContract.PREFFERED_FOOTAGE_OID));
appCFG.setTweaks(jsonToParse.optString(AppCFGContract.TWEAKS));
appCFG.setUploadUserContent(jsonToParse.optString(AppCFGContract.UPLOAD_USER_CONTENT));
appCFG = parseMixedScreen(appCFG, jsonToParse);
realm.beginTransaction();
realm.copyToRealmOrUpdate(appCFG);
realm.commitTransaction();
The result of pulling all results:
All fields are empty.. but somehow the json is attached to the object.. What am I doing wrong??
EDIT:
When I do result.getBaseResourceUrl() after pulling all results and opening this result (the result from the image), for instance, I get back a good answer which is:http://blah.blah. but when I try to get results from realm based on baseResourceUrl = "http://blah.blah" I get back nothing...
Realm uses a zero-copy architecture with proxy objects. This means that all your data is always kept inside our internal storage engine in C++ and not actually copied to Java. This also means that Realm doesn't really use the Java variables and as such they always have uninitialised value when looking at them through a debugger (null for objects, 0 for ints, "" for strings)
You can see your object is really a <yourtype>RealmProxy. What this proxy class does is overriding all getters and setters to access the data in C++ instead of Java. So if you use the normal getters you can access you data. The proxy also creates a proper toString() method which is why your popup shows the correct output.

Categories

Resources