(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:
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:
``` // Unmanaged behaviour
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:
@LinkingObjects
are kinda a special case. 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
I'm thinking about the following .NET API:
class RealmInteger
{
void Increment(long value);
// implicit conversion, comparison operators to long
}
class NullableRealmInteger
{
bool HasValue { get; }
long Value { get; }
void Increment(long value);
// implicit conversion operators to long?
}
class Party : RealmObject
{
// Implicit [Required]
RealmInteger Guests { get; set; }
NullableRealmInteger NullableGuests { get; set; } // Always return NullableRealmInteger, even if null
}
// Usage:
var party = realm.Find<Party>(1);
Console.WriteLine(party.Guests); // 0
party.Guests = 5; // 5
party.Guests.Increment(3); // 8
party.Guests.Increment(-2); // 6
var guests = party.Guests;
guests.Increment(); // Allowed
party.Guests; // 7
// For the nullable case:
party.NullableGuests; // null
party.NullableGuests.HasValue; // false
party.NullableGuests.Value; // exception
party.NullableGuests.Increment(); // exception
party.NullableGuests = 4;
party.NullableGuests.HasValue; // true
party.NullableGuests.Value; // 4
party.NullableGuests.Increment(); // 5
var nullableGuests = party.NullableGuests;
nullableGuests.Increment(); // Allowed
party.NullableGuests; // 6
party.NullableGuests = null;
nullableGuests.HasValue; // false
nullableGuests.Increment(); // exception
I'm still playing with the API and trying to find a way to merge the two types, but it feels like it won't be very intuitive and nice to use - the main problem being that if it supports null, we can't provide implicit conversion operators to long
, which means people won't be able to pass it to methods expecting long
without casting, even if they mark it [Required]
.
@zaki50
oldManaged1Value.longValue();// throws IllegalStateException since the oldManaged1Value is invalid. same with deleted RealmObject
Is what bothers me, because not having a method to check for this sounds really bad to me. This indicate that you should really have a RealmInteger.isNull()
method. But on the other side that seems really odd if you have
@Required RealmInteger guests;
I guess having NullableRealmInteger
and RealmInteger
like @nirinchev says, would fix that, but it is a departure from what we have done so far, so I would really like to avoid that, to be honest. Especially since we already have the @Required
annotation.
Without adding a completely new type like RealmCounter
, the least clunky I have come up with so far is adding RealmInteger.isNull()/RealmInteger.setNull()
methods. It would still allow you to make the field @Required
and then you can use the two extra methods if you want. Same as we have e.g isValid()
on un-managed RealmObject.
I'm thinking that RealmInteger
should have isValid()
instead of isNull()
.
In most cases, I guess users get RealmInteger
instance right before they use it and they don't need to check if it's valid since if invalid, null
should be returned instead of RealmInteger
.
When users keep RealmInteger
reference elsewhere, they still need to check if it's valid even if we prohibit nullify the RealmInteger field since the owner object can be deleted.
We can't avoid this validity check in either way.
It already has isValid()
which indicate if it deleted or not. Overriding that meaning would be rather unfortunate I think. If it can be nullable, it can both have the value null
and be managed at the same time.
E.g
realm.beginTransaction()
Party p = realm.createObject(Party.class);
RealmInteger ref = p.guests;
p.guests = null;
ref.isValid(); // true, because party is not deleted
ref.isNull(); // true, because value is null
Treating RealmInteger
as always required would get rid of that, but I would rather avoid that since that is an artificial restriction.
isValid()
indicates that the underlying data is usable or not.
I don't think it should reflect that the container object is usable or not.
When a field becomes null, I treat it that the counter is destroyed and it's OK for isValid()
to return false
.
And when a new non-null value is set to a null field, I treat it that a new counter is created. That's why the RealmInteger
instance is re-created in that case.
Doesn't it make sense?
This is the javadoc for it https://github.com/realm/realm-java/blob/master/realm/realm-library/src/main/java/io/realm/internal/ManagableObject.java#L32
I could probably be convinced that we can use isValid()
to indicate a null
value. Although I still think there are cases where an isValid()
and isNull()
would return different results. If those cases are important is another matter.
So I assume you want to just have isValid()
and =
as the way of setting null then?
party.guests.isValid(); // true
RealmInteger ref = party.guests;
party.guests = null;
ref.isValid(); // false
Users can check if the field is null
by party.guests == null
.
As you said, if there is a case that requires to check it only with RealmInteger
instance, that's a matter. I don't think any proposal solves this though.
So I assume you want to just have isValid() and = as the way of setting null then?
Exactly!
@simonask Sigh. Really looking forward to being able to discuss this without an 8-hr delay, next week. I think you are missing the point. This doesn't have anything to do with nulling the value to which the RealmInteger points. This is about whether you can null the pointer to the RealmInteger. There is no equivalent concept in core.
@cmelchior If core support nullable integers (meaning, roughly, "no data available"), then I absolutely agree, that c.guests.set(null)
is consistent. All that takes is having RealmInteger.set()
take a Long
, instead of a long
. Absolutely no problem.
I see only one way that c.guests = null
makes sense, and that is if we translate it into c.guests.set(null)
. And that works for me, as long as we can live with the following wildly un-Java-like behavior:
c.guest = null
RealmInteger cGuest = null
cGuest == c.guest // false
I like the idea that a Realm{Counter/Integer}
is a RealmObject
. I also don't see why it couldn't be @Required
. In fact that is almost precisely the implementation for which every has been arguing for the last week. There are two differences:
You mean because it would be like saying null == null; // false
?
Given what other trade-offs we are debating currently, I could probably live with that.
Yes. Exactly. That totally weirds me out. It does not, however, weird out either you or Alexsander, and that works for me! ;-)
It does weird me out, but since I cannot get everything I want :man_shrugging: 馃槃
@beeender Your argument about RealmList
s is quite apropos. So what happens when I write, in my code:
class C extends RealmObject {
private RealmList foo;
{ foo = null; }
}
... and when you say "Well! That's not allowed!", please go have a chat with @zaki50. I'll be right here. ;-P
I think the thing that it seems to me that both of you are missing is that it IS a reference to another object! There is nothing you can do about that. We are defining a type. Sadly, unlike Haskell, Scala, Swift, or a bunch of other languages, Java does not have type aliases. We can hide the fact that it is a reference some of the time. We cannot hide the fact that it is a reference all of the time.
I think there is a great deal of confusion about the difference between the reference to the counter object and the value of the counter object. The attempts to conflate them might work, but lead to some inconsistencies that, seem to me, to suck. But they do not suck enough so that I am going to put up any further resistance.
I think that Alexander and Christian are advocating a solution that essentially hides the references to the RealmInteger
in the context of a RealmObject. They cannot hide it outside the RealmObject
. That's easy to say, easy to describe and easy to document. It will surprise people but we can say "I tole you so".
I think that @zaki50 is sort of headed in the same direction but even less consistent:
// after a field becomes null
RealmInteger oldManaged1Value = managed1.realmInteger;
managed1.realmInteger = null; // internally cached RealmInteger is disposed.
managed1.realmInteger // returns null
oldManaged1Value.longValue();// throws IllegalStateException since the oldManaged1Value is invalid. same with deleted RealmObject
WTF? So, how do I find out if I have set the value of managed1.realmInteger
to null
. Like this???
managed1.realmInteger.isNull() // NPE!
I'm going to continue working towards the hidden RealmInteger
implementation... but very very carefully. Hopefully in-person discussions in CPH will help.
I would like, also, to take this moment, to mention that it would, absolutely, be possible to have this discussion -- perhaps, even, to bring it to some kind of conclusion -- before implementation starts. That would be awesome.
No need to have isNull()
. Users can do managed1.realmInteger == null
like other nullable types.
I would like, also, to take this moment, to mention that it would, absolutely, be possible to have this discussion -- perhaps, even, to bring it to some kind of conclusion -- before implementation starts.
I apologize about this. It's my fault.
No need to have isNull(). Users can do managed1.realmInteger == null like other nullable types.
I do believe you are correct! Boy, are people going to be surprised the first time they try any of this stuff outside the context of a RealmObject!!!
I apologize about this. It's my fault.
Definitely not your fault. It is systemic. I could have insisted on clarity, before I got started.
Does the Java binding have a compile time way to warn people not to use realmInteger outside the context of a RealmObject? E.g. detect that there's a local variable of RealmInteger type and emit a warning/error?
Yeah, we could probably do that. This just gets weirder and weirder.
Look, this is just a horrible precedent. We are gonna build something that behaves in such a completely non-Java way, that we are actually talking about using magic to warn them against using it normally. I thought that with our byte-code re-writing powers came great responsibility! I thought we were using them for good! ;-P
I do believe you are correct! Boy, are people going to be surprised the first time they try any of this stuff outside the context of a RealmObject!!!
What do you mean?
I don't see any surprising result.
RealmInteger oldManaged1Value = managed1.realmInteger;
oldManaged1Value.isValid() // true
realm.beginTransaction();
managed1.realmInteger = null;
realm.commitTransaction();
managed1.realmInteger == null // true
oldManaged1Value == null // false of course
oldManaged1Value.isValid() // false since the counter is disposed
oldManaged1Value.longValue();// throws IllegalStateException
You know what they say, the path to hell is lined with good intentions ;) At this point I'm lost as to what the most Java-friendly implementation would be 馃槙
Yeah, this is a horrendously tricky design problem.
The alternative is to have a method on RealmObject
s directly that allows you to increment a property, but I don't think any of our supported languages (except maybe C#) allow you to reify the concept of "a property on this object", which means it would have to be stringly typed 馃槶 .
What does it mean for a realmInteger
to be null
?
While I realize that RealmInteger
is this funky number that you can increment, decrement, and set value to, and if you don't do set
then the inc/dec
operations get merged together instead of overwriting each other, right? And it seems you can set the value in the RealmInteger
be null
although I assume calling increment/decrement
on a RealmInteger
which has field null
will crash, so initializing to null
in a distributed setting seems like a very powerful way to shoot yourself in the foot during operation merge (or whatever it's called).
Anyways, why not do something with RealmInteger
like what you did with @LinkingObjects private final RealmResults
?
So, for example
public class RealmInteger extends RealmObject {
private Long value;
public Long get() {
return value;
}
public void set(Long value) {
this.value = value; // throw in proxy accessor if marked with `@Required`
}
public void increment() {
value += 1; // do your native magic for RealmInt with accessor
}
public void decrement() {
value -= 1; // do your native magic for RealmInt with accessor
}
}
and then you can do
public class Post extends RealmObject {
private final RealmInteger likes = /*cannot be null*/ RealmInteger.valueOf(0); // initial value if doesn't exist in Realm
public RealmInteger getLikes() {
return likes;
}
}
Then the only tricky things you need to consider then is how to handle the set value of RealmInteger in unmanaged object that you insert, but you could ignore the initial value while if it was explicitly set then even from unmanaged object it would be a "set" operation for a managed one, maybe?
Query support would be tricky in the sense that you wouldn't want to do realmInt.value
for queries, you want to do realmInt
, so the query api would probably need to check against the type and if it's RealmInt then handle it a bit differently, I guess.
@Zhuinden I'm all over that. Very Java. I'm losing the argument, though. Bring friends! ;-P
What do you mean?
I can't see any surprising result.
I don't think you are looking in the right place!
managed1.realmInteger = null;
managed1.realmInteger == null // true
// some code that may or may not delete this entire row
managed1.realmInteger.isValid() // what does this return?
The ref is null. It was assigned null, right there. I suspect you are gonna want isValid()
, above, available to distinguish between whether the null value for this row's realmInteger
has been deleted or not. And, despite that fact that that is an abomination in the Java language, we absolutely can make that work. It looks really weird, though, and we cannot make it work outside the context of a RealmObject
. Outside the RealmObject
it will NPE.
managed1RealmInteger = null;
managed1RealmInteger == null // true
// doesn't matter what the code here does
managed1.realmInteger.isValid() // NPE!!!!
Here's another example. How about this:
managed1.realmInteger = new RealmInteger(10);
myRealmInteger = managed1.realmInteger;
myRealmInteger = null;
managed1.realmInteger == null // ???
That one is gonna be surprising, dontcha think?
Btw, if these are the semantics, then it actually makes no difference at all whether the RealmInteger
object is lazy or always there. You are depending on its being invisible. If it is invisible, I can make it lazy or permanent, without any affect on the API. Currently, my implementation is permanent.
If the row is deleted, managed1.realmInteger
will throw IllegalStateException
.
If not deleted, managed1.realmInteger
returns null and calling isValid()
against it causes NPE.
managed1.realmInteger = new RealmInteger(10);
myRealmInteger = managed1.realmInteger;
myRealmInteger = null;
managed1.realmInteger == null // ???
false
since myRealmInteger = null;
just cleared the local variable. No effects against persisted data.
By the way, I realized that my proposal is difficult (almost impossible) to implement collectly againt changes from other threads (or other devices).
https://github.com/realm/realm-java/issues/4266#issuecomment-307335626 is the simplest solution??
Is there really a point to being able to do new RealmInteger
instead of only RealmInteger.valueOf()
inside a RealmObject?
That's mostly what I wonder about, can a RealmInteger have meaning outside of a RealmObject (just like how linkingObjects realmResults only exists in realm models as well)?
Then again I'm a bit sleepy so maybe I'm not taking something important into consideration.
@zaki50 : got it. What about this:
managed1.realmInteger = new RealmInteger(10);
myRealmInteger = managed1.realmInteger;
// delete the row...
myRealmInteger.isNull();
IllegalStateException
?
@Zhuinden My implementation already depends on the fact that you can't use new
on a RealmInteger
. You need to use the factory, because I return an instance of either a managed or an unmanaged subclass.
... and I totally agree: yet one more way of stating the problem is that these suckers don't mean much outside the context of the RealmObject. The original functional requirements, though, were that you be able to hold a reference to a live, updating object.
@bmeike I think that https://github.com/realm/realm-java/issues/4266#issuecomment-307348997 and following 3 comments described it.
@zaki50 Did you want to eliminate isValid
for RealmIntegers
all together and always use exceptions (or, certainly, isValid
on the containing instance) to indicate that a row has been deleted or are you advocating this only in the case of a deleted, null-valued integer? ... and I guess I'm still missing how #4266 (comment) handles calling isValid
on a null reference.
The original functional requirements, though, were that you be able to hold a reference to a live, updating object
That's a surprise to me. Did we agree on that globally (i.e. all bindings), or was it a requirement just for the Java implementation? (and yes, Github badly needs threading...)
@nirinchev Well, as usual, the "functional requirements" were occasional comments in Slack. There was definitely common sentiment that, although you could not listen for changes on the RealmInteger
object, you could hold a reference to an (row X column) object, outside the context of the container (row) object, and that that object would be live.
I suspect that this is only the case for Java. In other bindings it is going to be much easier to hide the existence of the RealmInteger
object and, thus, the issue of holding one, outside the context of the container, is simply irrelevant.
@nirinchev has a point, btw. If this is illegal:
RealmInteger myRealmInteger = managed1.realmInteger;
... and, instead, this is legal:
int i = managed1.realmInteger;
Then we are a huuuuuge step closer to making this all work.
It is still magic of a rather greyish variety but I think that, if we give up on ever holding references to these things, then the very last of my objections to @zaki50 's proposals go away. I also think it gets Christian and Alexander where they want to be.
... and I feel like an idiot for not seeing this sooner. If this is what you all have been trying to tell me, I beg your pardon.
Actually what I was wrong about is RealmInteger extends RealmObject
, because it is not an object that can be deleted on its own. It is a ManagableObject
, so it's great that it was factored out to make this work :smile: but you're already on that.
I think what primarily causes confusion is that it's technically just an "Integer" in the core, but it is a "Counter" from the outside. But as it is a counter, it really shouldn't be just a number.
I think realmObj1.counter = realmObj2.counter
has too much implicit meaning and should be forbidden, the only thing that matters is the operators get
, set
, increment
and decrement
.
But if reassignment is forbidden, then it works very similarly to @LinkingObjects
.
@beeender 's RealmObject.incrementCounter
seems to be super-similar to https://github.com/realm/realm-object-store/issues/357#issuecomment-275555967 in concept, but is it really necessary?
Should setting a RealmInteger to null
truly mean reset? What does it mean for a counter to be nullable?
As @bmeike said, ability to set RealmCounter to null
makes calling isValid()
possible to throw NPE; which is never the case for managed RealmLists (although possible for unmanaged ones).
But what I wonder about is this comment https://github.com/realm/realm-object-store/issues/357#issuecomment-277663036 which says,
if we want a Counter type to function as a robust counter which you increment/decrement and occasionally reset then we want to make accidental reset hard.
In which case implicit resets are a bit scary on assignment between RealmInt and RealmInt - as it is not an explicit intention to "reset".
I really do wonder that it should be a final field that exposes the operators and that's that. I might have written that exact same thing at the top of this post though, but now with reference to object-store comment.
Anyways, maybe @cmelchior will know.
Ok, ladies and gentlemen. I propose the following. It matches nearly all of the requirements I've heard so far:
RealmInteger x = c.x; // compile time error: type mismatch c.x is Long. Not absolutely certain that I can arrange this...
x = new RealmInteger(); // compile time error: RealmInteger is abstract
x = new RealmInteger() { }; // compile time error: RealmInteger has no public constructor
public class C {
// blah blah...
public RealmInteger x;
public Long getX() { return x; }
public void setX(Long x) { this.x = x; }
}
realm.beginTransaction();
C c = realm.createObject(C.class);
c.x = 7;
realm.commitTransaction();
c.x == 7; // true I think
c.x.intValue() == 7; // definitely true
c.x = -1; // throws: not in a transaction
realm.beginTransaction();
c.x = -1;
c.x == -1; // true
c.x.set(4);
c.x.increment(5);
c.x.decrement(2);
C c2 = realm.createObject(C.class);
c2.x = c.x;
realm.commitTransaction();
c.x == 7; // true
c2.x == 7; // true
c.x == c2.x; // true, I think.
c.x.equals(c2.x); // definitely true
realm.beginTransaction();
c2.x.increment(1);
realm.commitTransaction();
c.x == 7; // true
c2.x == 8; // true
c.x.isValid(); // == c.isValid(). I suggest getting rid of this
c.x.isManaged(); // == c.isManaged(). I suggest getting rid of this
c.x.isNull(); // false. With @zaki50 : suggest getting rid of this
c.x = null;
c.x == null; // true;
c.x.longValue(); // NPE
C uc = realm.copyFromRealm(c);
// the value of uc.x is now frozen: it will never be updated
// uc behaves exactly as c above, except:
uc.x.isValid(); // true, except NPE if x is null: let's get rid of it.
uc.x.isManaged(); // false, except NPE if x is null: let's get rid of it.
I might have missed something and there are a couple of things that I'm not completely certain that I can pull off.
c.x = 7;
seems to be assigning int
to RealmInteger
field.
Is there any way to successfully compile it?
Yeah! I think there is! You totally inspired me! I'm not absolutely positive, but I believe I can do it.
Won't be an int
. Will be a Long
. You get your nullability!!
It depends on exactly how the pre-processor is run, and what kinds of casting java is willing to do...
I gotta try it out... but I want to know if people like it, before I spend another week or so...
Ok, one more time. Running this up the flag-pole. Who's gonna salute?
// All asserts are true unless otherwise noted.
public static void unmanaged() {
C c1 = new C();
C c2 = new C();
c1.x = null; // compiler error: c1.x is final
c1.x = RealmInteger.valueOf(7); // compiler error: c1.x is final
c1.x.set(7); // !!! NPE possible
c2.x.set(Long.valueOf(7)); // !!! NPE possible
assert c1.x.equals(c2.x);
assert c1.x != c2.x;
RealmInteger r1 = c1.x;
r1.increment(1);
assert r1.equals(c1.x);
assert r1 == c1.x;
assert c1.x.get() == 8; // !!! undefined
assert c1.x.get().equals(8L);
assert !c1.x.equals(c2.x.get());
assert c1.x.get().intValue() == 8;
Long n = c1.x.get();
assert n.equals(Long.valueOf(8));
assert n.equals(c1.x.get());
assert n == Long.valueOf(8); // !!! undefined
assert n == c1.x.get(); // !!! undefined
assert n.intValue() == c1.x.get().intValue();
c1.x.increment(1);
assert n.intValue() != c1.x.get().intValue();
assert n.intValue() != r1.get().intValue();
assert !c1.x.isNull();
c1.x.set(null);
assert c1.x != null;
assert c1.x.isNull();
assert r1.isNull();
assert r1.get() == null;
assert r1.isValid();
assert !r1.isManaged();
}
// Exactly as unmanaged except that changing the RI's value requires a transaction.
public static void managed() {
Realm realm = Realm.getDefaultInstance();
realm.beginTransaction();
C c1 = realm.createObject(C.class);
C c2 = realm.createObject(C.class);
c1.x.set(7); // !!! NPE possible
c2.x.set(7); // !!! NPE possible
realm.commitTransaction();
c1.x.increment(1); // !!! throws IllegalStateException
RealmInteger r1 = c1.x;
r1.increment(1); // !!! throws IllegalStateException
realm.beginTransaction();
r1.increment(1);
realm.commitTransaction();
assert r1.isValid() == c1.isValid();
assert r1.isManaged();
}
public static void mixed() {
Realm realm = Realm.getDefaultInstance();
realm.beginTransaction();
C c1 = realm.createObject(C.class);
c1.x.set(7); // !!! NPE possible
realm.commitTransaction();
C c2 = new C();
c2.x.set(7); // !!! NPE possible
assert c2.x.equals(c1.x);
assert c2.x.get().equals(c1.x.get());
assert !c2.x.equals(c1.x.get());
RealmInteger r2 = c1.x;
C c3 = realm.copyFromRealm(c1);
assert r2 != c3.x;
assert r2.get().equals(c3.x.get());
assert r2.get().longValue() == c3.x.get().longValue();
r2 = c2.x;
realm.beginTransaction();
c3 = realm.copyToRealm(c12);
realm.commitTransaction();
assert c1.x != c3.x;
assert c1.x.get().equals(c3.x.get());
assert r2.get().longValue() == c3.x.get().longValue();
}
Are there any cases you care about that are not addressed here?
Looks fine for the most parts. I have a few concerns/comments about null handling:
I don't understand:
C c1 = new C();
c1.x.set(7); // !!! NPE possible
c2.x.set(Long.valueOf(7)); // !!! NPE possible
Unless you mean it is because people did
public class C extends RealmObject {
public final MutableRealmInteger x = null; // Allowed, but would be weird.
}
I'm not sure I agree with this:
C c1 = realm.createObject(C.class);
C c2 = realm.createObject(C.class);
c1.x.set(7); // !!! NPE possible
c2.x.set(7); // !!! NPE possible
This would mean that if you set the value to null
, you can never change it again. IMO the managed object should always return a RealmInteger
.
realm.beginTransaction();
C c1 = realm.createObject(C.class);
c.x.set(null); // Should always work
c.x.set(42); // Should always work
Also we need to consider how the @Required
annotation should work. Right now you can e.g. do @Required String value = ""
, which will cause Realm to throw an exception if you ever try to assign null
to value
. We should IMO do the same for MutableRealmInteger
, so:
public class C extends RealmObject {
@Required
public final MutableRealmInteger val = MutableRealmInteger.valueOf(null); // Not allowed, should throw when calling `realm.createObject()`. Doubt we can catch it before.
}
public class C1 extends RealmObject {
@Required
public final MutableRealmInteger val = MutableRealmInteger.valueOf(0);
}
realm.beginTransaction();
realm.createObject(C1.class).val.set(null); // Throw IllegalArgumentException
realm.commitTransaction();
Exactly: We will not prevent:
public final MutableRealmInteger x = null; // Allowed, but would be weird.
In fact, we require that, for @LinkingObjects
, so it might happen.
You say:
This would mean that if you set the value to null, you can never change it again. IMO the managed object should always return a RealmInteger.
... but you can't set x
. It is final
. I take your point, though, that the managed object should always have a non-null counter. That totally seems correct.
I also think that you are right about @Required
. If normal RealmInteger
s are nullable, @Required
ones should not be.
Making it so.
I think the problem with the magic trick of making RealmInteger = null
would be the inability to provide an initial value.
This is @cmelchior's original heading for this Issue. It is now quite out of date. I do not want to lose either it or the rest of this history of this issue, so I am moving the original comment here and revising the head of the ticket to reflect current reality:
==================
Core supports CRDT Counter functionality, which is very useful for Sync.
As discussed here: https://github.com/realm/realm-object-store/issues/357
Conclusion so far:
It is just a wrapper type around Cores INTEGER that also expose any specialized operations there. Currently only "add_int()", but we might allow exposing the int as a bit pattern in the future as well.
Pr. the discussion in the Object Store, the plan is that it should be possible to use RealmInteger
interchangeably with standard integers, i.e. changing a field from Integer
to RealmInteger
should _not_ trigger a migration.
Possible implementation:
// RealmInteger or RealmInt?
public class RealmInteger extends Number implements Comparable {
// Constructors
public RealmCounter(String val);
public RealmCounter(long val);
// Possible others?
// Realm specific methods
public void increment(long val); // add() instead?
public void decrement(long val); // Need this? or just use increment(-val)?
public void set(long val);
// Standard methods from Integer
// Which ones to include here? Probably just all of them
// https://docs.oracle.com/javase/7/docs/api/java/lang/Integer.html
// Convert to java primitive types. From Number class
public int intValue();
public long longValue();
public short shortValue();
public float floatValue();
public double doubleValue();
// Comparable
public int compareTo(RealmInteger other);
String toString();
int hashCode();
boolean equals(Object o);
}
Since it will be a wrapper class I would be inclined to treat it as implicit nullable just like an Integer
.
TODO:
Support RealmList
Isn't RealmInteger just a fancy int? I would think RealmList<RealmInteger>
is unsupported because it's a primitive.
@Zhuinden : https://github.com/realm/realm-java/issues/575
@bmeike wooooooooooooooooooooow!!!!!!!
heh. I'll take that as approval. ;-P
Filed #5024 for Dynamic Realm support
Filed #5025 for RealmList support
Closing. #5024 and #5025 track remaining work
Most helpful comment
@Zhuinden : https://github.com/realm/realm-java/issues/575