Setting up a Room database on Android with Kotlin

Setup the required dependencies. You’ll need the room-runtime and room-compiler.

Dependencies & Gradle plugin

// app/build.gradle

apply plugin: 'kotlin-kapt'

dependencies {
    def room_version = "2.2.3"
    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:$room_version"
}

Creating the database

// Book.kt

@Entity
data class Book(@PrimaryKey val id: UUID = UUID.randomUUID(),
                var title: String = "",
                var date: Date = Date(),
                var isComplete: Boolean = false)
// database/BookDatabase.kt

@Database(entities = [ Book::class ], version=1)
@TypeConverters(BookTypeConverters::class)
abstract class BookDatabase : RoomDatabase() {
    abstract fun bookDao(): BookDao
}

Type Converters

// database/BookTypeConverters.kt

class BookTypeConverters {
    @TypeConverter
    fun fromDate(date: Date?): Long? {
        return date?.time
    }

    @TypeConverter
    fun toDate(millisSinceEpoch: Long?): Date? {
        return millisSinceEpoch?.let {
            Date(it)
        }
    }

    @TypeConverter
    fun fromUUID(uuid: UUID?): String? {
        return uuid?.toString()
    }

    @TypeConverter
    fun toUUID(uuid: String?): UUID? {
        return UUID.fromString(uuid)
    }
}

DAO (Data Access Object)

// database/BookDao.kt

@Dao
interface BookDao {
    @Query("SELECT * FROM book")
    fun getBooks(): LiveData<List<Book>>

    @Query("SELECT * FROM book WHERE id=(:id)")
    fun getBook(id: UUID): LiveData<Book?>
}

Repository

// BookRepository.kt

private const val DATABASE_NAME = "book-database"

class BookRepository private constructor(context: Context) {

    private val database : BookDatabase = Room.databaseBuilder(
        context.applicationContext,
        BookDatabase::class.java,
        DATABASE_NAME
    ).build()

    private val bookDao = database.bookDao()

    fun getBooks(): LiveData<List<Book>> = bookDao.getBooks()

    fun getBook(id: UUID): LiveData<Book?> = bookDao.getBook(id)

    companion object {
        private var INSTANCE: BookRepository? = null

        fun initialize(context: Context) {
            if (INSTANCE == null) {
                INSTANCE = BookRepository(context)
            }
        }

        fun get(): BookRepository {
            return INSTANCE ?: throw IllegalStateException("BookRepository must be initialized")
        }
    }
}

Application Subclass

// BookshelfApplication.kt

class BookshelfApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        BookRepository.initialize(this)
    }
}

manifests/AndroidManifest.xml

<application
    android:name=".BookshelfApplication"
    android:allowBackup="true">
</application>

ViewModel

// BookListViewModel.kt

class BookListViewModel : ViewModel() {
    private val bookRepository = BookRepository.get()
    val bookListLiveData = bookRepository.getBooks()
}

List Fragment

// BookListFragment.kt

private const val TAG = "BookListFragment"

class BookListFragment : Fragment() {

    private lateinit var bookRecyclerView: RecyclerView
    private var adapter: BookAdapter? = BookAdapter(emptyList())

    private val bookListViewModel: BookListViewModel by lazy {
        ViewModelProvider(this).get(BookListViewModel::class.java)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_book_list, container, false)
        bookRecyclerView = view.findViewById(R.id.book_recycler_view) as RecyclerView
        bookRecyclerView.layoutManager = LinearLayoutManager(context)
        bookRecyclerView.adapter = adapter

        return view
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        bookListViewModel.bookListLiveData.observe(
            viewLifecycleOwner,
            Observer { books ->
                books?.let {
                    Log.i(TAG, "Got books ${books.size}")
                    updateUI(books)
                }
            }
        )
    }

    private fun updateUI(books: List<Book>) {
        adapter = BookAdapter(books)
        bookRecyclerView.adapter = adapter
    }

    private inner class BookHolder(view: View)
        : RecyclerView.ViewHolder(view), View.OnClickListener {

        private lateinit var book: Book

        private val titleTextView = itemView.findViewById<TextView>(R.id.book_title)
        private val dateTextView = itemView.findViewById<TextView>(R.id.book_date)
        private val completedImageView = itemView.findViewById<ImageView>(R.id.book_completed)

        init {
            itemView.setOnClickListener(this)
        }

        fun bind(book: Book) {
            this.book = book
            titleTextView.text = this.book.title

            dateTextView.text = android.text.format.DateFormat.format("EEEE, MMM d, yyyy", this.book.date)

            if (book.isCompleted) {
                completedImageView.visibility = View.VISIBLE
            } else {
                completedImageView.visibility = View.GONE
            }
        }

        override fun onClick(v: View?) {
            Toast.makeText(context, "${book.title} pressed", Toast.LENGTH_SHORT).show()
        }

    }

    private inner class BookAdapter(var books: List<Book>) : RecyclerView.Adapter<BookHolder>() {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookHolder {
            val view = layoutInflater.inflate(R.layout.list_item_book, parent, false)
            return BookHolder(view)
        }

        override fun getItemCount() = books.size

        override fun onBindViewHolder(holder: BookHolder, position: Int) {
            val book = books[position]
            holder.bind(book)
        }

    }

    companion object {
        fun newInstance() : BookListFragment {
            return BookListFragment()
        }
    }
}

Published by Jesús Sánchez

IT specialist at Quantum Valley.

Leave a comment

Your email address will not be published. Required fields are marked *