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
Search the entire web