Slide 1

Slide 1 text

Room SQLite object mapping library Shohei Kawano @shaunkawano

Slide 2

Slide 2 text

Many apps make data persistent to improve User Experience.

Slide 3

Slide 3 text

Data Persistence for UX • Enabling Offline mode • Preventing redundant API calls for unchangeable data • Loading data from local storage is much faster than loading data from network => Enabling faster data loading

Slide 4

Slide 4 text

What are some options for persisting data in Android?

Slide 5

Slide 5 text

Persistent Storage in Android • Content Providers • Shared Preferences • SQLite

Slide 6

Slide 6 text

Persistent Storage in Android • Content Providers: Publicly sharing data • Shared Preferences: key-value data • SQLite: Structured data

Slide 7

Slide 7 text

Persistent Storage in Android • Content Providers: Publicly sharing data • Shared Preferences: key-value data • SQLite: Structured data

Slide 8

Slide 8 text

Persistent Storage in Android • Content Providers: Publicly sharing data • Shared Preferences: key-value data • SQLite: Structured data → Android provides Core Framework API for SQLite

Slide 9

Slide 9 text

Persistent Storage in Android • Content Providers: Publicly sharing data • Shared Preferences: key-value data • SQLite: Structured data → Android provides Core Framework API for SQLite; works for API level 1~ Let’s read the documentation!

Slide 10

Slide 10 text

https://developer.android.com/training/basics/data-storage/databases.html

Slide 11

Slide 11 text

https://developer.android.com/training/basics/data-storage/databases.html

Slide 12

Slide 12 text

https://developer.android.com/training/basics/data-storage/databases.html Define SQL query statements using String objects

Slide 13

Slide 13 text

https://developer.android.com/training/basics/data-storage/databases.html Declare custom SQLiteOpenHelper class

Slide 14

Slide 14 text

https://developer.android.com/training/basics/data-storage/databases.html Instantiate the helper class

Slide 15

Slide 15 text

Now we are ready to read information from a Database.

Slide 16

Slide 16 text

https://developer.android.com/training/basics/data-storage/databases.html

Slide 17

Slide 17 text

https://developer.android.com/training/basics/data-storage/databases.html Specify FROM WHICH COLUMN to fetch data from

Slide 18

Slide 18 text

https://developer.android.com/training/basics/data-storage/databases.html Specify IN WHAT Condition =(WHERE) to fetch data from

Slide 19

Slide 19 text

https://developer.android.com/training/basics/data-storage/databases.html Specify IN WHAT ORDER to fetch if there are multiple data

Slide 20

Slide 20 text

https://developer.android.com/training/basics/data-storage/databases.html Query data and get result as Cursor object

Slide 21

Slide 21 text

https://developer.android.com/training/basics/data-storage/databases.html

Slide 22

Slide 22 text

https://developer.android.com/training/basics/data-storage/databases.html Finally, move cursor and get information…

Slide 23

Slide 23 text

“This is horrible!”

Slide 24

Slide 24 text

• No compile-time analysis for SQLite statements, so hard to debug, prone to bug. • A lot of boilerplate codes required for converting SQL queries into Application- specific data objects.

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

Room

Slide 27

Slide 27 text

What is Room? - 1 • Introduced in Google I/O 2017 • One of the Architecture Components
 (a.k.a. arch) • Object mapping library for persisting data with SQLite

Slide 28

Slide 28 text

What is Room? - 2 • Fully supports SQLite; focuses on what SQLite can do. • Provide compile-time analysis for SQL queries for better debugging experience and less bugs • Provide Observability and testability

Slide 29

Slide 29 text

What is Room? - 3 • Generate boilerplate codes and implementation by declaring interface with annotations (Similar to Retrofit or Dagger) • No direct “R”elationship between entities; No object references between @Entities • Use @Embedded or @ForeignKey to save or access to minimal data

Slide 30

Slide 30 text

Rest of the Content • Basics of Room • Reading Some of the generated source codes • TypeConverter and observable queries • Conclusion • Extra

Slide 31

Slide 31 text

Not to cover in this talk • About Migration • How to write tests using Room • Other in-depth content

Slide 32

Slide 32 text

Basics of Room

Slide 33

Slide 33 text

Add Room dependencies

Slide 34

Slide 34 text

root/build.gradle allprojects { repositories { jcenter() maven { url 'https://maven.google.com' } } }

Slide 35

Slide 35 text

app/build.gradle compile "android.arch.persistence.room:runtime:1.0.0-alpha1" kapt “android.arch.persistence.room:compiler:1.0.0-alpha1"

Slide 36

Slide 36 text

app/build.gradle compile "android.arch.persistence.room:runtime:1.0.0-alpha1" kapt “android.arch.persistence.room:compiler:1.0.0-alpha1" // RxJava2 support compile "android.arch.persistence.room:rxjava2:1.0.0-alpha1" // Testing support testCompile "android.arch.persistence.room:testing:1.0.0- alpha1"

Slide 37

Slide 37 text

Room Components

Slide 38

Slide 38 text

https://developer.android.com/topic/libraries/architecture/room.html

Slide 39

Slide 39 text

https://developer.android.com/topic/libraries/architecture/room.html

Slide 40

Slide 40 text

@Entity

Slide 41

Slide 41 text

User.kt class User( val uid: Int, val firstName: String, val lastName: String)

Slide 42

Slide 42 text

User.kt @Entity class User( val uid: Int, val firstName: String, val lastName: String)

Slide 43

Slide 43 text

@Entity class User( @PrimaryKey val uid: Int, val firstName: String, val lastName: String) User.kt

Slide 44

Slide 44 text

@Entity class User( @PrimaryKey val uid: Int, val firstName: String, val lastName: String) User.kt

Slide 45

Slide 45 text

@Database(entities = arrayOf(User::class), version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao } AppDatabase.kt

Slide 46

Slide 46 text

@Entity class User( @PrimaryKey val uid: Int, val firstName: String, val lastName: String) User.kt

Slide 47

Slide 47 text

@Entity(tableName = "follower") class User( @PrimaryKey val uid: Int, val firstName: String, val lastName: String) User.kt

Slide 48

Slide 48 text

@Entity(tableName = "follower") class User( @PrimaryKey val uid: Int, @ColumnInfo(name = "first_name") val firstName: String, @ColumnInfo(name = "last_name") val lastName: String) User.kt

Slide 49

Slide 49 text

@Annotations for @Entity

Slide 50

Slide 50 text

@PrimaryKey @Annotations for @Entity

Slide 51

Slide 51 text

User.kt @Entity class User( @PrimaryKey(autoGenerate = true) val uid: Int,


Slide 52

Slide 52 text

User.kt @Entity class User( @PrimaryKey(autoGenerate = true) val uid: Int,
 At least one field annotated with @PrimaryKey is required.

Slide 53

Slide 53 text

User.kt @Entity class User( @PrimaryKey(autoGenerate = true) val uid: Int,
 Room will assign unique ID automatically.

Slide 54

Slide 54 text

@Ignore @Annotations for @Entity

Slide 55

Slide 55 text

User.kt @Entity class User( @PrimaryKey val uid: Int, … @Ignore val profileImage: Bitmap)

Slide 56

Slide 56 text

User.kt @Entity class User( @PrimaryKey val uid: Int, … @Ignore val profileImage: Bitmap)

Slide 57

Slide 57 text

@Embedded @Annotations for @Entity

Slide 58

Slide 58 text

@Entity class User( @PrimaryKey(autoGenerate = true) val uid: Long, …. val address: Address ) class Address( @ColumnInfo(name = "post_code") val postCode: Int, val street: String, val province: String, val city: String )

Slide 59

Slide 59 text

@Entity class User( @PrimaryKey(autoGenerate = true) val uid: Long, …. @Embedded val address: Address ) class Address( @ColumnInfo(name = "post_code") val postCode: Int, val street: String, val province: String, val city: String )

Slide 60

Slide 60 text

@Embedded User table VJE QPTU@DPEF TUSFFU QSPWJODF DJUZ   “Maruyamacho …“ l5PLZPz l4IJCVZBz

Slide 61

Slide 61 text

@Embedded User table VJE QPTU@DPEF TUSFFU QSPWJODF DJUZ   “Maruyamacho …“ l5PLZPz l4IJCVZBz 6TFSLU "EESFTTLU "EESFTTLU "EESFTTLU "EESFTTLU

Slide 62

Slide 62 text

Multiple @Embedded fields @Annotations for @Entity

Slide 63

Slide 63 text

@Entity class User( @PrimaryKey(autoGenerate = true) val uid: Long, …. @Embedded val address: Address ) class Address( @ColumnInfo(name = "post_code") val postCode: Int, val street: String, val state: String, val city: String )

Slide 64

Slide 64 text

@Entity class User( @PrimaryKey(autoGenerate = true) val uid: Long, …. @Embedded val primaryAddress: Address @Embedded val secondaryAddress: Address ) class Address( @ColumnInfo(name = "post_code") val postCode: Int, val street: String, val state: String, val city: String )

Slide 65

Slide 65 text

@Entity class User( @PrimaryKey(autoGenerate = true) val uid: Long, …. @Embedded val primaryAddress: Address @Embedded (prefix = “secondary_”) val secondaryAddress: Address ) class Address( @ColumnInfo(name = "post_code") val postCode: Int, val street: String, val state: String, val city: String )

Slide 66

Slide 66 text

@Embedded User Table VJE QPTU@DPE F TUSFFU QSPWJODF DJUZ TFDPOEB SZ@QPTU@ DPEF TFDPOEB SZ@TUSFFU ʜ 1 1500043 “Maruyam acho…“ “Tokyo” “Shibuya” 1500043 “Kamiyam acho…”

Slide 67

Slide 67 text

Parameters for @Entity

Slide 68

Slide 68 text

@Entity( primaryKeys = arrayOf("first_name", "last_name"), tableName = "follower", indices = arrayOf(Index("last_name"), Index(value = "first_name", unique = true)), inheritSuperIndices = false ) @Entity Parameters

Slide 69

Slide 69 text

@ForeignKey Parameters for @Entity

Slide 70

Slide 70 text

@ForeignKey on Book.kt @Entity(foreignKeys = arrayOf(ForeignKey( entity = User::class, parentColumns = arrayOf("id"), childColumns = arrayOf("user_id"), onDelete = ForeignKey.CASCADE ))) class Book( @PrimaryKey val bookId: Long, @ColumnInfo(name = "user_id") val userId: Long, val title: String)

Slide 71

Slide 71 text

@ForeignKey on Book.kt @Entity(foreignKeys = arrayOf(ForeignKey( entity = User::class, parentColumns = arrayOf("id"), childColumns = arrayOf("user_id"), onDelete = ForeignKey.CASCADE ))) class Book( @PrimaryKey val bookId: Long, @ColumnInfo(name = "user_id") val userId: Long, val title: String)

Slide 72

Slide 72 text

@ForeignKey on Book.kt @Entity(foreignKeys = arrayOf(ForeignKey( entity = User::class, parentColumns = arrayOf("id"), childColumns = arrayOf("user_id"), onDelete = ForeignKey.CASCADE ))) class Book( @PrimaryKey val bookId: Long, @ColumnInfo(name = "user_id") val userId: Long, val title: String)

Slide 73

Slide 73 text

Relationship in Room

Slide 74

Slide 74 text

No object references between @Entities

Slide 75

Slide 75 text

SQLite Object Mapping library Room is

Slide 76

Slide 76 text

Book.kt val title: String val description: String val author: Author

Slide 77

Slide 77 text

Book.kt val title: String val description: String val author: Author

Slide 78

Slide 78 text

No content

Slide 79

Slide 79 text

{ Book { Book { Book { Book { Book

Slide 80

Slide 80 text

{ { { { { Book Book Book Book Book

Slide 81

Slide 81 text

authorNameTextView.setText(user.getAuthor().getName());

Slide 82

Slide 82 text

authorNameTextView.setText(user.getAuthor().getName()); Querying SQLite table in UI thread may create junk…

Slide 83

Slide 83 text

https://developer.android.com/topic/libraries/architecture/room.html

Slide 84

Slide 84 text

https://developer.android.com/topic/libraries/architecture/room.html

Slide 85

Slide 85 text

@Dao

Slide 86

Slide 86 text

UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List) @Update fun update(users: List) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List

Slide 87

Slide 87 text

UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List) @Update fun update(users: List) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List

Slide 88

Slide 88 text

UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List) @Update fun update(users: List) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List

Slide 89

Slide 89 text

UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List) @Update fun update(users: List) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List

Slide 90

Slide 90 text

UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List) @Update fun update(users: List) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List

Slide 91

Slide 91 text

UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List) @Update fun update(users: List) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List

Slide 92

Slide 92 text

UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List) @Update fun update(users: List) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List

Slide 93

Slide 93 text

UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List) @Update fun update(users: List) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List Very similar to Retrofit’s service interfaces

Slide 94

Slide 94 text

UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List) @Update fun update(users: List) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List

Slide 95

Slide 95 text

@Database(entities = arrayOf(User::class), version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao } AppDatabase.kt

Slide 96

Slide 96 text

UserDao.kt @Dao interface UserDao { @Insert fun insertAll(users: List) @Update fun update(users: List) @Delete fun delete(user: User) @Query("SELECT * FROM USER") fun getAll(): List

Slide 97

Slide 97 text

UserDao_Impl.java @Override public List getAll() { final String _sql = "SELECT * FROM USER"; final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0); final Cursor _cursor = __db.query(_statement); try { final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid"); …

Slide 98

Slide 98 text

UserDao_Impl.java @Override public List getAll() { final String _sql = "SELECT * FROM USER"; final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0); final Cursor _cursor = __db.query(_statement); try { final int _cursorIndexOfUid = _cursor.getColumnIndexOrThrow("uid"); …

Slide 99

Slide 99 text

UserDao_Impl.java final List _result = new ArrayList(_cursor.getCount()); while(_cursor.moveToNext()) { final User _item; final int _tmpUid; _tmpUid = _cursor.getInt(_cursorIndexOfUid); … _item = new User(…); _result.add(_item); }

Slide 100

Slide 100 text

UserDao_Impl.java final List _result = new ArrayList(_cursor.getCount()); while(_cursor.moveToNext()) { final User _item; final int _tmpUid; _tmpUid = _cursor.getInt(_cursorIndexOfUid); … _item = new User(…); _result.add(_item); }

Slide 101

Slide 101 text

UserDao_Impl.java final List _result = new ArrayList(_cursor.getCount()); while(_cursor.moveToNext()) { final User _item; final int _tmpUid; _tmpUid = _cursor.getInt(_cursorIndexOfUid); … _item = new User(…); _result.add(_item); }

Slide 102

Slide 102 text

UserDao_Impl.java … return _result; } finally { _cursor.close(); _statement.release(); }

Slide 103

Slide 103 text

… return _result; } finally { _cursor.close(); _statement.release(); } UserDao_Impl.java

Slide 104

Slide 104 text

Methods using @Query

Slide 105

Slide 105 text

UserDao.kt @Dao interface UserDao { @Query("SELECT * FROM user") fun getAll(): List

Slide 106

Slide 106 text

UserDao.kt @Dao interface UserDao { @Query("SELECT * FROM user") fun getAll(): List

Slide 107

Slide 107 text

Bind Parameters Methods using @Query

Slide 108

Slide 108 text

UserDao.kt @Dao interface UserDao { @Query("SELECT * FROM user") fun getAll(): List @Query("SELECT * FROM user WHERE age > :minAge" ) fun loadAllUsersOlderThan(minAge: Int): List

Slide 109

Slide 109 text

@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge") public User[] loadAllUsersBetweenAges(int minAge, int maxAge); UserDao.kt @Dao interface UserDao { @Query("SELECT * FROM user") fun getAll(): List @Query("SELECT * FROM user WHERE age > :minAge" ) fun loadAllUsersOlderThan(minAge: Int): List

Slide 110

Slide 110 text

@Query("SELECT * FROM user WHERE first_name LIKE :search" + "OR last_name LIKE :search") fun findUserWithName(search: String): List UserDao.kt @Dao interface UserDao { @Query("SELECT * FROM user") fun getAll(): List @Query("SELECT * FROM user WHERE age > :minAge" ) fun loadAllUsersOlderThan(minAge: Int): List

Slide 111

Slide 111 text

Returning Subsets of Columns Methods using @Query

Slide 112

Slide 112 text

NameTaple.kt @Entity class NameTuple( @ColumnInfo(name = "first_name") val firstName: String, @ColumnInfo(name = "last_name") val lastName: String )

Slide 113

Slide 113 text

NameTaple.kt @Entity class NameTuple( @ColumnInfo(name = "first_name") val firstName: String, @ColumnInfo(name = "last_name") val lastName: String )

Slide 114

Slide 114 text

UserDao.kt @Dao interface UserDao { @Query("SELECT first_name, last_name FROM user") fun loadFullName(): List

Slide 115

Slide 115 text

UserDao.kt @Dao interface UserDao { @Query("SELECT first_name, last_name, middleName FROM user") fun loadFullName(): List

Slide 116

Slide 116 text

UserDao.kt @Dao interface UserDao { @Query("SELECT first_name, last_name, FROM user") fun loadFullName(): List

Slide 117

Slide 117 text

OnConflictStrategy for @Insert, @Update

Slide 118

Slide 118 text

UserDao.kt @Dao interface UserDao { @Insert(onConflict = OnConflictStrategy.ROLLBACK) fun insertAll(users: List) @Update(onConflict = OnConflictStrategy.REPLACE) fun updateUsers(users: List)

Slide 119

Slide 119 text

@Retention(SOURCE) … public @interface OnConflictStrategy { int REPLACE = 1; int ROLLBACK = 2; int ABORT = 3; int FAIL = 4; int IGNORE = 5; } OnConflictStrategy.java

Slide 120

Slide 120 text

@Retention(SOURCE) … public @interface OnConflictStrategy { int REPLACE = 1; int ROLLBACK = 2; int ABORT = 3; int FAIL = 4; int IGNORE = 5; } OnConflictStrategy.java https://sqlite.org/lang_conflict.html Based on SQLite Conflict Algorithm

Slide 121

Slide 121 text

https://developer.android.com/topic/libraries/architecture/room.html

Slide 122

Slide 122 text

https://developer.android.com/topic/libraries/architecture/room.html

Slide 123

Slide 123 text

@Database

Slide 124

Slide 124 text

@Database(entities = arrayOf(User::class, Address::class), version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao } AppDatabase.kt

Slide 125

Slide 125 text

@Database(entities = arrayOf(User::class, Address::class), version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao } AppDatabase.kt

Slide 126

Slide 126 text

@Database(entities = arrayOf(User::class, Address::class), version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao } AppDatabase.kt

Slide 127

Slide 127 text

AppDatabase.kt appDatabase = Room.databaseBuilder(this, AppDatabase::class.java, "room-sample-database") .build();

Slide 128

Slide 128 text

AppDatabase.kt appDatabase = Room.databaseBuilder(this, AppDatabase::class.java, "room-sample-database") .build(); // InMemoryDatabase appDatabase = Room.inMemoryDatabaseBuilder(this, AppDatabase::class.java).build();

Slide 129

Slide 129 text

Room Extensions

Slide 130

Slide 130 text

@TypeConverters

Slide 131

Slide 131 text

@Entity class User( @PrimaryKey(autoGenerate = true) val uid: Int, @ColumnInfo(name = "first_name") val firstName: String, @ColumnInfo(name = "last_name") val lastName: String, @JvmField val birthday: LocalDateTime ) User.kt

Slide 132

Slide 132 text

class LocalDateTimeConverter { @TypeConverter fun fromTimestamp(value: Long?): LocalDateTime? { return if (value == null) null else LocalDateTime.ofEpochSecond(value, 0, ZoneOffset.UTC) } @TypeConverter fun dateToTimestamp(date: LocalDateTime?): Long? { return date?.toEpochSecond(ZoneOffset.UTC) …. LocalDateTimeConverter.kt

Slide 133

Slide 133 text

class LocalDateTimeConverter { @TypeConverter fun fromTimestamp(value: Long?): LocalDateTime? { return if (value == null) null else LocalDateTime.ofEpochSecond(value, 0, ZoneOffset.UTC) } @TypeConverter fun dateToTimestamp(date: LocalDateTime?): Long? { return date?.toEpochSecond(ZoneOffset.UTC) …. LocalDateTimeConverter.kt

Slide 134

Slide 134 text

@Database(entities = arrayOf(User::class, Address::class), version = 1) @TypeConverters(LocalDateTimeConverter::class) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao } AppDatabase.kt

Slide 135

Slide 135 text

private final LocalDateTimeConverter __localDateTimeConverter = new LocalDateTimeConverter(); ... final LocalDateTime _tmpBirthday; final Long _tmp; ... _tmpBirthday = __localDateTimeConverter.fromTimestamp(_tmp); UserDao_Impl.java

Slide 136

Slide 136 text

Observable Queries

Slide 137

Slide 137 text

@Query("SELECT * FROM user WHERE first_name LIKE :search" + "OR last_name LIKE :search") fun findUserWithName(search: String): List UserDao.kt @Dao interface UserDao { @Query("SELECT * FROM user") fun getAll(): List

Slide 138

Slide 138 text

@Query("SELECT * FROM user WHERE first_name LIKE :search" + "OR last_name LIKE :search") fun findUserWithName(search: String): LiveData> UserDao.kt @Dao interface UserDao { @Query("SELECT * FROM user") fun getAll(): Flowable>

Slide 139

Slide 139 text

@Query("SELECT * FROM user WHERE first_name LIKE :search" + "OR last_name LIKE :search") fun findUserWithName(search: String): LiveData> UserDao.kt @Dao interface UserDao { @Query("SELECT * FROM user") fun getAll(): Flowable>

Slide 140

Slide 140 text

Conclusion

Slide 141

Slide 141 text

Conclusion • Room provides simple and customizable APIs with raw SQL queries to solve current problems on Framework APIs • Room forbids direct references between entities and forces to access/fetch as minimal data as possible • Room supports LiveData and Flowable integration for observability

Slide 142

Slide 142 text

Extra

Slide 143

Slide 143 text

Extra • You may not need Room if current DB or ORM library works better for you • Compared to other Architecture Components, Room is more stable and ready to be used • Room currently does not support query builders - “That’s a Android Studio’s job and we are working on it”

Slide 144

Slide 144 text

Room SQLite object mapping library Shohei Kawano @shaunkawano

Slide 145

Slide 145 text

Room SQLite object mapping library Shohei Kawano @shaunkawano Thank You!