Android room type converter kotlin

tinmegali / AppDatabse.kt

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

@Database(entities = arrayOf( Note :: class , User :: class ), version = 1 )
@TypeConverters( Converters :: class )
abstract class AppDatabse : RoomDatabase ()
abstract fun userDAO (): UserDAO
abstract fun noteDAO (): NoteDAO
>

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

package com.tinmegali.daggerwithkotlin.room
import android.arch.persistence.room.TypeConverter
import java.util.Date
class Converters
@TypeConverter
fun fromTimestamp ( value : Long? ): Date ?
return if (value == null ) null else Date (value)
>
@TypeConverter
fun dateToTimestamp ( date : Date ? ): Long?
return date?.time
>
>

Источник

Converting types with Room and Kotlin

I’ve been working on a personal project, trying to get to grips with the various Android Architecture Components and Kotlin. One of the things I came up with was the requirement to deal with type conversion when using a SQLite database and the Room persistence library. Room is a nice abstraction to the internal SQLite database that converts models to tables within SQLite. It’s nice because it works alongside LiveData and RxJava to provide observable objects — when the database changes, the observable changes as well.

Write a Room data access layer the normal way

Let me explain my problem with type conversion with an example. I’ve got a nice model:

import android.arch.persistence.room.ColumnInfo import android.arch.persistence.room.Entity import android.arch.persistence.room.PrimaryKey import java.time.Instant import java.util.* /** * Definition of an Album. */ @Entity(tableName = "albums") data class Album( @PrimaryKey @ColumnInfo(name = "id") var id: String = UUID.randomUUID().toString(), @ColumnInfo(name = "created") var created: Instant = Instant.now(), @ColumnInfo(name = "modified") var modified: Instant = Instant.now(), @ColumnInfo(name = "deleted") var deleted: Boolean = false, @ColumnInfo(name = "album_name") var name: String = "New Album", @ColumnInfo(name = "hidden") var hidden: Boolean = false, @ColumnInfo(name = "pinned") var pinned: Boolean = false )

This is a fairly simple model for the Room persistence layer. However, I’m using the (relatively) new java.time package that is available in Java 1.8. Specifically, the Instant type (which represents a time zone agnostic moment in time) is not recognized by SQLite or Room.

Continuing on, I have a DAO:

import android.arch.paging.DataSource import android.arch.persistence.room.* @Dao interface AlbumDao  @Insert(onConflict = OnConflictStrategy.REPLACE) fun addAlbum(album: Album) @Update fun updateAlbum(album: Album) @Delete fun reallyDeleteAlbum(album: Album) @Query("SELECT * FROM albums WHERE NOT(deleted) AND NOT(hidden) ORDER BY pinned,album_name") fun listAlbumsByName(): DataSource.FactoryInt, Album> @Query("SELECT * FROM albums WHERE >) fun getAlbumById(id: String): Album >

This DAO does the normal CRUD operations. I have a funky custom select statement for dealing with the ordering of the albums. I want “pinned” albums to appear first and then in alphabetical order. I’m also using a data source as a return value here so I can deal with the paging adapter. Finally, here is my app database class:

import android.arch.persistence.room.Database import android.arch.persistence.room.Room import android.arch.persistence.room.RoomDatabase import android.content.Context @Database(entities = [ Album::class ], version = 1, exportSchema = false) abstract class AppDatabase : RoomDatabase()  abstract fun albumDao(): AlbumDao companion object  @Volatile private var INSTANCE: AppDatabase? = null fun getInstance(context: Context): AppDatabase = INSTANCE ?: synchronized(this)  INSTANCE ?: buildDatabase(context).also  INSTANCE = it > > private fun buildDatabase(context: Context) = Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app.db").build() > >

Again, this is a fairly normal — even boilerplate — implementation of the database class. It deals with building the database and is a synchronized singleton. I took this code directly from the Google sample.

Add Converters

Compiling this, I get the following errors:

error: Cannot figure out how to save this field into database. You can consider adding a type converter for it. private final java.time.Instant created = null; error: Cannot figure out how to save this field into database. You can consider adding a type converter for it. private java.time.Instant modified; 

This is not unexpected. As I mentioned earlier, SQLite doesn’t understand the Instant type, so it needs to be converted before being stored. Fortunately, the Room persistence library has provided a mechanism for this. First, create a class with a to/from pair of type converters:

import android.arch.persistence.room.TypeConverter import java.time.Instant class Converters  companion object  @TypeConverter @JvmStatic fun fromInstant(value: Instant): Long  return value.toEpochMilli() > @TypeConverter @JvmStatic fun toInstant(value: Long): Instant  return Instant.ofEpochMilli(value) > > >

The important thing here is that they are annotated with both the @TypeConverter and @JvmStatic annotations. This comes from a peculiarity of Kotlin. When you place something in the companion object, it doesn’t appear as a normal static method. You can’t call Converters.fromInstant() from within a Java class. Instead, you have to call Converters.Companion().fromInstant() . The Companion here is the companion object. If, however, you annotate the method with @JvmStatic it will get the appropriate treatment to be a true static method of the Converters class.

Now that I have a set of type converters, I can add it to the application database class:

import android.arch.persistence.room.Database import android.arch.persistence.room.Room import android.arch.persistence.room.RoomDatabase import android.arch.persistence.room.TypeConverters import android.content.Context @Database(entities = [ Album::class ], version = 1, exportSchema = false) @TypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase()  abstract fun albumDao(): AlbumDao companion object  @Volatile private var INSTANCE: AppDatabase? = null fun getInstance(context: Context): AppDatabase = INSTANCE ?: synchronized(this)  INSTANCE ?: buildDatabase(context).also  INSTANCE = it > > private fun buildDatabase(context: Context) = Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app.db").build() > >

The important line here is line 8 — the @TypeConverters annotation. You can put as many converters as you want. Just include a to/from pair for the custom type.

Fix the model class

There is another warning that creeps up:

warning: There are multiple good constructors and Room will pick the no-arg constructor. You can use the @Ignore annotation to eliminate unwanted constructors. 

If you have done any Room development with Kotlin, the likelihood is that you have run into this. This is because the de-facto advice is to use a data class as the model, such as I have done above. You can easily get rid of this warning by switching to a normal class. This is my converted class:

import android.arch.persistence.room.ColumnInfo import android.arch.persistence.room.Entity import android.arch.persistence.room.PrimaryKey import java.time.Instant import java.util.* @Entity(tableName = "albums") class Album  @PrimaryKey @ColumnInfo(name = "id") var id: String = UUID.randomUUID().toString() @ColumnInfo(name = "created") var created: Instant = Instant.now() @ColumnInfo(name = "modified") var modified: Instant = Instant.now() @ColumnInfo(name = "deleted") var deleted: Boolean = false @ColumnInfo(name = "album_name") var name: String = "New Album" @ColumnInfo(name = "hidden") var hidden: Boolean = false @ColumnInfo(name = "pinned") var pinned: Boolean = false override fun equals(other: Any?): Boolean  if (other == null) return false // null check if (javaClass != other.javaClass) return false // type check val mOther = other as Album return id == mOther.id && created == mOther.created && modified == mOther.modified && deleted == mOther.deleted && name == mOther.name && hidden == mOther.hidden && pinned == mOther.pinned > override fun hashCode(): Int  return Objects.hash(id, created, modified, deleted, name, hidden, pinned) > >

Two of the things that the data class provides are the equals() and hashCode() methods. Since I am switching to a non-data class, I now need to provide those. This is actually not really a problem for me because I am going to be doing a RecyclerView with a PagedListAdapter . The PagedListAdapter requires me to provide a Diffutil.ItemCallback to compare two objects in the list. The best place to compare two objects is within the model class itself, so I end up extending the data class for this purpose.

Now my code compiles without warnings and I have bi-directional type conversion when storing data in SQLite. I can move on to my UI.

Updated: August 08, 2018

Share on

Leave a comment

You may also enjoy

Deleting Azure resources the right way.

July 12, 2023 3 minute read

If you are an Azure developer, you likely spin up an application, do some work, then shut it down again. However, shutting down resources and deleting them has an order to them. If your service is network isolated (in a virtual network), then you can’t delete the application until the private endpoints are shut down. Budgets don’t get deleted with the resource group. Diagnostic settings can’t be deleted if the resource no longer exists. There is an order you should do things:

Purge Azure API Management soft-deleted services with ease.

January 20, 2023 4 minute read

I work a lot with Azure API Management, which means I turn up and down services quite a few times a day. Azure API Management has an awesome feature that prevents you from accidentally deleting a service, called soft-delete. Instead of immediately deleting the service, it marks it as soft deleted and purges it later on. Unfortunately, that means that you can’t immediately reuse that service name. In production, this is a great thing to have. In development, it turns into a pain. That’s b.

Type-checking Bicep arrays and objects

November 28, 2022 3 minute read

As you may have guessed by now, I’m delving heavily into the world of Bicep right now, mostly in order to describe the infrastructure for my personal projects in a readable way. JSON and YAML (used by ARM) is most definitely not readable for the average consumer. Part of that work was learning about bicep modules, which I love for modularizing my code. However, there is one distinctive problem with this.

Bicep, loops, and defaults

November 21, 2022 3 minute read

I’ve been playing around a lot with bicep recently. I like it because it is much more readable than ARM templates and lets me modularize my deployments easily. Recently, I was writing a module for creating named values in Azure API Management. Here is my service.bicep:

Источник

Читайте также:  Margin right inline css
Оцените статью