Implementing a TypeConverter in the Open Event Android Project

In the Open Event Android project, we fetch events from the server. After fetching them from the server, we implement caching by inserting them into the Room Database. The problem with Room Database is that it can only have primitive fields in the model! You can’t have an object Person() directly inserted to the database. As an example, it can only take input of a integer or a String.

But it can be a case sometimes, when we want a Person() object to be stored. This is where a TypeConverter comes in. What it basically does is that, it takes an input of a non-primitive field, and then converts it into a required primitive one. Let’s just go through it.

In the OpenEvent Android project we need to fetch social links. Social links have a relationship with the event related. So we are actually fetching two models, one is the SocialLink and another one is EventId. The SocialLink model is present below:-

@Type(“social-link”)

@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class)

@Entity(foreignKeys = [(ForeignKey(entity = Event::class, parentColumns = [“id”], childColumns = [“event”], onDelete = CASCADE))])

data class SocialLink(

       @Id(IntegerIdHandler::class)

       @PrimaryKey

       val id: Int,

       val link: String,

       val name: String,

       @ColumnInfo(index = true)

       @Relationship(“event”)

       var event: EventId? = null

)

We can clearly observe that the model has primitive fields such as id, link and name. But now we have another field of the object EventId, which is not primitive. Lets observe the model of EventId:-

@Type(“event”)

data class EventId(

       @Id(LongIdHandler::class)

       val id: Long

)

This model has only one primitive field, that is id. So our motive is to insert the object EventId() into the RoomDatabase. This is where a TypeConverter comes in.

A TypeConverter must have methods which convert the object into a required primitive field, and another one which initialises a new object with the field. Let’s see our TypeConverter:-

class EventIdConverter {

   @TypeConverter

   fun fromEventId(eventId: EventId): Long{

       return eventId.id

   }

   @TypeConverter

   fun toEventId(id: Long): EventId{

       return EventId(id)

   }

}

@TypeConverter annotation belongs to Room. The function fromEventId() specifies that it returns the id of the object EventId. The function toEventId() specifies that it makes a new EventId object using the field id.

We see that we are just returning the id of the EventId object. Why did we need all of this then? We could have easily just used a long eventId in the SocialLink model instead of the object Eventid!

The reason is simple. If we clearly observe both the models, we can see that @Type annotations are used. The thing is, the JSON request which is captured, returns us a model where we have nested models. Let us observe the JSON.

   {

                “type”: “social-link”,

                “id”: “1”,

                “relationships”: {

                    “event”: {

                              “links”: {

                                       “self”: “/v1/social-links/1/relationships/event”,

                                       “related”: “/v1/social-links/1/event”

                              },

                              “data”: {

                                       “type”: “event”,

                                       “id”: “76”

                              }

                    }

                },

                “attributes”: {

                    “link”: “https://www.facebook.com/eventyay/?ref=br_rs”,

                    “name”: “Facebook Url”

                },

                “links”: {

                    “self”: “/v1/social-links/1”

                }

    },

We clearly observe that there are two models present with two different types. One is the SocialLink in attributes. The other one is EventId in data inside the event field. That one has a

type of “event”. Thus the schema is mapped differently and that is why we are using another object.

We have now made our TypeConverter, how do we implement it?

We just add the annotation @TypeConverters(…) on top of the database of the app.

@Database(entities = [Event::class, User::class, SocialLink::class], version = 1)

@TypeConverters(EventIdConverter::class)

abstract class OpenEventDatabase : RoomDatabase() {

   abstract fun eventDao(): EventDao

   abstract fun userDao(): UserDao

   abstract fun socialLinksDao(): SocialLinksDao

}

There we have it, we have successfully implemented a TypeConverter. After compilation, we observe the schema of the generated database. ( A json file )

“fields”: [

 {

   “fieldPath”: “id”,

   “columnName”: “id”,

   “affinity”: “INTEGER”,

   “notNull”: true

 },

 {

   “fieldPath”: “link”,

   “columnName”: “link”,

   “affinity”: “TEXT”,

   “notNull”: true

 },

 {

   “fieldPath”: “name”,

   “columnName”: “name”,

   “affinity”: “TEXT”,

   “notNull”: true

 },

 {

   “fieldPath”: “event”,

   “columnName”: “event”,

   “affinity”: “INTEGER”,

   “notNull”: false

 }

We observe that the field event has been created with the type of an integer! Thus, this is the process by which a TypeConverter is successfully implemented in the Open Even Android app.

References

Tags: GSoC18, FOSSASIA, Open Event, Android, TypeConverter