Android persistence library room example

dependencies

implementation 'android.arch.lifecycle:extensions:1.1.1'
implementation 'android.arch.persistence.room:runtime:1.1.1'
kapt 'android.arch.lifecycle:compiler:1.1.1'
kapt 'android.arch.persistence.room:compiler:1.1.1'

Creating a class that represents a table in the database, all you needed is to annotate the class with @Entity The following classes are creating 3 tables in the database when initialized, User, Book, Loan.

User.kt

import android.arch.persistence.room.Entity
import android.arch.persistence.room.PrimaryKey

@Entity
class User(@PrimaryKey var id: String = "",
           var name: String? = null,
           var lastName: String? = null,
           var age: Int = 0
)

Book.kt

import android.arch.persistence.room.Entity
import android.arch.persistence.room.PrimaryKey

@Entity
data class Book(
        @PrimaryKey var id: String = "",
        var title: String? = null
)

Loan.kt

import android.arch.persistence.room.ColumnInfo
import android.arch.persistence.room.Entity
import android.arch.persistence.room.ForeignKey
import android.arch.persistence.room.PrimaryKey
import android.arch.persistence.room.TypeConverters
import java.util.Date

@Entity(foreignKeys = arrayOf(ForeignKey(entity = Book::class, parentColumns = arrayOf("id"), childColumns = arrayOf("book_id")),
        ForeignKey(entity = User::class, parentColumns = arrayOf("id"), childColumns = arrayOf("user_id"))))
@TypeConverters(DateConverter::class)
class Loan(@PrimaryKey var id: String = "",
           var startTime: Date? = null,
           var endTime: Date? = null,
           @ColumnInfo(name = "book_id") var bookId: String? = null,
           @ColumnInfo(name = "user_id") var userId: String? = null

)

Sometimes we need store a complex object in the database table such as a Date object as a field in the database table. In this case, we will need a type converter class to tell the library room how to convert the date object. Here is an example of type converter for Date, the functions are annotated with @TypeConverter

import android.arch.persistence.room.TypeConverter
import java.util.Date

class DateConverter {
    @TypeConverter
    fun toDate(timestamp: Long?): Date? {
        return if (timestamp == null) null else Date(timestamp)
    }

    @TypeConverter
    fun toTimestamp(date: Date?): Long? {
        return date?.time
    }
}

To access the database, all we need is to create an interface class and annotate it with @Dao and @TypeConverters if there are any table field need a converter. @Query, Insert, @Delete, @Delete are some of the annotations used on the function signatures in the interface for CRUD operations against the database. The query statements are specified after the annotations. The query statements are verified by the compiler, which means Android Studio will not compile your project if there is any errors in your query statements defined in the Dao interface class.

UserDao.kt

import android.arch.persistence.room.Dao
import android.arch.persistence.room.Delete
import android.arch.persistence.room.Insert
import android.arch.persistence.room.Query
import android.arch.persistence.room.OnConflictStrategy.IGNORE

@Dao
interface UserDao {
    @Query("select * from user")
    fun loadAllUsers(): List

    @Query("select * from user where id = :id")
    fun loadUserById(id: Int): User

    @Query("select * from user where name = :firstName and lastName = :lastName")
    fun findUserByNameAndLastName(firstName: String, lastName: String): List

    @Insert(onConflict = IGNORE)
    fun insertUser(user: User)

    @Delete
    fun deleteUser(user: User)

    @Query("delete from user where name like :badName OR lastName like :badName")
    fun deleteUsersByName(badName: String): Int

    @Insert(onConflict = IGNORE)
    fun insertOrReplaceUsers(vararg users: User)

    @Delete
    fun deleteUsers(user1: User, user2: User)

    @Query("SELECT * FROM User WHERE :age == :age") // TODO: Fix this!
    fun findUsersYoungerThan(age: Int): List

    @Query("SELECT * FROM User WHERE age < :age")
    fun findUsersYoungerThanSolution(age: Int): List

    @Query("DELETE FROM User")
    fun deleteAll()
}

BookDao.kt

import android.arch.lifecycle.LiveData
import android.arch.persistence.room.Dao
import android.arch.persistence.room.Insert
import android.arch.persistence.room.Query
import android.arch.persistence.room.TypeConverters
import android.arch.persistence.room.Update
import java.util.Date
import android.arch.persistence.room.OnConflictStrategy.IGNORE
import android.arch.persistence.room.OnConflictStrategy.REPLACE

@Dao
@TypeConverters(DateConverter::class)
interface BookDao {

    @Query("select * from Book where id = :id")
    fun loadBookById(id: Int): Book

    @Query("SELECT * FROM Book " +
            "INNER JOIN Loan ON Loan.book_id = Book.id " +
            "INNER JOIN User on User.id = Loan.user_id " +
            "WHERE User.name LIKE :userName")
    fun findBooksBorrowedByName(userName: String): LiveData>

    @Query("SELECT * FROM Book " +
            "INNER JOIN Loan ON Loan.book_id = Book.id " +
            "INNER JOIN User on User.id = Loan.user_id " +
            "WHERE User.name LIKE :userName " +
            "AND Loan.endTime > :after ")
    fun findBooksBorrowedByNameAfter(userName: String, after: Date): LiveData>

    @Query("SELECT * FROM Book " +
            "INNER JOIN Loan ON Loan.book_id = Book.id " +
            "INNER JOIN User on User.id = Loan.user_id " +
            "WHERE User.name LIKE :userName")
    fun findBooksBorrowedByNameSync(userName: String): List

    @Query("SELECT * FROM Book " +
            "INNER JOIN Loan ON Loan.book_id LIKE Book.id " +
            "WHERE Loan.user_id LIKE :userId ")
    fun findBooksBorrowedByUser(userId: String): LiveData>

    @Query("SELECT * FROM Book " +
            "INNER JOIN Loan ON Loan.book_id LIKE Book.id " +
            "WHERE Loan.user_id LIKE :userId " +
            "AND Loan.endTime > :after ")
    fun findBooksBorrowedByUserAfter(userId: String, after: Date): LiveData>

    @Query("SELECT * FROM Book " +
            "INNER JOIN Loan ON Loan.book_id LIKE Book.id " +
            "WHERE Loan.user_id LIKE :userId ")
    fun findBooksBorrowedByUserSync(userId: String): List

    @Query("SELECT * FROM Book")
    fun findAllBooks(): LiveData>


    @Query("SELECT * FROM Book")
    fun findAllBooksSync(): List

    @Insert(onConflict = IGNORE)
    fun insertBook(book: Book)

    @Update(onConflict = REPLACE)
    fun updateBook(book: Book)

    @Query("DELETE FROM Book")
    fun deleteAll()
}

LoanDao.kt

import android.arch.lifecycle.LiveData
import android.arch.persistence.room.Dao
import android.arch.persistence.room.Insert
import android.arch.persistence.room.Query
import android.arch.persistence.room.TypeConverters
import java.util.Date

@Dao
@TypeConverters(DateConverter::class)
interface LoanDao {

    @Query("SELECT * From Loan")
    fun findAllLoans(): LiveData>

    @Query("SELECT Loan.id, Book.title, User.name, Loan.startTime, Loan.endTime From Loan " +
            "INNER JOIN Book ON Loan.book_id = Book.id " +
            "INNER JOIN User ON Loan.user_id = User.id ")
    fun findAllWithUserAndBook(): LiveData>

    @Query("SELECT Loan.id, Book.title as title, User.name as name, Loan.startTime, Loan.endTime " +
            "FROM Book " +
            "INNER JOIN Loan ON Loan.book_id = Book.id " +
            "INNER JOIN User on User.id = Loan.user_id " +
            "WHERE User.name LIKE :userName " +
            "AND Loan.endTime > :after ")
    fun findLoansByNameAfter(userName: String, after: Date): LiveData>

    @Insert
    fun insertLoan(loan: Loan)

    @Query("DELETE FROM Loan")
    fun deleteAll()
}

A class for query results. LoanWithUserAndBook.kt

data class LoanWithUserAndBook(
        var id: String,
        @ColumnInfo(name = "title") var bookTitle: String? = null,
        @ColumnInfo(name = "name") var userName: String? = null,
        @TypeConverters(DateConverter::class) var startTime: Date? = null,
        @TypeConverters(DateConverter::class) var endTime: Date? = null
)

The room database initialization. AppDatabase.kt extends from RoomDatabase.

import android.arch.persistence.room.Database
import android.arch.persistence.room.Room
import android.arch.persistence.room.RoomDatabase
import android.content.Context

@Database(entities = arrayOf(User::class, Book::class, Loan::class), version = 1)
abstract class AppDatabase : RoomDatabase() {

    abstract fun userModel(): UserDao

    abstract fun bookModel(): BookDao

    abstract fun loanModel(): LoanDao

    companion object {

        private var INSTANCE: AppDatabase? = null

        fun getInMemoryDatabase(context: Context): AppDatabase {
            if (INSTANCE == null) {
                INSTANCE = Room.inMemoryDatabaseBuilder(context.applicationContext, AppDatabase::class.java)
                        // To simplify the codelab, allow queries on the main thread.
                        // Don't do this on a real app! See PersistenceBasicSample for an example.
                        .allowMainThreadQueries()
                        .build()
            }
            return INSTANCE!!
        }

        fun destroyInstance() {
            INSTANCE = null
        }
    }
}

Initializing some data in the database. DatabaseInitializer.kt

public class DatabaseInitializer {

    // Simulate a blocking operation delaying each Loan insertion with a delay:
    private static final int DELAY_MILLIS = 500;

    public static void populateAsync(final AppDatabase db) {

        PopulateDbAsync task = new PopulateDbAsync(db);
        task.execute();
    }

    public static void populateSync(@NonNull final AppDatabase db) {
        populateWithTestData(db);
    }

    private static void addLoan(final AppDatabase db, final String id,
                                final User user, final Book book, Date from, Date to) {
        Loan loan = new Loan();
        loan.setId(id);
        loan.setBookId(book.getId());
        loan.setUserId(user.getId());
        loan.setStartTime(from);
        loan.setEndTime(to);
        db.loanModel().insertLoan(loan);
    }

    private static Book addBook(final AppDatabase db, final String id, final String title) {
        Book book = new Book();
        book.setId(id);
        book.setTitle(title);
        db.bookModel().insertBook(book);
        return book;
    }

    private static User addUser(final AppDatabase db, final String id, final String name,
                                final String lastName, final int age) {
        User user = new User();
        user.setId(id);
        user.setAge(age);
        user.setName(name);
        user.setLastName(lastName);
        db.userModel().insertUser(user);
        return user;
    }

    private static void populateWithTestData(AppDatabase db) {
        db.loanModel().deleteAll();
        db.userModel().deleteAll();
        db.bookModel().deleteAll();

        User user1 = addUser(db, "1", "Jason", "Seaver", 40);
        User user2 = addUser(db, "2", "Mike", "Seaver", 12);
        addUser(db, "3", "Carol", "Seaver", 15);

        Book book1 = addBook(db, "1", "Dune");
        Book book2 = addBook(db, "2", "1984");
        Book book3 = addBook(db, "3", "The War of the Worlds");
        Book book4 = addBook(db, "4", "Brave New World");
        addBook(db, "5", "Foundation");
        try {
            // Loans are added with a delay, to have time for the UI to react to changes.

            Date today = getTodayPlusDays(0);
            Date yesterday = getTodayPlusDays(-1);
            Date twoDaysAgo = getTodayPlusDays(-2);
            Date lastWeek = getTodayPlusDays(-7);
            Date twoWeeksAgo = getTodayPlusDays(-14);

            addLoan(db, "1", user1, book1, twoWeeksAgo, lastWeek);
            Thread.sleep(DELAY_MILLIS);
            addLoan(db, "2", user2, book1, lastWeek, yesterday);
            Thread.sleep(DELAY_MILLIS);
            addLoan(db, "3", user2, book2, lastWeek, today);
            Thread.sleep(DELAY_MILLIS);
            addLoan(db, "4", user2, book3, lastWeek, twoDaysAgo);
            Thread.sleep(DELAY_MILLIS);
            addLoan(db, "5", user2, book4, lastWeek, today);
            Log.d("DB", "Added loans");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static Date getTodayPlusDays(int daysAgo) {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.DATE, daysAgo);
        return calendar.getTime();
    }

    private static class PopulateDbAsync extends AsyncTask {

        private final AppDatabase mDb;

        PopulateDbAsync(AppDatabase db) {
            mDb = db;
        }

        @Override
        protected Void doInBackground(final Void... params) {
            populateWithTestData(mDb);
            return null;
        }

    }
}

Retrieving data from the database in a viewmodel. The view model retrieves the data from the database through the Dao interfaces and the retrieved data are posted to the live data object for activities to observe.

class CustomResultViewModel(application: Application) : AndroidViewModel(application) {

    var loansResult: LiveData? = null
        private set

    private var mDb: AppDatabase? = null

    private val yesterdayDate: Date
        get() {
            val calendar = Calendar.getInstance()
            calendar.add(Calendar.DATE, -1)
            return calendar.time
        }

    fun createDb() {
        mDb = AppDatabase.getInMemoryDatabase(getApplication())

        // Populate it with initial data
        DatabaseInitializer.populateAsync(mDb)

        // Receive changes
        subscribeToDbChanges()
    }

    private fun subscribeToDbChanges() {
        val loans = mDb!!.loanModel().findLoansByNameAfter("Mike", yesterdayDate)

        // Instead of exposing the list of Loans, we can apply a transformation and expose Strings.
        loansResult = Transformations.map(loans) { loansWithUserAndBook ->
            val sb = StringBuilder()
            val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US)

            for (loan in loansWithUserAndBook) {
                sb.append(String.format("%s\n  (Returned: %s)\n",
                        loan.bookTitle,
                        simpleDateFormat.format(loan.endTime))
                )
            }
            sb.toString()
        }
    }
}

Showing the data from the database in an activity. The activity is observing the live data from the view model, and populates the ui when it gets live data change events.

class MainActivity : AppCompatActivity() {
    private var mShowUserViewModel: CustomResultViewModel? = null

    private var mBooksTextView: TextView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_main)
        mBooksTextView = findViewById(R.id.books_tv)

        mShowUserViewModel = ViewModelProviders.of(this).get(CustomResultViewModel::class.java)

        populateDb()

        subscribeUiLoans()
    }

    private fun populateDb() {
        mShowUserViewModel!!.createDb()
    }

    private fun subscribeUiLoans() {
        mShowUserViewModel!!.loansResult?.observe(this, Observer { result -> mBooksTextView!!.text = result })
    }

    fun onRefreshBtClicked(view: View) {
        populateDb()
        subscribeUiLoans()
    }

}

The layout file for the activity. activity_main.xml



    

        


            

            

            

dimens.xml


    16dp
    16dp

strings.xml


    Room Example

    Books borrowed by Mike:
    Refresh
    Young users:

Complete example in Github
References:
https://github.com/googlecodelabs/android-persistence
https://codelabs.developers.google.com/codelabs/android-persistence/#0

Search within Codexpedia

Custom Search

Search the entire web

Custom Search