realm / realm-java

Realm is a mobile database: a replacement for SQLite & ORMs
http://realm.io
Apache License 2.0
11.45k stars 1.75k forks source link

Realm List fetching, many to many relationship #3671

Closed ghost closed 7 years ago

ghost commented 7 years ago

My goal is to have many to many relationship within same class in Realm. I tried to add object to RealmList in this way :

Realm realm = Realm.getDefaultInstance();
            DeviceEntity device = realm.where(DeviceEntity.class).equalTo(Constants.DEVICE_PARAM_DEVICE_ID, deviceId).findFirst();
            DeviceEntity deviceToBeAdded = realm.where(DeviceEntity.class).equalTo(Constants.DEVICE_PARAM_DEVICE_ID, deviceIdToAdd).findFirst();

            if(device != null && deviceToBeAdded != null){

                realm.beginTransaction();

                device.getConnectedDevices().add(deviceToBeAdded);
                realm.copyToRealmOrUpdate(device);
                realm.commitTransaction();

            }

When I try to fetch all DeviceEntity objects stored in realm like this :

 Realm realm = Realm.getDefaultInstance();
            final RealmResults<DeviceEntity> realmResults = realm
                    .where(DeviceEntity.class)
                    .findAll();

            List<DeviceEntity> list = Arrays.asList(realmResults.toArray(new DeviceEntity[realmResults.size()]));

For all DeviceEntity objects getConnectedDevices() return null

public class DeviceEntity extends RealmObject{

@PrimaryKey
private long id;
private String name;
private String deviceId;
private String deviceType;
private RealmList<DeviceEntity> connectedDevices = new RealmList<>();

public DeviceEntity() {

}

public long getId() {
    return id;
}

public void setId(long id) {
    this.id = id;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public String getDeviceId() {
    return deviceId;
}

public void setDeviceId(String deviceId) {
    this.deviceId = deviceId;
}

public String getDeviceType() {
    return deviceType;
}

public void setDeviceType(String deviceType) {
    this.deviceType = deviceType;
}

public RealmList<DeviceEntity> getConnectedDevices() {
    return connectedDevices;
}

public void setConnectedDevices(RealmList<DeviceEntity> connectedDevices) {
    this.connectedDevices = connectedDevices;
}

public static int getNextPrimaryKey(Realm realm){
    int key;
    try {
        Number number = realm.where(DeviceEntity.class).max("id");
        key = number != null ? (number.intValue() + 1) : 1;
    } catch(ArrayIndexOutOfBoundsException ex) {
        key = 1;
    }

    return key;
}
}

How should I fetch connectedDevices ?

Zhuinden commented 7 years ago

Do the query inside the transaction.

ghost commented 7 years ago

Still same effect

It is ok that RealmList is null during transaction, realm should take care of creating RealmList right?

EDIT:

I checked in stetho and I have created relationship, id are assigned to connectedDevices but when I fetch devices RealmList is null

zaki50 commented 7 years ago

Hi @dzakens You don't need to call realm.copyToRealmOrUpdate(device); since the device is already managed object.

And getter of RealmList field never return null since the generated code always returns non-null object like following.

        if (connectedDevicesRealmList != null) {
            return connectedDevicesRealmList;
        } else {
            LinkView linkView = proxyState.getRow$realm().getLinkList(columnInfo. connectedDevicesIndex);
            connectedDevicesRealmList = new RealmList< DeviceEntity>(DeviceEntity.class, linkView, proxyState.getRealm$realm());
            return connectedDevicesRealmList;
        }

You can find DeviceEntityRealmProxy#realmGet$connectedDevices() in build/generated directory.

ghost commented 7 years ago

DeviceEntity device = realm.where(DeviceEntity.class).equalTo(Constants.DEVICE_PARAM_DEVICE_ID, deviceId).findFirst();

device.getConnectedDevices() always return null after such query, maybe I misunderstand something, but I assume that it should not be null

Zhuinden commented 7 years ago

You're probably overwriting the managed object with an unmanaged object that doesn't have any elements in the list then, but that also means the error is elsewhere.

ghost commented 7 years ago

You're probably overwriting the managed object with an unmanaged object that doesn't have any elements in the list then, but that also means the error is elsewhere.

is there any way to check is object is managed or not ?

zaki50 commented 7 years ago

RealmObject has isManaged() method to check that

ghost commented 7 years ago
return Observable.create(new Observable.OnSubscribe<DeviceDomain>() {
            @Override
            public void call(final Subscriber<? super DeviceDomain> subscriber) {
                final Realm realm = Realm.getDefaultInstance();

                try {
                    realm.executeTransaction(new Realm.Transaction() {
                        @Override
                        public void execute(Realm realm) {

                            DeviceEntity device = realm.where(DeviceEntity.class).equalTo(Constants.DEVICE_PARAM_DEVICE_ID, deviceId).findFirst();
                            DeviceEntity deviceToBeAdded = realm.where(DeviceEntity.class).equalTo(Constants.DEVICE_PARAM_DEVICE_ID, deviceIdToAdd).findFirst();
                            if (device != null && deviceToBeAdded != null) {

                                device.getConnectedDevices().add(deviceToBeAdded);
                                realm.copyToRealmOrUpdate(device);
                                realm.copyToRealmOrUpdate(deviceToBeAdded);

                                subscriber.onCompleted();
                            }

                        }
                    });
                } finally {
                    if (realm != null) {
                        realm.close();
                    }
                }

            }
        });

 repository.addDeviceToControlled(deviceId, deviceIdToAdd)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .subscribe(new Subscriber<DeviceDomain>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(DeviceDomain deviceDomain) {

                    }
                });

Anything wrong here ? I spend next day on finding solution and it seems rx cannot cooperate with executeTransaction

Zhuinden commented 7 years ago

I guess deviceId and deviceIdToAdd are not in your Realm, but you are just ignoring that with an if. Try to log it.

ghost commented 7 years ago

deviceId nad deviceIdToAdd are available in Realm, I can see them through Stetho Realm plugin and via debugger

zrzut ekranu 2016-10-28 o 22 12 42
Zhuinden commented 7 years ago

In that case your problem is that you send onCompleted() while the transaction hasn't been committed yet, and the Realm on the main thread is updated with some delay and therefore you should use RealmChangeListener or results.asObservable()

ghost commented 7 years ago

return Observable.create(new Observable.OnSubscribe<DeviceDomain>() {
            @Override
            public void call(final Subscriber<? super DeviceDomain> subscriber) {

                final Realm realm = Realm.getDefaultInstance();
                final DeviceEntity device = realm.where(DeviceEntity.class).equalTo(Constants.DEVICE_PARAM_DEVICE_ID, deviceId).findFirst();
                final DeviceEntity deviceToBeAdded = realm.where(DeviceEntity.class).equalTo(Constants.DEVICE_PARAM_DEVICE_ID, deviceIdToAdd).findFirst();

                if (device != null && deviceToBeAdded != null) {

                    realm.executeTransaction(new Realm.Transaction() {
                        @Override
                        public void execute(final Realm realm) {
                            device.getConnectedDevices().add(deviceToBeAdded);
                            realm.copyToRealmOrUpdate(device);
                            device.addChangeListener(new RealmChangeListener<DeviceEntity>() {
                                @Override
                                public void onChange(DeviceEntity element) {
                                    device.getConnectedDevices().size();
                                    subscriber.onCompleted();
                                    realm.close();
                                }
                            });
                        }
                    });
                }

            }
        });

In this case onChange is never called, exception being thrown java.lang.IllegalStateException: You can't register a listener from a non-Looper thread or IntentService thread. If I change Scheduler from subscribeOn to AndroidSchedulers.mainThread onCalled

cmelchior commented 7 years ago

@dzakens It would be easier to debug this if you created a sample project demonstrating the problem. Trying to debug code snippets is rather difficult and error prone.

kneth commented 7 years ago

@dzakens Did you solve the issue? Or can you help us by creating a sample project?

kneth commented 7 years ago

@dzakens Please reopen or create a new issue if you can take the time to create a sample project.