Only valid managed objects can be copied from Realm - java

I have one-to-many relationship in Realm
public class BayEntity extends RealmObject implements RealmModel {
#PrimaryKey
private int id;
private String title;
}
public class TermEntity extends RealmObject implements RealmModel {
#PrimaryKey
private String termId;
private String name;
private RealmList<BayEntity> bayList;
public void updateBayList(ArrayList<BayEntity> bayList) {
if(CollectionUtility.isEmptyOrNull(this.bayList))
this.bayList = new RealmList();
this.bayList.addAll(bayList);
realm.beginTransaction();
realm.copyToRealm(this.bayList);
realm.insertOrUpdate(this); //update current instance
realm.commitTransaction();
}
public ArrayList<BayEntity> getSimpleList() {
if(CollectionUtility.isEmptyOrNull(bayList))
return new ArrayList<>();
return (ArrayList<BayEntity>) realm.copyFromRealm(bayList);
}
}
I am getting all TermEntity objects containing baylist as:
public RealmResults<TermEntity> getTerms() {
return realm.where(TermEntity.class).findAll();
}
I am able to save and retrieve bayList successfully. When I try to convert it into Arraylist in method getSimpleList(), I am getting exception:
java.lang.IllegalArgumentException: Only valid managed objects can be
copied from Realm.
I can't understand how objects can be unmanaged when they are saved and retrieved from realm. Please correct me. Thanks.

It looks to me as though you have several problems:
Where are you getting the Realm instance that you use in the updateBayList method? Since it is instance local state, it might easily refer to a Realm that is not valid in the current context (time or thread)
The first three lines if ... addAll(bayList); modify Terms RealmList. You can't do that outside of a transaction, if the object is managed. When the object is not managed, you get the error you describe, because you are trying to add managed objects to an unmanaged RealmList
While not a problem, RealmObject already implements RealmModel. Say something once, why say it again?
Edited to add:
In order to add BayEntitys to a TermEntity, first make sure that the TermEntity is a managed object (use copyToRealm, or query for it). Once you do that, the code in updateBayList is very nearly correct. You want something like:
realm.beginTransaction();
if (CollectionUtility.isEmptyOrNull(this.bayList)) {
this.bayList = new RealmList();
}
this.bayList.addAll(bayList);
realm.commitTransaction();

Related

Find all RealmObjects that have inside a list a given RealmObject

Lets say I have this class (it already has an id property but it's already auto generated by realm):
public class User extends RealmObject {
private String name;
private String username;
}
And another one that has a list of Users inside:
public class Ride extends RealmObject {
private String tripName;
private String rideType;
private RealmList<User> usersJoined;
}
I just want to do a query to realm to get a list of Rides where the user is inside. I found another question like this but in that case the asker already has the object that have the whole list and he just need to find the user inside that spefic object property list, but in my case I want to get the Rides where the user (that I already know) is inside. Thank you in advanced!
Finally fixed using stream().filter :
RealmResults<Ride> rides = realm.where(Ride.class).findAll();
List<Ride> = rides.stream().filter(ride -> ride.getUsersJoined().contains(givenUser)).collect(Collectors.toList())

reducing json output while trying to retrieve only parent fields excluding child fields

I have this class hierarchy:
#Data // Lombok
public class CarInfo {
private String brand;
private String model;
}
public class CarFullInfo extends CarInfo {
private Integer horsepower;
private String engineType;
}
I need to develop REST API, where I can ask for a list of all cars, where is only CarInfo fields are returned, and there is another extended query, that returns list of CarFullInfo - extended, with 4 fields total.
I store all data in memory (I need to do it this way for some reason) as a collection of CarFullInfo objects, and I'm trying to figure out how can I reduce two extra fields if I'm not asked to return full info.
First my assumption was to find some JSON appropriate annotation to do this some way, but I failed.
Another try was just to cast subclass to superclass and return list of superclass objects from rest controller, but that also didn't work. I assume that happened cause THEY KNOW actual objects class and THEY SEE all it's fields.
#GetMapping("/cars")
public ResponseEntity<List<? extends CarInfo>> getCars(...) {
List<CarFullInfo> select = ...
List<CarInfo> skimpy = select.stream().map(car -> (CarInfo)car).collect(Collectors.toList());
return new ResponseEntity<>(skimpy, HttpStatus.OK);
}
Thus, my last idea was to create a new list of CarInfo and copy only it's fields - excluding CarFullInfo fields, but when number of fields grows - it gets cumbersome to do this:
CarFullInfo cfi = ...
CarInfo carInfo = new CarInfo(cfi.getBrand(), cfi.getModel(), cfi.getCountry(), cfi.getGeneration(), cfi.getModification() and more, and more)
I'm feeling there is some elegant way to do what I need. Direct me please.
Thank you!
#JsonView should help you. Take a look at the examples here - https://www.baeldung.com/jackson-json-view-annotation
And you can use BeanUtils
#Service
public class CarService {
List<CarFullInfo> someCars = Collections.singletonList(new CarFullInfo("Opel", "Cadet", 75, "oil"));
public List<CarFullInfo> getFullCarInfo() {
return someCars;
}
public List<CarInfo> getCarInfo() {
return someCars
.stream()
.map(this::toCarInfo)
.collect(Collectors.toList());
}
private CarInfo toCarInfo(CarFullInfo carFullInfo) {
CarInfo carInfo = new CarInfo();
BeanUtils.copyProperties(carFullInfo, carInfo);
return carInfo;
}
}

java.lang.UnsupportedOperationException: This feature is available only when the element type is implementing RealmModel

In my app, I'm saving an Arraylist to realmlist and that list is being saved to realm database so now at runtime when I try to fetch result with some search query it gives me java.lang.UnsupportedOperationException: This feature is available only when the element type is implementing RealmModel.
I can't seem to understand what is wrong here. Any help will be appreciated!
Here is my realm class:
public class Vendordb extends RealmObject {
public RealmList<String> getVendor() {
return vendor;
}
public void setVendor(RealmList<String> vendor) {
this.vendor = vendor;
}
RealmList<String> vendor = new RealmList<>();
}
This is the code for create realmlist:
final List<String> vendors = macvendorDatabaseAccess.getvendors();
final RealmList<String> vend = new RealmList<>();
vend.addAll( vendors );
macvendorDatabaseAccess.close();
realm.executeTransaction( new Realm.Transaction() {
#Override
public void execute(Realm realm) {
Vendordb vendordb = realm.createObject( Vendordb.class );
vendordb.setVendor(vend);
}
} );
//At this piece of code i'm getting error:
Vendordb vendordb = realm.where(Vendordb.class).findFirst();
RealmList<String> vendor = vendordb.getVendor();
RealmResults<String> filteredMembers = vendor.where().equalTo("mac", identifier).findAll();
if (!filteredMembers.isEmpty()) {
holder.vendor.setText( filteredMembers.get( 0 ).toString() );
}
Your problem is in setVendor, your use of an unmanaged RealmList outside of an object, and using a query on a non-Realm object (in this case a primitive String).
In setVendor, you cannot reassign the value of the list at this time; the list already exists, so you have to modify its contents.
RealmList should only be used within an object where it is managed. You shouldn't try and use them outside of the object.
Although it's admirable to try and encapsulate the RealmList in the object and provide accessors, it doesn't actually help here; you'll notice the Realm examples always use public members. You can make them private if you wish, but your accessors should then hide the implementation and dependency on Realm and provide you with the necessary operations your application layer needs.
So, delete the getVendor and setVendor methods from your model.
Next, add methods to do your dirty work. E.g.
public class Vendordb extends RealmObject {
RealmList<String> vendor = new RealmList<>();
public void addVendors(List<String> vendors)
{
this.vendor.addAll(vendors);
}
}
Your 'create' section then becomes:
final List<String> vendors = macvendorDatabaseAccess.getvendors();
macvendorDatabaseAccess.close();
realm.executeTransaction( new Realm.Transaction() {
#Override
public void execute(Realm realm) {
Vendordb vendordb = realm.createObject( Vendordb.class );
vendordb.addVendors(vendors);
}
} );
I'm not quite sure what you're trying to do in the final 'search' part. You have a list of strings but are performing a 'where' on a primitive type - this is probably what is generating your runtime error. Think about what you want to pull out of the list of strings, and then add a similar method to find it for you (maybe just a call to contains).

Android resetting all fields of a class

Hey guys I have a class called UserData consisting of static fields, which is as follows:
public class UserData {
private static JSONObject fbProfilePicture;
private static boolean loggedOut=false;
private static Integer commonFriendID;
private static Integer userID1;
private static Integer UserID2;
private static JSONObject stolenTrio=null;
}
Actually the class contains alot more fields but I decided to show you a small version of my class.
In my app, I've a feature to delete account and create a new one, when I choose to do that, my app goes back to the sign up process, but there's a problem, I want to clear all fields of that class after deleting user.
How can I do that?
If you think you need a static / singleton kind of instance of UserData, I suggest doing it like this:
public class UserDataSingleton {
public static UserData userdata;
}
and make your UserData a simple Pojo with non-static fields.
This way you can reset your data with
UserDataSingleton.userData = new UserData()
Create a method that reset your values. You might want later to do more things into that method. Ex inform a listener that your object has been cleared.

Android: Realm + Retrofit 2 + Gson

I have a problem when using Retrofit + Gson and Realm. I know that there is an issue with the combination of these 3 libraries. Some answers suggest that setting an ExclusionStrategy for Gson can solve this issue, and I tried it but it didn't work.
My code looks like:
public class ObjectList {
public List<AnotherObject> anotherObject;
}
public class AnotherObject extends RealmObject {
private String propA;
public void setPropA(String propA){
this.setPropA = propA
}
public String getPropA(){
return propA
}
}
Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaringClass().equals(RealmObject.class);
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}).create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost/api/")
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
ObjectAPI objectAPI = retrofit.create(ObjectAPI.class);
call.enqueue(new Callback<ObjectList>() {
#Override
public void onResponse(Response<ObjectList> response, Retrofit retrofit) {
objectList = response.body().anotherObject;
onRefreshComplete();
}
#Override
public void onFailure(Throwable t) {
Toast.makeText(context, "Connection to server failed, please check your connection", Toast.LENGTH_LONG).show();
}
});
With the current code, I'm still getting the memory leak. Is there any suggestion for this code?
My json structure looks like:
{"anotherObject":[{"propA": "someValue"}]}
Why writing all these custom serializers when you can make Gson and
Realm work together with just ONE LINE OF CODE?
TL;DR.
You can simply solve this by passing unmanaged RealmObjects to your Retrofit calls.
MyModel model = realm.where(MyModel.class).findFirst();
MyModel unmanagedModel = realm.copyFromRealm(model);
// then pass unmanagedModel to your retrofit calls
If you don't want to go through all this answer, then skip to the "Recommended solutions" section posted down below.
Long talk (verbose answer)
This has nothing to do with Retrofit. If you have set Gson to be the data converter to your current Retrofit instance, then you can be sure that it's Gson who's failing.
Suppose we have this Model:
public class Model extends RealmObject {
#PrimaryKey
long id;
boolean happy;
public Model() {/* Required by both Realm and Gson*/}
public Model(long id, boolean happy) {
this.id = id;
this.happy = happy;
}
public long getId() {
return id;
}
public boolean isHappy() {
return happy;
}
}
For this code, we'll have no issue:
Model unmanagedModel = new Model(5, true); // unmanagedModel
new Gson().toJson(unmanagedModel); // {id : 5, happy : true}
But for this one:
Realm realm = /*...*/;
Model managedModel = realm.copyToRealm(unmanagedModel);
new Gson().toJson(managedModel); // {id : 0, happy : false}
// We'll get the samething for this code
Model anotherManagedModel = realm.where(Model.class).equalTo("id",5).findFirst();
new Gson().toJson(anotherManagedModel); // {id : 0, happy : false}
We'll be surprised. We're seeing nulls everywhere!.
Why?
Gson fails serializing a RealmObject only if it's a managed one. Which means that there's currently an opened Realm instance making sure this RealmObject is reflecting what is currently held in the persistence layer (the Realm database).
The reason why this is happening is due to the conflicting nature of how both Gson and Realm work. Quoting Zhuinden on why Gson sees null everywhere:
... that's because GSON tries to read the fields of the
Realm object via reflection, but to obtain the values, you need to use
accessor methods - which are automatically applied to all field access
in the code via the Realm-transformer, but reflection still sees nulls
everywhere...
Christian Melchior proposes a workaround to this conflict by writing a custom JsonSerializers to every created Model. This is the workaround you have used, but I would NOT recommend it. As you have realized, it requires writing a lot of code which is error prone and the worst of all, kills what Gson is about (which is making our life less painful).
Recommended solutions
If we can somehow make sure the realmObject we pass to Gson is not a managed one, we'll avoid this conflict.
Solution 1
Get a copy in memory of the managed RealmObject and pass it to Gson
new Gson().toJson(realm.copyFromRealm(managedModel));
Solution 2
(Wrapping the 1st solution). If the 1st solution is too verbose for you, make your models look like this one:
public class Model extends RealmObject {
#PrimaryKey
long id;
boolean happy;
// Some methods ...
public Model toUnmanaged() {
return isManaged() ? getRealm().copyFromRealm(this) : this;
}
}
And then, you can do something like this:
// always convert toUnmanaged when serializing
new Gson().toJson(model.toUnmanaged());
Solution 3
This one is NOT very practical but is worth mentioning. You can go with deep-cloning your models (taken from here).
1 - Create a generic interface CloneableRealmObject:
interface CloneableRealmObject<T> {
T cloneRealmObject();
}
2 - Make your realmObjetcs implement the above interface like so:
public class Model extends RealmObject implements CloneableRealmObject<Model> {
#PrimaryKey
long id;
public Model() {
// Empty constructor required by Realm.
}
#Override
public Model cloneRealmObject() {
Model clone = new Model();
clone.id = this.id;
return clone;
}
}
3 - Clone the object before passing to your Retrofit calls.
new Gson().toJson(model.cloneRealmObject());
In a recent post
I gave an answer explaining why we're getting this weird serialized output when using managed realmObjects. I recommend you to take a look at it.
Bonus
You might also want to check RealmFieldNamesHelper, a library made by Christian Melchior "to make Realm queries more type safe".
I too faced the similar issue. This is because your request format is wrong. In my case, I am trying to send a Realm object by getting it from local SQLite DB instead of Java object. Retrofit converts only Java object to JSON but not Realm object. Please make sure you are sending a right JSON as a request when using Retrofit.
Then I replaced this:
List<MyRealmModel> objectsToSync = mRealm.where(MyRealmModel.class).findAll();
To:
List<MyRealmModel> objectsToSend = mRealm.copyFromRealm(objectsToSync);

Categories

Resources