27

I'm trying to get data from Realm using an ID as a reference. However, when querying for an ID, I've found that Realm is giving me the same ID for all elements (ID of 0). Why doesn't the ID auto-increment when I use the @PrimaryKey annotation on the model's ID?

Here's the shortened class for the model:

public class Child_pages extends RealmObject {
    @PrimaryKey
    private int id_cp;

    private int id;
    private String day;
    private int category_id;

And the query I'm executing is: realm.where(Child_pages.class).equalTo("id_cp",page_id).findFirst()

amargherio
  • 148
  • 1
  • 7
Bachlet Tansime
  • 1,273
  • 3
  • 12
  • 17
  • Could you please increase the readability of your post? What do you want to achieve and what's going wrong? – Mast May 04 '15 at 10:53
  • It's clear that we get data using query specially ID of element, so my question is how I can get my objects from data base using ID and realm gives all the ID, the same value =0, why isn't auto Increment ! – Bachlet Tansime May 04 '15 at 10:57

8 Answers8

66

Realm currently doesn't support auto incrementing primary keys. However you can easily implement it yourself using something like:

public int getNextKey() { 
    try { 
         Number number = realm.where(object).max("id");
         if (number != null) {
             return number.intValue() + 1;
         } else {
             return 0;
         }
    } catch (ArrayIndexOutOfBoundsException e) { 
         return 0;
    }
}

I hope that can get you started.

krossovochkin
  • 12,030
  • 7
  • 31
  • 54
Bachlet Tansime
  • 1,273
  • 3
  • 12
  • 17
  • 4
    As `maximumInt` method has been deprecated, now we need to use `.max("id").intValue()` instead. – atabek Nov 15 '15 at 04:15
  • 9
    we need to add an try and catch for ArrayIndexOutOfBoundsException when the object is not created in the database. try { return realm.where(object).max("id").intValue() + 1; } catch (ArrayIndexOutOfBoundsException e) { return 0; } – beni Jan 28 '16 at 12:10
  • 4
    If there are no objects of that type in the db, `max("id")` returns `null`. So you have to check that before calling `intValue()`, and return the first id if it is `null`. – David Miguel Nov 24 '16 at 12:21
  • Can someone explain to me the reason for this? Why is this thing not included out of the box? – Neon Warge Dec 13 '16 at 09:32
  • 1
    @NeonWarge because autoincremented primary key doesn't work in distributed environment which is the Realm Sync – EpicPandaForce Jul 14 '17 at 14:28
10

The Java binding does not support primary keys yet, but it's on the roadmap and with high priority - see: https://groups.google.com/forum/#!topic/realm-java/6hFqdyoH67w . As a workaround you can use this piece of code for generating keys:

int key;
try {
  key = realm.where(Child_pages.class).max("id").intValue() + 1;
} catch(ArrayIndexOutOfBoundsException ex) {
 key = 0;
}

I use singleton factory for generating primary keys as a more generic solution with better performance (no need to query for max("id") every time thanks to AtomicInteger).

There is a long discussion in Realm Git Hub if you need more context: Document how to set an auto increment id?

zacheusz
  • 8,750
  • 3
  • 36
  • 60
4

As already mentioned, auto-increment isn't supported yet.

But for those who use kotlin and wants to have an auto-increment behavior with realm, this is one of the possibilities:

open class Route(
    @PrimaryKey open var id: Long? = null,
    open var total: Double? = null,
    open var durationText: String? = null,
    open var durationMinutes: Double? = null,
    open var distanceText: String? = null,
    open var distanceMeters: Int? = null): RealmObject() {

companion object {
    @Ignore var cachedNextId:Long? = null
        get() {
            val nextId =    if (field!=null) field?.plus(1)
                            else Realm.getDefaultInstance()?.where(Route::class.java)?.max("id")?.toLong()?.plus(1) ?: 1
            Route.cachedNextId = nextId
            return nextId
        }
}}
Vinicius Lima
  • 1,601
  • 1
  • 12
  • 15
1

Here is a genereic solution, personnaly I create a class named RealmUtils and I add this type of methods :

 public static int getPrimaryKey(Class c)
{
    Realm realm = Realm.getDefaultInstance();

    String primaryKeyFied = realm.getSchema().get(c.getSimpleName()).getPrimaryKey();
    if (realm.where(c).max(primaryKeyFied)== null)
        return 1;
    int value = realm.where(c).max(primaryKeyFied).intValue();
    return value+1;
}

Why I return 0 when the table is empty ? Because I hate Id's with 0 as value, so just change it. Hope it help someone.

Mabrouki Fakhri
  • 229
  • 1
  • 7
0

if your Id is Long then:

val nextId = bgRealm.where(Branch::class.java).max("localId") as Long + 1
LEMUEL ADANE
  • 8,336
  • 16
  • 58
  • 72
0

Sadly, I can't comment on other answers, but I'd like to add to Vinicius` answer.

First, you shouldn't open a new realm instance, when looking for primary key, especially when you don't close them, as this adds to maximum possible file descriptor count.

Secondly, although this is a preference, you shouldn't have primitive types (or object equivalents) as nullables (in Kotlin), as this adds redundant requirement to check nullability.

Third, since you're using kotlin, you can just define an extension method to Realm class, such as:

fun Realm.getNextId(model: RealmModel, idField : String) : Long
{
    val count = realm.where(model::class.java).max(idField)
    return count + 1L
}

Since all RealmObject instances are RealmModel, and even objects that aren't tracked by Realm are still RealmModel instances, therefore it will be available where ever you use your realm related classes. Java equivalent would be:

static long getNextId(RealmModel object, Realm realm, String idField)
{
    long count = realm.where(object.class).max(idField);
    return count + 1;
}

Late edit note: You're better off not using this approach if you're dealing with data that might come from outside sources, such as other databases or the web, because that WILL result in collisions between data in your internal database. Instead you should use max([id field name]). See the previous snippets, I already modified them to accommodate for this edit.

Community
  • 1
  • 1
Dragas
  • 1,140
  • 13
  • 29
0
//generates primary key
    public static int getNextKey(RealmQuery realmQuery, String fieldName) {
        try {
            Number number = realmQuery.max(fieldName);
            if (number != null) {
                return number.intValue() + 1;
            } else {
                return 1;
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            return 1;
        }
    }

Example

int id = RealmDbHelper.getNextKey(realm.where(RealmDocument.class), RealmDocument.FIELD_DOCUMENT_ID)
    realmObject.setId(id);
    realm.insert(realmObject);
Pavel Poley
  • 5,307
  • 4
  • 35
  • 66
-1

// EDIT

I found this new solution on this problem

@PrimaryKey
private Integer _id = RealmAutoIncrement.getInstance().getNextIdFromDomain(AccountType.class);

// Original Answer

I just wanted to share my attempt on solving this Problem, because i don't want to pass a primary Key value all the time. First i created a Database-Class to handle the storage of a RealmObject.

public class RealmDatabase {
Realm realm;

public RealmDatabase() {
    RealmConfiguration realmConfiguration =  new RealmConfiguration.Builder()
            .name("test").schemaVersion(1).build();

    try {
        realm = Realm.getInstance(realmConfiguration);
    } catch (Exception e) {
        Log.e("Realm init failed", e.getMessage());
    }
}

protected void saveObjectIntoDatabase(final RealmObject object) {
    realm.executeTransactionAsync(new Realm.Transaction() {
        @Override
        public void execute(Realm bgRealm) {
            bgRealm.copyToRealm(object);    
        }
    }, new Realm.Transaction.OnSuccess() {
        @Override
        public void onSuccess() {
            // onSuccess
        }
    }, new Realm.Transaction.OnError() {
        @Override
        public void onError(Throwable error) {
            // onError
        }
    });
}

After creating the database-class i created an Interface to distinguish between Objects with a Primary Key and without.

public interface AutoIncrementable {
    public void setPrimaryKey(int primaryKey);
    public int getNextPrimaryKey(Realm realm);
}

Now you will just need to edit this code of the previous execute method

if(object instanceof AutoIncrementable){
  AutoIncrementable autoIncrementable = (AutoIncrementable) object;
  autoIncrementable.setPrimaryKey(autoIncrementable.getNextPrimaryKey(bgRealm));
  bgRealm.copyToRealm((RealmObject)autoIncrementable);
} else {
  bgRealm.copyToRealm(object);
}

With this solution the database logic will still be in one class and this class can passed down to every class which needs to write into the database.

public class Person extends RealmObject implements AutoIncrementable{

    @PrimaryKey
    public int id;
    public String name;

    @Override
    public void setPrimaryKey(int primaryKey) {
        this.id = primaryKey;
    }

    @Override
    public int getNextPrimaryKey(Realm realm) {
        return realm.where(Person.class).max("id").intValue() + 1;
    }
}

For further suggestions feel free to leave a comment below.

kejith
  • 82
  • 9