15

Problem

I have a POJO parsed from an API call which looks like this

public class Article {

  public Long id;

  @Expose
  @SerializedName("section")
  public String section;

  @Expose
  @SerializedName("title")
  public String title;

  @Expose
  @SerializedName("topics")
  public List<String> topics;

  @Expose
  @SerializedName("media")
  public List<Media> media;
}

For the sake of minimizing redundancy and duplicates, I am looking to create a schema like this

@Entity(foreignKeys = { 
          @ForeignKey(entity = Article.class, parentColumns = "id", childColumns = "articleId"), 
          @ForeignKey(entity = Topic.class, parentColumns = "id", childColumns = "topicId"),
          @ForeignKey(entity = Media.class, parentColumns = "id", childColumns = "mediaId")
}
public class Articles {

    @PrimaryKey
    public Long articleId; 

    @ColumnInfo(name = "topic_id")
    public Long topicId;

    @ColumnInfo(name = "media_id")
    public Long mediaId;
}

@Entity
public class Article {
    // Left out
}

@Entity
public class Media {
    // Left out
}

As you can see, when I call the DAO methods to access the database, I can not just pass in the pojo object directly (unless I am mistaken about this). I believe I need to transform the object to one which matches the database entity model.

Question

Does the Android Framework provide a natural way to convert from POJO to the database model object? Is there a way to do this other than manually converting it myself?

Things I've Tried

  • I know that I can implement the conversion inside a method within my DAO interface. But then I would have to create a new object and set all the values manually.
  • I initially thought typeconverters would work but they seem to convert individual columns.
Kevin.Lam
  • 289
  • 1
  • 4
  • 16

3 Answers3

9

All you have to do is use @Embedded annotation for your POJO(Model Class) which will refer to another class. then create a type converter class.

@Embedded(prefix = "media")
private Media media;

@TypeConverters({TypeConvertorClass.class})
@Database(entities = {Article .class,Media.class}, version = 1, exportSchema = false)
public abstract class `DataBaseExample` extends RoomDatabase {
}


public class Converters {
    @TypeConverter
    public static ArrayList<String> fromString(String value) {
        Type listType = new TypeToken<ArrayList<String>>() {}.getType();
        return new Gson().fromJson(value, listType);
    }
 
    @TypeConverter
    public static String fromArrayList(ArrayList<String> list) {
        Gson gson = new Gson();
        String json = gson.toJson(list);
        return json;
    }
}


public class TypeConvertorClass {
    @TypeConverter
    public static Media getMedia(String longId) {
        return longId== null ? null : new Meida();
    }
}

@Entity(tableName = "Article")
public class Article {
    @ColumnInfo (name = "article_id")
    public Long id;
    
    @Expose
    @SerializedName("section")
    public String section;

    @Expose
    @SerializedName("title")
    public String title;

    @Expose
    @SerializedName("topics")
    public List<String> topics;

    @Embedded(prefix = "media") // We need relation to Media table
    @Expose
    @SerializedName("media")
    public List<Media> media;
}

public class Media {
    @ColumnInfo (name = "media_id")
    public Long id;
}
Atif AbbAsi
  • 5,633
  • 7
  • 26
  • 47
  • I can see that typeconverters does simplify the process. But how does it handle the case where I need to query from list type objects like "topics". What if I wanted to get all articles with a certain topic? If I separated "topics" into its own entity, I believe I could do this with a select and join query. Does it still work if I use typeconverters? – Kevin.Lam Oct 03 '18 at 06:29
  • I don't think you can use a query in the `DAO` to annotate over a list that is held as a value in one of the columns. You would probably have to select each list out and for/while loop over the values to match. You CAN match to a specific object that uses the `TypeConverters` with some creative `@Query` statements, like: `@Query("Select article_entry Where media = :mediaItem) abstract void loadArticleByMediaItem(Media mediaItem);` – Mr.Drew Oct 03 '18 at 06:55
  • @Kevin.Lam if you want to query topic, all you can do is just pass topic to your method while querying and get your desired result . – Atif AbbAsi Oct 03 '18 at 07:14
  • 1
    in you Dao simply use query. @Query("SELECT * FROM topic where topic.topic LIKE :topic") List getSpecificTopicList(String topic); – Atif AbbAsi Oct 03 '18 at 07:17
  • 1
    I would agree to @AtifAbbAsi , that is why we are using `TypeConverters` in the first place, i haven't tried it yet but i think it will work for you – Abdul Kawee Oct 03 '18 at 07:23
  • @AtifAbbAsi I am confused on how @Query("SELECT * FROM topic where topic.topic LIKE :topic") would work in this case. If you mean querying inside the Article table, wouldn't the query not find a match? The typeconverter would just convert the String value back into List topic which "LIKE :topic" would not match properly. Also I am trying to get a List of Articles by topic so List
    getArticlesByTopic(String topic);
    – Kevin.Lam Oct 03 '18 at 07:30
  • 1
    Just read through another stackoverflow post. The query works because it searches through the converted topic string for similar topics. I will be using typeconverter. Thank you for the help! – Kevin.Lam Oct 03 '18 at 08:54
  • @Kevin.Lam "Just read through another stackoverflow post"? ANY? How could it help? You should be more specific about which one. – The incredible Jan May 31 '21 at 11:35
  • @The incredible Jan My mistake about not adding a link to the stackoverflow post. It's been some time, so I don't remember which post I was referring to. The idea behind it was that I previously was under the wrong assumption that the "LIKE :topic" query would be performed on the list of topics (List) rather than the String of topics. It works because it's done on the latter – Kevin.Lam Jun 01 '21 at 21:30
1

You can use @Embedded annotation for your related POJO which refers to another class.

You can do like this:

Article.java

@Entity(tableName = "Article")
public class Article {
    @ColumnInfo (name = "article_id")
    public Long id;

    @Expose
    @SerializedName("section")
    public String section;

    @Expose
    @SerializedName("title")
    public String title;

    @Expose
    @SerializedName("topics")
    public List<String> topics;

    @Embedded // We need relation to Media table
    @Expose
    @SerializedName("media")
    public List<Media> media;
}

Media.java

public class Media {
    @ColumnInfo (name = "media_id")
    public Long id;
}

So now on, you can directly use this POJO as Entity for ROOM.


Please note:

Though i'm not sure about how you'll handle that relation (because, Media obj is in list for Article class, you'll need to use type converter for that)

Reference from here

Jeel Vankhede
  • 11,592
  • 2
  • 28
  • 58
1

According to the documentation here "There is no limit on the number of Entity or Dao classes but they must be unique within the Database." So I think you can simply declare the different classes within your database class that extends RoomDatabase.

Have you tried simply declaring the different POJOs as different entities and including them all in the same database class?

For instance:

  // Article, Topic and Media are classes annotated with @Entity.
  @Database(version = 1, entities = {Article.class, Topic.class, Media.class})
  abstract class MyDatabase extends RoomDatabase {
  // ArticleDao is a class annotated with @Dao.
  abstract public ArticleDao articleDao();
  // TopicDao is a class annotated with @Dao.
  abstract public TopicDao topicDao();
  // MediaDao is a class annotated with @Dao.
  abstract public MediaDao mediaDao();
}

This may not exactly help with the redundancy, but my initial thought would be type converters as well. I've actually successfully even implemented a parcelable object as a column within my Room Database using TypeConverters and a single Dao.

Have you tried using Gson in your TypeConverter class? I believe this article addresses your question more directly. It's a guide to storing objects in a room database. Again, the trick is in the type converters and declaring your object as the type token for Gson. For instance:

public class Converters {
   @TypeConverter
   public static List<Media> fromStringToList(String mediaListString) {
      Type myType = new TypeToken<List<Media>>() {}.getType();
      return new Gson().fromJson(mediaListString, myType);
   }
   @TypeConverter
   public static String fromMediaListToString(List<Media> mediaItems) {
      if (mediaItems== null || mediaItems.size() == 0) {
        return (null);
      }
      Gson gson = new Gson();
      Type type = new TypeToken<List<VideoParcelable>>() {
      }.getType();
      String json = gson.toJson(mediaItems, type);
      return json;
   }
}

That addresses the things you've tried. Now on to your statement "I believe I need to transform the object to one which matches the database entity model." Actually, not necessarily. You can use the @Ignore annotation for different creation instances or implementations of your entity, so long as there is at least one default constructor that includes the primary key of the entry. In your case:

@Entity(foreignKeys = { 
      @ForeignKey(entity = Article.class, parentColumns = "id", childColumns = 
      "articleId"), 
      @ForeignKey(entity = Topic.class, parentColumns = "id", childColumns = 
      "topicId"),
      @ForeignKey(entity = Media.class, parentColumns = "id", childColumns = 
      "mediaId")
}

public class ArticlesEntry {

@PrimaryKey
public Long articleId; 
@ColumnInfo(name = "topic_id")
public Long topicId;
@ColumnInfo(name = "media_id")
public Long mediaId;

private Article articleObject;
private Media mediaObject;

//default constructor
public ArticlesEntry(int id) {
    this.articleId = id;
}

//You can call this anytime you add to the database with media object input
@Ignore
public ArticlesEntry(int id, Media inMedia) {
    this.articleId = id;
    this.mediaObject= inMedia;
}
//You can create many of these and insert as needed, the left out variables of the 
//are null, note that id has to be passed b/c your primary key isn't set to 
//autogenerate
@Ignore
public ArticlesEntry(int id, Article inArticle) {
    this.articleId = id;
    this.articleObject= articleObject;
}
//Or both objects:
@Ignore
public ArticlesEntry(int id, Media inMedia, Article inArticle) {
    this.articleId = id;
    this.mediaObject = inMedia;
    this.articleObject= articleObject;
}

//getters and setters here...

}

If you create your ArticlesEntry like above, you'll need to make and include the different TypeConverters, which can all be within the same class and imported to the specific DB with @TypeConverters(MyConverters.class). Hope this helps!

Mr.Drew
  • 939
  • 2
  • 9
  • 30