有没有方法可以替代Android Room Entities构造函数?

时间:2019-07-19 12:45:51

标签: java android android-room

我正在尝试覆盖Android Room实体对象的创建。

从数据库实例化对象时,我想初始化其他属性。我关注了Android Room实体Documentation

我正在使用会议室版本:room-runtime:2.1.0

我试图在设置器和构造函数中记录消息,但是消息没有出现在LogCat中。

@Entity
// EDIT AFTER SOLUTION. You have to do this in the class that room is using to query the data, in my case it was a viewModel.
public class Client /* or ImportantInformationsClientViewModel */ {

    @SerializedName("azEMail")
    private String azEMail;

    @SerializedName("azFirstName")
    private String azFirstName;

    @SerializedName("azMobile")
    private String azMobile;

    @Ignore
    private MyMobileObject;

    public Client(String azEMail, String azFirstName, String azMobile) {
        Log.d("CLIENT_LOG", "Constructor is instanciated"); // Never logged
        this.azEMail = azEMail;
        this.azFirstName = azFirstName;
        this.azMobile = azMobile;
        // I would like to instantiate MyMobileObject each time this constructor (or the setter) is called
    }

    public String getAzEMail() {
        return azEMail;
    }

    public void setAzEMail(String azEMail) {
        Log.d("CLIENT_LOG", "Setter is called"); // Never Logged
        this.azEMail = azEMail;
    }

    public String getAzFirstName() {
        return azFirstName;
    }

    public void setAzFirstName(String azFirstName) {
        this.azFirstName = azFirstName;
    }

    public String getAzMobile() {
        return azMobile;
    }

    public void setAzMobile(String azMobile) {
        this.azMobile = azMobile;
        // I would like to instantiate MyMobileObject each time this setter (or the constructor) is called
    }

    public String getAzName() {
        return azName;
    }

    public void setAzName(String azName) {
        this.azName = azName;
    }

    public void setupObject() {
        // One ugly way to fix the problem is to call this method when my object is created. I want to avoid this. 
    }
}

我发现解决该问题的一种方法是在对象中创建一个setupObject方法,并在查询中找到对象后调用此方法。它确实可以工作,但是有点丑陋,而且它增加了更多的代码和复杂性。我正在努力避免这种情况。

是否可以添加将在android-room创建对象时调用的特定代码?例如在AzMobile设置器中吗?

房间如何实例化对象呢?属性是私有的,访问它的唯一方法是通过在LogCat中似乎未调用的设置器。

回答后编辑

有空间的棘手的事情是了解我们实体的实现方式。

我的实体是一个客户端对象,但是当我从数据库进行查询时,我正在使用ViewModel(类似于ImportantInformationsClientViewModel之类)进行查询。 我以为,由于Room仅了解客户实体,因此它将ViewModel包裹在实体中,然后从Entity神奇地构建了它(这不是那么愚蠢。对我来说这很有意义。。)

检查了我的android-room生成的DAO实现(ScheduleDao_Impl)后,我看到该房间实际上是在直接构建ViewModel对象。我只是在ViewModel中移动了属性和函数,一切正常。

如果我必须列出要知道的重要事项:

  • android-room仅使用@Entity来构建SQLite数据库中的对象模型,而不使用@Entity来构建查询对象

  • android-room将在构建应用程序时生成YourDao_Impl.java对象,您可以使用CTRL + MAJ + F

  • 访问它
  • android-room将需要ctor或setter或两者(仅需要访问所有属性)

  • 花几个小时检查所有ApplicationDatabase_Impl文件,这将帮助您了解android-room的工作原理以及所有内容如何包装在一起。

2 个答案:

答案 0 :(得分:1)

更新(澄清后)

很奇怪您没有看到这些电话。由于通过检查生成的DAO,我可以看到ROOM在做什么。它通过SQLite查询,并使用游标在结果之间移动,从而调用CTOR。

DAO是在编译时生成的,因此,在构建项目后,如果您使用的是macOS(或Android Studio用于查找的快捷方式),请按“ ctrl-shift-F”或“ cmd”,然后尝试找到您的DAO的名称。您将看到YourDaoYourDao_Impl()->这是自动生成的。 :) 打开那个。

这是我的DAO实现之一的简化​​复制/粘贴:

“模型”为RealTimeData。道的方法是loadAll(),很明显它返回了List<RealTimeData>

这里是方法(删除了最不相关的内容):我在行中添加了注释。

  @Override
  public List<RealTimeData> loadAll() {
    /// PREPARE THE QUERY, SQL, AND CURSOR.
    final String _sql = "SELECT * FROM realtime_data";
    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
    final Cursor _cursor = __db.query(_statement);
    try {
      // OBTAIN THE COLUMN NAMES FROM THE TABLE DEFINITION
      final int _cursorIndexOfId = _cursor.getColumnIndexOrThrow("id");
      final int _cursorIndexOfJsonData = _cursor.getColumnIndexOrThrow("json_data");
      final int _cursorIndexOfIsSent = _cursor.getColumnIndexOrThrow("is_sent");
      final int _cursorIndexOfDeviceId = _cursor.getColumnIndexOrThrow("device_id");
      final int _cursorIndexOfDateCreated = _cursor.getColumnIndexOrThrow("date_created");

      // THIS WILL STORE THE RESULTS
      final List<RealTimeData> _result = new ArrayList<RealTimeData>(_cursor.getCount());

      // ITERATE IT, CREATE A "RealTimeData" AND POPULATE IT.
      while(_cursor.moveToNext()) {
        final RealTimeData _item;
        final String _tmpId;
        _tmpId = _cursor.getString(_cursorIndexOfId);
        final String _tmpJsonData;
        _tmpJsonData = _cursor.getString(_cursorIndexOfJsonData);
        final Date _tmpDateCreated;
        final Long _tmp;
        // SOME THINGS NEED EXTRA CHECKS, THIS IS A DATE FIELD, STORED AS "long", SO NULL MUST BE CHECKED OR THE DATE CONVERTER WOULD THROW NPE
        if (_cursor.isNull(_cursorIndexOfDateCreated)) {
          _tmp = null;
        } else {
          _tmp = _cursor.getLong(_cursorIndexOfDateCreated);
        }
        // IT'S A DATE, SO CALL THE DATE CONVERTER (supplied via the @TypeConverter() annotation)
        _tmpDateCreated = DateConverter.toDate(_tmp);


        // BAM: INVOKE THE CTOR
        _item = new RealTimeData(_tmpId,_tmpJsonData,_tmpDateCreated);

        // NOW USE SETTERS FOR THE "OTHERS" 
        final boolean _tmpIsSent;
        final int _tmp_1;
        _tmp_1 = _cursor.getInt(_cursorIndexOfIsSent);
        _tmpIsSent = _tmp_1 != 0;
        _item.setSent(_tmpIsSent);
        final String _tmpDeviceId;
        _tmpDeviceId = _cursor.getString(_cursorIndexOfDeviceId);
        _item.setDeviceId(_tmpDeviceId);

         // AND ADD IT TO THE RESULTS...
        _result.add(_item);
      }

      // YOU GET THIS ONE :p
      return _result;
    } finally {
      _cursor.close();
      _statement.release();
    }
  }

“本例中的模型”看起来完全像这样:

Entity(tableName = "realtime_data")
public class RealTimeData {

    @PrimaryKey
    @NonNull
    private String id;

    @ColumnInfo(name = "json_data")
    private String jsonData;

    @ColumnInfo(name = "is_sent")
    private boolean isSent;

    @ColumnInfo(name = "device_id")
    private String deviceId;

    @ColumnInfo(name = "date_created")
    private Date dateCreated;

    @Ignore
    RealTimeData(@NonNull final String jsonData, @NonNull final Date dateCreated) {
        id = UUID.randomUUID().toString();
        this.jsonData = jsonData;
        this.dateCreated = dateCreated;
    }

    RealTimeData(@Nonnull final String id, final String jsonData, final Date dateCreated) {
        this.id = id;
        this.jsonData = jsonData;
        this.dateCreated = dateCreated;
    }

    String getJsonData() {
        return jsonData;
    }

    @Nonnull
    public String getId() {
        return id;
    }

    public boolean isSent() {
        return isSent;
    }

    public String getDeviceId() {
        return deviceId;
    }

    public Date getDateCreated() {
        return dateCreated;
    }

    public void setSent(final boolean sent) {
        isSent = sent;
    }

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

所以您要说的是,当ROOM实例化此对象时,不会调用您的ctor吗?

更新结束

对于有价值的东西,您可以有其他构造函数(前提是它们不会遮盖空的公共对象和/或使用所有字段的对象)。添加@Ignore属性。

例如:(出于一致性考虑,我从哈迪克的回答中窃取了哈迪克的样本)

@Entity
 public class User {
   @PrimaryKey
   private final int uid;
   private String name;
   @ColumnInfo(name = "last_name")
   private String lastName;

   public User(int uid) {
       this.uid = uid;
   }
   public String getLastName() {
       return lastName;
   }
   public void setLastName(String lastName) {
       this.lastName = lastName;
   }

   @Ignore
   User(String firstName, String lastName) {
      this.lastName = lastname;
      this.name = firstName;
   }
 }

这将起作用,但是请记住,如果不使用“自动生成的”主键,则需要在Room接受其插入或类似操作之前将一个分配给该字段。

这是您要做什么吗?

答案 1 :(得分:0)

每个实体必须具有无参数构造函数,或者其参数匹配字段(基于类型和名称)的构造函数。构造函数不必将所有字段都作为参数来接收,但是如果没有将字段传递给构造函数,则它应该是公共的或具有公共的setter。如果有匹配的构造函数可用,Room将始终使用它。

例如

@Entity
 public class User {
   @PrimaryKey
   private final int uid;
   private String name;
   @ColumnInfo(name = "last_name")
   private String lastName;

   public User(int uid) {
       this.uid = uid;
   }
   public String getLastName() {
       return lastName;
   }
   public void setLastName(String lastName) {
       this.lastName = lastName;
   }
 }