36

Is it possible to use an Enum type as an embedded field in an Entity class with the new Android Architecture Components and Room Persistence Library?

My Entity (with embedded Enum):

@Entity(tableName = "tasks")
public class Task extends SyncEntity {

    @PrimaryKey(autoGenerate = true)
    String taskId;

    String title;

    /** Status of the given task.
     * Enumerated Values: 0 (Active), 1 (Inactive), 2 (Completed)
     */
    @Embedded
    Status status;

    @TypeConverters(DateConverter.class)
    Date startDate;

    @TypeConverters(StatusConverter.class)
    public enum Status {
        ACTIVE(0),
        INACTIVE(1),
        COMPLETED(2);

        private int code;

        Status(int code) {
            this.code = code;
        }

        public int getCode() {
            return code;
        }
    }
}

My TypeConverter:

public class StatusConverter {

    @TypeConverter
    public static Task.Status toStatus(int status) {
        if (status == ACTIVE.getCode()) {
            return ACTIVE;
        } else if (status == INACTIVE.getCode()) {
            return INACTIVE;
        } else if (status == COMPLETED.getCode()) {
            return COMPLETED;
        } else {
            throw new IllegalArgumentException("Could not recognize status");
        }
    }

    @TypeConverter
    public static Integer toInteger(Task.Status status) {
        return status.getCode();
    }
}

When I compile this I get an error saying Error:(52, 12) error: Entities and Pojos must have a usable public constructor. You can have an empty constructor or a constructor whose parameters match the fields (by name and type).

Update 1 My SyncEntity class:

/**
 * Base class for all Room entities that are synchronized.
 */
@Entity
public class SyncEntity {

    @ColumnInfo(name = "created_at")
    Long createdAt;

    @ColumnInfo(name = "updated_at")
    Long updatedAt;
}
Bohsen
  • 4,242
  • 4
  • 32
  • 58
  • I think that you either need to make your fields `public`, offer `public` setters, or offer a `public` constructor that matches your `@Query` columns. Otherwise, Room cannot provide you with data. Your only constructor is the zero-argument one. – CommonsWare Jun 12 '17 at 12:11
  • Getters and Setters does not solve the problem and an Enum doesn't allow a public constructor. I thought the TypeConverter would do the trick and convert the Enum to an int, but it doesn't. Probably Room just isn't yet mature enough for this kind of use. Should probably make a feature request. – Bohsen Jun 12 '17 at 21:32

2 Answers2

53

I can use enum values at Room with TypeConverters. There are some parts to change at your code:

1) You must declare your Entity's fields public or they must have public getters/setters. Or you'll get below error:

yourField is not public in YourEntity; cannot be accessed from outside package

2) You don't need the @Embedded annotation for your status field. It is for nested objects. More from docs.

3) You didn't use the @TypeConverters annotation at the correct place. In your case it can be set above the status field. More from docs.

4) You must define a constructor for your Entity or you'll get below error:

Entities and Pojos must have a usable public constructor. You can have an empty constructor or a constructor whose parameters match the fields (by name and type).

You can define an empty constructor to skip this error.

5) Use int instead of Integer in your TypeConverter.

Sum; below works as expected:

@Entity(tableName = "tasks")
public class Task extends SyncEntity {

    @PrimaryKey(autoGenerate = true)
    public String taskId;

    public String title;

    /** Status of the given task.
     * Enumerated Values: 0 (Active), 1 (Inactive), 2 (Completed)
     */
    @TypeConverters(StatusConverter.class)
    public Status status;

    @TypeConverters(DateConverter.class)
    public Date startDate;

    // empty constructor 
    public Task() {
    }

    public enum Status {
        ACTIVE(0),
        INACTIVE(1),
        COMPLETED(2);

        private int code;

        Status(int code) {
            this.code = code;
        }

        public int getCode() {
            return code;
        }
    }
}
Devrim
  • 15,345
  • 4
  • 66
  • 74
10

I encountered a similar problem. Devrim's answer to the subject was very helpful. However my entity have nullable one.

In this case, the insert operation returns an error. This is caused by the primitive "code" member in the enum type.

I have developed the following implemantation to resolve this error.

First I fixed the enum type as follows.

package com.example.models.entities.enums;


import androidx.room.TypeConverter;

public enum Status {
    ACTIVE(0),
    INACTIVE(1),
    COMPLETED(2);

    private final Integer code;

    Status(Integer value) {
        this.code = value;
    }
    public Integer getCode() {
        return code;
    }

    @TypeConverter
    public static Status getStatus(Integer numeral){
        for(Status ds : values()){
            if(ds.code == numeral){
                return ds;
            }
        }
        return null;
    }

    @TypeConverter
    public static Integer getStatusInt(Status status){

        if(status != null)
            return status.code;

        return  null;
    }

}

Then I updated the entity as follows.

package com.example.models.entities;


import com.example.models.TypeConverters.DateConverter;
import com.example.models.entities.enums.Status;

import java.util.Date;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import androidx.room.TypeConverters;

@Entity(tableName = "tasks")
@TypeConverters({Status.class, DateConverter.class})

public class Task extends SyncEntity {

    @PrimaryKey(autoGenerate = true)
    String taskId;

    @NonNull
    String title;

    /** Status of the given task.
     * Enumerated Values: 0 (Active), 1 (Inactive), 2 (Completed)
     */
    @Nullable
    Status status;

    @NonNull
    Date startDate;


    public String getTaskId() {
        return taskId;
    }

    public void setTaskId(String taskId) {
        this.taskId = taskId;
    }

    @NonNull
    public String getTitle() {
        return title;
    }

    public void setTitle(@NonNull String title) {
        this.title = title;
    }

    @Nullable
    public Status getStatus() {
        return status;
    }

    public void setStatus(@Nullable Status status) {
        this.status = status;
    }

    @NonNull
    public Date getStartDate() {
        return startDate;
    }

    public void setStartDate(@NonNull Date startDate) {
        this.startDate = startDate;
    }
}

So, I could manage nullable enum property in Room database.

I hope I've been able to help with a similar situation.

Happy coding.

Hasan BINBOGA
  • 794
  • 9
  • 19