Closed cmelchior closed 7 years ago
Cocoa implementation is here: https://github.com/realm/realm-cocoa/pull/4744
I actually am not sure what this is for exactly.
It is only useful in a synchronised setting, but there it is very useful any time you want to count anything, since a normal integer field would get overridden with last-write-wins
which means you could "lose" something you counted. A Counter CRDT is guaranteed to always converge with the semantics that increments from all devices are included.
Some use cases:
Are RealmCounter
and RealmInteger
different things? I like the name RealmCounter
better... It seems to me that the feature of this thing is that it is a counter, not that it is an integer,
@bmeike See https://github.com/realm/realm-object-store/issues/357 for the discussion with the rationale for the name.
Some description of use cases for the difference between managed/unmanaged states
// Unmanaged variants
RealmInteger ri = new RealmInteger()
ri.isManaged() == false
ri.isValid() == false
// Managed variant
realm.beginTransaction();
AllTypes obj = realm.createObject(AllTypes.class);
realm.commitTransaction();
RealmInteger managedRi = obj.getRealmInteger();
managedRi.isValid() == true
managedRi.isManaged() == true;
// Deleting object will invalidate it
realm.beginTransaction()
obj.deleteFromRealm();
realm.commitTransaction();
managedRi.isValid() == false
managedRi.isManaged() == true
A further clarification of semantics:
party1 = new Party();
party2 = new Party();
party1.increment(1);
party2.guests = party1.guests;
party2.guests.longValue() == 1; // true // value is copied
party1.guests.equals(part2.guests) // true // objects are considered equal
party1.guests == party2.guests // false -> different objects
// Mixed behaviour, ignore missing transactions
Party party1 = realm.createObject(Party.class);
Party party2 = new Party();
party1.guests.increment(1);
party2.guests = party1.guests;
party2.guests.longValue() == 1; // true
party1.guests.equals(party2.guests) // false -> One is managed, other is not
party1.guests == party2.guests // false -> different objects
// Managed behaviour
Party party1 = realm.createObject(Party.class);
Party party2 = realm.createObject(Party.class);
party1.guests.increment(1);
party2.guests = party1.guests;
party2.guests.longValue() == 1; // true
party1.guests.equals(party2.guests) // false -> Both are managed, and have the same value, but point to two different rows.
party1.guests == party2.guests // false -> different objects
... and more clarification. Assuming:
public class Party extends RealmObject {
// ...
public RealmInteger guests;
}
First of all, guests
is null. It could be initialized on creation and, possibly, final
(... requiring this might be interesting). If it is not, though, we need at least this:
party1 = new Party();
party1.guests = new RealmInteger(0);
party2 = new Party();
party2.guests = new RealmInteger(30);
party1.guests.increment(1);
party2.guests = party1.guests;
party2.guests.longValue() == 1; // true: value is copied
party1.guests.equals(party2.guests); // true: objects are considered equal
party1.guests == party2.guests; // false: different objects
This is definitely do-able. It is interesting, though, to compare this to what happens if the references in the scenario are NOT members of a RealmObject:
party1Guests = new RealmInteger(0);
party2Guests = new RealmInteger(30);
party1Guests.increment(1);
party2Guests = party1Guests;
party2Guests.longValue() == 1; // true: value is copied
party1Guests.equals(party2Guests); // true: objects are considered equal
party1Guests == party2Guests; // true: duh! you just assigned it 3 lines ago!
Note that, even if it were possible to change this behaviour, we certainly better not.
I'd like to suggest the following alternative semantics. They seems pretty consistent, to me. The one thing that I can think of, that this table doesn't completely address, is how we initialize the value of a Realm-created RealmInteger
. That is, assuming class Party
, from the example above, and the rules below, what is the value of realm.createObject(Party.class).guests
?
I would suggest it should be a RealmInteger
created as part of creating the Party
object and reflecting the current value of the underlying core value.
Adopting the semantics suggested in the table above, the sample code would work like this:
party1 = new Party();
party1.guests = new RealmInteger(0);
party2 = new Party();
party2.guests = new RealmInteger(30);
party1.guests.increment(1);
party2.guests = party1.guests; // legal: both objects are unmanaged
party2.guests.longValue() == 1; // true: party2's guests were replaced by party1's
party1.guests.equals(part2.guests); // true: objects have the same value
party1.guests == party2.guests; // true: same object
// Mixed behaviour, ignore missing transactions
Party party1 = realm.createObject(Party.class); // assuming party1's guest count, as known to core, is 0
Party party2 = new Party();
party2.guests = new RealmInteger(30);
party1.guests.increment(1); // throws outside a transaction
party2.guests = party1.guests; // throws: can't put a managed RealmInteger into an unmanaged object
party2.guests.set(party1.guests.getLong()); // legal anywhere: party2 is unmanaged
party2.guests.longValue() == 1; // true
party1.guests.equals(party2.guests); // true: both objects have the same value
party1.guests == party2.guests; // false: different objects, one is managed, the other is not
party1.guests = party2.guests; // throws: assignment to a managed RealmInteger ref not allowed
party1.guests.set(party2.guests.getLong()); // works in a transaction, fails outside
// Managed behaviour: ignore missing transactions and initialization.
Party party1 = realm.createObject(Party.class); // assuming party1's guest count, as known to core, is 0
Party party2 = realm.createObject(Party.class);
party1.guests.increment(1);
party2.guests = party1.guests; // throws. assignment to a managed RealmInteger ref not allowed
party1.guests.set(party2.guests.getLong()); // works in a transaction, fails outside
party2.guests.longValue() == 1; // true
party1.guests.equals(party2.guests); // true -> both objects have the same value
party1.guests == party2.guests; // false -> different objects
Some notes:
We don't generally allow final fields in Realm Model classes. Mostly because they don't make sense for an object that live updates. @LinkingObjects
are kinda a special case.
The initial value of RealmInteger
would depend on the @Required
annotation. If present, default value is 0, if not, default value is null
(Since column then is nullable). This is the same pattern that Integer
follows.
I could live with the semantic outlined in https://github.com/realm/realm-java/issues/4266#issuecomment-305333092, I'm still a bit uneasy about party1.guests = party2.guests; // throws: assignment to a managed RealmInteger ref not allowed
though, but the opposite would be akin to operator overloading (replacing =
with original.set(otherRi.longValue()
which could probably also throw some people of. Your approach makes the behavior explicit. The downside is that the exception will not be thrown until runtime since it would legal when writing it in the editor.
In any case, this is probably fine for the first implementation of this. Depending on feedback, it also seems possible to lift the restriction that you cannot assign to managed RealmIntegers later. Going in the other direction would definitely be more painful.
Got it.
FWIW, note that the RealmInteger
actually is a bit like@LinkingObjects
. The reference in the containing object is not live and does not get updated. It is only the value to which the RealmInteger
is a reference, that is live. This is very different, from, say, a reference to an Integer
which is not a reference to an updatable thing but actually the thing that is updated.
Also, I completely agree about party1.guests = party2.guests; // throws: assignment to a managed RealmInteger ref not allowed
. If we can think of some consistent meaning for it, I'd be all over it.
Proceeding under the assumption that we have a basic plan, here, and will probably need only to touch it up, later.
I don't think we should interfere unmanaged model object.
We should always allow party1.guests = party2.guests;
if party1
is unmanaged in order to keep Java's semantic in unmanaged object.
And I think we already allow to assign managed RealmList
to a field in unmanaged object.
There is discussion about the setter for a RealmInteger
. I submit the following as my train of thought concerning the issue. I am, of course, open to suggestion.
The user creates a field:
private RealmInteger myCounter;
The signatures getter/setter that every Java dev expects (and that are auto-generated by IDEs) for this field, are:
public void setMyCounter(RealmInteger myCounter)
public RealmInteger getMyCounter()
These methods do not take/return Objects
. They do not take/return Long
or Integer
. They do not take/return Number
. They take/return RealmIntegers. The other possibilities require runtime checking that should have been compile time checking.
While I hold the view with equal strength, my argument for disallowing the assignment of a managed RealmInteger, to an unmanaged RealmObject is not as clear. It is, essentially, that I cannot think of any circumstances that it is not a mistake. All of a sudden, your unmanaged object cannot be passed between threads! All of a sudden some parts of your unmanaged object can be changed only in a transaction. To me, it doesn't make sense.
Some of the confusion may be due to a misunderstanding about what is a reference to what. A java Integer
and a java int
are the same in the following respect: you cannot assign them. Forgive me if this is all obvious. I'm not certain we are on the same page.
You can do this:
class C {
private int x = 3;
private Integer y = Integer.valueOf(4);
{
x = 5;
y = Integer.valueOf(6)
}
... but you cannot do this:
{
x.set(7);
y.set(Integer.valueOf(8));
}
The location called x
holds a value. The location y
hold a reference to an immutable value. Because it is immutable, it is almost exactly as if y held the value itself.
RealmIntegers
are not immutable. You can do this:
class C {
private RealmInteger z = RealmInteger.valueOf(11);
{
z.set(42);
}
A RealmInteger
does not function as a value. Instead, it functions as a reference to a reference to a native value. Two references. Like this:
C.z -> RealmInteger1 -> native value
so, re-assigning C.z does this:
RealmInteger1 ----- \
C.z -> RealmInteger2 -> native value
Exactly nothing has happened.
We absolutely could change these semantics. We could pretend that the middle reference didn't exist. We can only do it for RealmObjects, though. If we do it, as I pointed out previously, it makes the behavior of perfectly vanilla assignment different for fields of RealmObject
than anywhere else.
I argue against doing that.
If umanagedRealmInt = managedRealmInt
is not allowed, then managedRealmInt1 = managedRealmInt2
should not be allowed as well if they point to different rows.
But those behaviours are quite different from how we support boxed types.
We are adding hidden setters/getters (such as realmSet$foo()
) only for our proxy's intercepting field values. We should not do more than that even if users are doing wrong thing.
And we already have RealmList
which is similar to RealmInteger
. Those are references to internal native data. We should not introduce different rules to them.
If we change the rule, it should be happen in the next major version as a breaking change.
Sorry for wall of text. Also feel free to edit the comment if you think I didn't represent your oppinion correctly. TLDR: What a mess :/
If umanagedRealmInt = managedRealmInt is not allowed, then managedRealmInt1 = managedRealmInt2 should not be allowed as well if they point to different rows. But those behaviours are quite different from how we support boxed types.
I think that is @bmeike point. RealmInteger
isn't a boxed type, it is a weird mix between a true object and a datatype wrapper. That it isn't immutable is what is causing a big headache, but I agree that if we disallow one, we should probably disallow the other as well.
And we already have RealmList which is similar to RealmInteger. Those are references to internal native data. We should not introduce different rules to them.
RealmList is something slightly different, but that said, you are right. We already have precedence for referencing managed data from un-managed datatypes. This is e.g useful when preparing objects for copyToRealm()
. The consistency argument is a pretty strong one IMO, especially in the light of clear "correct" alternatives.
Let me try to summarize the advantages / disadvanteges so far. Perhaps that will make agreeing on something easier:
Allow unmanagedObj.realmInteger = managedObj.realmInteger
and managedObj1.realmInteger = managedObj2.realmInteger
Basically treat RealmInteger as a datatype instead of an object reference. We will copy it when needed.
Advantages
unmanagedObj.realmList = managedObj.realmList
. I don't recall anyone reporting problems with this ever?unmanagedObj.realmInteger == managedObj.realmInteger // true
Disadvantages
managedObject1.realmInteger = managedObject2.realmInteger;
managedObject1.realmInteger == managedObject2.realmInteger // false
Disallow unmanagedObj.realmInteger = managedObj.realmInteger
and managedObj1.realmInteger =
managedObj2.realmInteger``
We elevate RealmInteger
to a "special" type that cannot be assigned once managed, but need to be manipulated using methods. This is so the underlying implementation is more visible to users.
RealmList does something similar as if you do managedObj1.list = managedObj2.list
it will actually copy all values from obj2
to obj1
: https://github.com/realm/realm-java/blob/master/realm/realm-annotations-processor/src/test/resources/io/realm/AllTypesRealmProxy.java#L351
Advantage
managedObj.realmInteger.set(unmanagedObj.realmInteger.longValue());
Disadvantage
Treat RealmInteger as a first class object reference instead of a row value. This allow multiple objects to reference the same "Counter" and will make it behave like a RealmObject reference.
Advantages
Disadvantages
Will require changes to Object Store. It must be able to understand RealmInteger as a special type. This hasn't been designed yet and we would need to consider how this will scale down the line.
We probably need to implement "aliases" in the query layer:
// If RealmInteger is a normal RealmObject
realm.where(Party.class).equalTo("guests.value", 42);
// Should be automatically converted so this is possible
realm.where(Party.class).equalTo("guests", 42);
Treat RealmInteger as an immutable datatype. Don't allow modifications in-place. set()/increment()/decrement()
all return the modified value, instead of modifying in place
Advantage
Disadvantages
RealmInteger
as a distributed counter is going to be really wonkey:
obj.realmInteger = obj.realmInteger.increment(1);
For .NET, I'm fairly certain RealmInteger
will be a mutable reference type. We'll probably introduce implicit conversion operators so you can use it without casting in methods that expect integers.
It will likely have to keep a reference to its owning object and property index like:
public class RealmInteger
{
internal RealmObject Parent { get; }
internal int PropertyIndex { get; }
}
Additionally, we'll generate a setter in the parent object that will call Reset rather than replace the object reference. So in the case of firstObj.RealmInteger = secondObj.RealmInteger
, under the hood, the setter will get the value stored in RealmInteger
and call firstObj.RealmInteger.Reset(secondObj.RealmInteger.Value)
. With this approach, we don't care whether the parent objects are managed or not as no transfer of ownership occurs. Obviously, this will be subject to the threading confines that all RealmObject
s are, but I guess our users would expect that.
@zaki50 has convinced me that it is more consistent, albeit abhorrent, to allow the assignment of a managed RealmInteger
to an unmanaged subclass of RealmObject
. Changing the implementation.
actually @cmelchior 's option 4 looks very charming to me :P but it will totally create a mess if user decides to have a public RealmInteger
field.
Yeah, I agree, #4 is nice. Making it immutable would be a good thing. Note that it has an affect on the getter too:
RealmInteger obj = managedObject.getRealmInteger();
obj
is not a live object. It's value does not change. In order to find the current value of the counter, you have to ask again.
The alternative would be a bit weird: you cannot change the value of the RealmInteger, but it might change value behind your back. Certainly possible to implement, but, I think, way weird.
After an extended and enlightening discussion with @nirinchev , I think that Java is sufficiently different (cannot change the meaning of ==
) so that we cannot use .NET as a model.
Option 2 makes most sense to me as you can always just do unmanagedObj.realmInt = new RealmInt(managedRealmInt)
I don't understand Option #3. I am currently implementing Option #1, based, as I said, on @zaki50's completely convincing argument.
@Zhuinden : @zaki50 convinced me with the following argument. We support:
class C extends RealmObject {
public C myC;
}
// ...
C aC = new C();
aC.myC = realm.where(C.class).findFirst().myC;
if we support that, we kinda have to support assignment of a managed RealmInteger
to an unmanaged instance of a subclass of RealmObject
.
Not that I don't like your solution better. It isn't consistent, though.
If we prohibit to call setters of RealmInteger
field, we need to provide a way to nullify and un-nullify (is this correct wording?) the field.
I think we have two points not discussed yet.
One is RealmInteger
field can be nullable or not. I think it can be nullable, but no deep discussion yet.
Another is how we define the semantics of Realm.*OrUpdate()
methods.
In my understanding, current semantic of update()
is that passing each field value to another's field respectively.
If we prohibit to assign RealmInteger
to a field in managed Realm Object, Realm.*OrUpdate()
does not work against objects that have RealmInteger
field anymore.
If we allow to work Realm.*OrUpdate()
against those Realm objects, I think that we should allow to assign RealmInteger
to managed Realm object as well.
What do you think? @realm/java
Field can definitely be nullable, and prohibiting copyOrUpdate would also be really strange.
I can't imagine plausible implementation in which *OrUpdate didn't work. No idea where that came from. Is it a requirement that it works by calling the synthetic setter? Totally didn't know that!
Also, please clarify what it means to set a counter to null? To me that seems to be the same thing as setting an int
to null. If you can define semantics, though, I can write the code!
We have to keep in mind that under the hood, the counter is just an integer, so making it required would be artificial limitation. Not sure what the increment and decrement instructions will result in when applied on null though. Probably someone from the core team can share? cc @finnschiermer
Yeah. That discussion is complicated, though, by the fact that Java has two kinds of integer, int
and Integer
. Former is not nullable; latter is.
I'm more interested in what it means. The operations on a RealmInteger
are set
, increment
, and decrement
. There are no corresponding operations on int
or Integer
.
A better comparison might be RealmList
. What happens if, in a shared Realm, device X sets a RealmList
to null, at the same time that device Y adds two items to the exact same RealmList
?
Just talking to the guys here in the SF office. Is it the case that we do not allow nulling a RealmList
? If so, I'm back in the "we shouldn't allow nulling a RealmInteger
" camp
RealmList
is not nullable because it doesn't make sense to be, but I'm not sure if it's relevant to the integer discussion.
@simonask can you shed some light on how a set(null)
is merged with increment(1)
on an integer column?
I feel we should not place arbitrary restrictions unless those are imposed by core or sync. As a very artificial example, I can have a Timer
object with a RealmInteger Seconds
property and I may want to differentiate between a timer that hasn't been started (Seconds == null
) and a timer that has just started (Seconds == 0
). Obviously, this can be modeled differently, but that's generally the case for most scenarios involving nullable types, yet we allow them :)
@nirinchev Just to be sure we are on the same page, here: There are two objects in Java (one more way to look at it: there is no such thing as a type alias, in Java) . In Java, there will be an artificial object, a mutable RealmInteger
that is a reference to the core value. Note that that is much more similar to a RealmList
, a container for values, than it is to an integer valued field.
In Java, we will be able to control what happens when you use the assignment operator (=
) when it is used in the context of a subclass of RealmObject
. We will not be able to control what it does outside that context.
Here is what an Integer valued field looks like in Java:
x[ ] --> i
That is, there is an object with a field that contains a value "i". If you want to add one to i, you get j:
p = *x
x[ ] --> j
*x != p // true: x points at a new thing. i and j are different things. This is true for both int and Integer
That is not the way counters work. Counters work like this:
x[ ] --> m[ ] --> i
That is, there is an object with a field that contains a reference to a container that contains a value. In particular, adding one to the value of i does not change x's value:
p = *x
x[ ] --> m[ ] --> j
*x == p // true. x is still pointing at the same thing.
Under these circumstances, the effect of nulling out x's reference seems unclear to me:
x[ ]
i
The value i is still there. It can no longer be changed from x, though. I have argued in the past, btw, that assigning x's reference at all seems of dubious value. It does this:
x[ ] --> n[ ] --> i
m[ ]
It has not changed the value i. It has change x's reference.
The current implementation will eliminate that problem by translating all assignments to x's reference into assignments to m's value. That will work only when assigning to a field on a RealmObject
, though. As I pointed out above, though, assignment will have its normal meaning everywhere else. I admit to finding the inconsistency quite bothersome.
Just talking to the guys here in the SF office. Is it the case that we do not allow nulling a RealmList? If so, I'm back in the "we shouldn't allow nulling a RealmInteger" camp
We don't allow null
for RealmList
is simply because of core doesn't support it which it should since an empty list and a null list are totally two different things.
@simonask can you shed some light on how a set(null) is merged with increment(1) on an integer column?
Yes, the increment is discarded.
Note that Core considers an increment on a null value as a logic error. This means that as opposed to Set
with a value, which only discard AddInteger
instructions with a lower timestamp (resetting the counter to a particular value), Set(null)
discards even AddInteger
instructions with a higher timestamp (since there is no way to apply the addition to a null value).
@bmeike We have the same behaviour elsewhere, because the Java objects are just pointers into the underlaying data, which means that things can change under the hood, so things like "null" somethings get represented slightly weird:
Person p = realm.where(Person.class).findFirstAsync(); // Works like an optional/future kind of thing
p.isLoaded(); // = false, object represents the intent to load things
p.load();
p.getName(); // Now points to data
p.deleteFromRealm(); // Object effectively become "null"
p.isValid(); // = false, indicating that the wrapper is now "empty"
// RealmInteger is also a "wrapper" type like the `Person` above
Party p = realm.where(Party.class).findFirst();
RealmInteger guestsRef = p.getGuests();
guests.longValue(); // 0
p.guests = null;
guestsRef.longValue(); // Crash, since it is now null?
// We could allow RealmInteger to contain the value "null" while still not allowing the
// reference to the RealmInteger to be null in Java. Having such requirements
// are really awkward although we already have it on RealmList.
p.guests = null; // Never allowed.
p.guests.setNull(); // is Allowed
p.guests.set(null); // or this.
p.guests.isNull(); // null check
To be honest, I stand with @bmeike on this. All the different versions we are discussing have flaws that are extremely weird in different cases and this whole debate really shows just how wrong the conclusion we came to in https://github.com/realm/realm-object-store/issues/357 is.
While I'm afraid to blow this whole discussion wide open again, I feel we need to take a step back.
Trying to create a datatype that functions as both an Integer and a Counter is just fundamentally not going to work well due to restrictions in the languages we want to support.
IMO the absolute simplest solution would be:
// New datatype shipped with Realm that works like an object
// Object Store is responsible for adding the class to the Realm files.
public RealmCounter extends RealmObject {
private long value;
public void increment(long val) { ... }
public void decrement(long val) { ... }
// ...
}
// My app model class
public class Party extends RealmObject {
// Normal object reference like any other
public RealmCounter guests = new RealmCounter(42);
}
// Default values implementation should automatically create it.
Party p = realm.createObject(Party.class);
p.guests.longValue(); // 42
// This is just a normal mutable object reference like any other
// It can be added to both managed and unmanaged objects just like other Realm objects
RealmCounter guestsRef = p.guests;
new Party(guestsRef)
Party p2 = realm.createObject(Party.class).setGuests(guestsRef)
// p2.guests point to the same object as p.guests
// Only problem is what happens if you delete object
// Without support for cascading deletes, removing the counters
// again will be slightly cumbersome.
// I'm actually fine with this as we will have cascading deletes down the line
p.deleteFromRealm(); //
Consequences:
RealmCounter
class and migrating any existing schemas. We need to consider the case where people had their own RealmCounter
class.Thoughts @bmeike @nirinchev @austinzheng ?
@cmelchior
I don't quite agree with that.
Yeah, the proposal makes the semantics closer to RealmObject
, but further from RealmList
.
RealmList list1;
obj1.setList(list1);
obj2.setList(obj1.getList());
obj2.getList().add(1);
print(obj1.getList().size()) // 0
print(obj2.getList().size()) // 1
I think making the Counter
with that semantics make our situation unnecessary complex.
The semantics to RealmInteger
should be similar to the RealmList
if it is good enough to solve the original problem for us.
This seems wrong:
public RealmCounter extends RealmObject {
... unless RealmObject
means something totally different in Java than what I thought?
No, this is exactly what I mean. RealmCounter is a separate class with one INTEGER
column named value
.
@cmelchior I agree with mulong and I think your new proposal is losing a way for counters to be @Required
.
@zaki50 Not sure what you mean by " losing a way to counters to @Required."
But @zaki50 / @beeender what do you suggest we do then?
@cmelchior In your proposal, I don't think users can mark guests
field as @Reqired
since it is a relationship to another object.
public class Party extends RealmObject {
@Required // Users can't do like this
public RealmCounter guests = new RealmCounter(42);
}
At least, current @bmeike 's implementation is allowing that.
Yes, that would be a drawback. That is correct. If it is a problem or not is up for debate though.
I feel strongly that counters should allow for being required. That way, I can use the type safely without worrying about null checks and stuff :)
hmmm, now I got the point, nulling is a problem:
public class Something extends RealmObject {
RealmInteger counter;
}
Something obj = realm.where(Something.class).findFirst();
Counter counter = obj.getCounter();
obj.setCounter(new RealmInteger(1));
counter.longValue(); // 1
obj.setCounter(new RealmInteger(2));
counter.longValue(); // 2
obj.setCounter(null);
counter.longValue(); // NOTHING would be correct to return here.
I am actually thinking another approach to make the situation simpler, since what we need is the operation for increment/decrement, so something like a static method somewhere would be good enough without introducing a new type?
public class RealmObject {
public static incrementIntegerField(String fieldName, long value);
public static decrementIntegerField(String fieldName, long value);
}
with some annotation tricks, it might be still easy to use.
@beeender I guess you mean counter.set(null); counter.longValue()
, but RealmInteger.set()
takes long
as an argument.
@zaki50 Sorry, put a wrong example. It was just updated.
@beeender got it. I was thinking the same thing and writing my proposal.
About managed RealmObject
:
public class Foo extends RealmObject {
public RealmInteger realmInteger = RealmInteger.valueOf(42);
}
Foo managed0 = realm.createObject(Foo.class);
// comparing the same field
managed0.realmInteger == managed0.realmInteger // true. managed object caches `RealmInteger` instance like `RealmList`.
// comparing RealmIntegers those have the same value but belong to different managed object
Foo managed1 = realm.createObject(Foo.class);
managed1.realmInteger == managed0.realmInteger; // false.
managed1.realmInteger.equals(managed0.realmInteger); // true. equals only check the current values
// after a field becomes null
RealmInteger oldManaged1Value = managed1.realmInteger;
managed1.realmInteger = null; // internally cached RealmInteger is also disposed.
managed1.realmInteger // returns null
oldManaged1Value.isValid() // false
oldManaged1Value.longValue();// throws IllegalStateException since the oldManaged1Value is invalid. same with deleted RealmObject
// assigning a value to null field
managed1.realmInteger = RealmInteger.valueOf(100)
managed1.realmInteger == oldManaged1Value; //false. Once new value is set to null field, getter returns new RealmInteger (and cache it).
managed1.realmInteger.longValue(); // of course 100
// assigning managed RealmInteger
managed1.realmInteger = managed0.realmInteger; // identical to managed1.realmInteger.set(managed0.realmInteger.longValue);
managed1.realmInteger == managed0.realmInteger; // false. since each getter returns its cached RealmInteger.
managed1.realmInteger.longValue(); // 42
(The original initial comment for this issue is here. This heading reflects the current understanding of the project)
The basic semantics for this feature, now called
MutableRealmInteger
have finally been sorted out here.This issue now tracks implementation.
TODO: