Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Data Persistence With Room

Data Persistence With Room

This talk is about how to easily persist data in Android using the Room library from the architectural components from Google. Gave the talk at the Android Nigeria meetup.

Mgbemena Chike

October 01, 2017
Tweet

More Decks by Mgbemena Chike

Other Decks in Programming

Transcript

  1. What is Room? Room is an ORM (Object-relational mapping) library

    provided by Google. Google describes Room as providing “an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.”
  2. Why Room? • Because It’s painful getting data in and

    out of the database • Made and maintained by Google
  3. Why Room? • Because It’s painful getting data in and

    out of the database • Made and maintained by Google • Eliminates database boilerplate
  4. Why Room? • Because It’s painful getting data in and

    out of the database • Made and maintained by Google • Eliminates database boilerplate • Compile-time validation of SQLite
  5. Room Classes These are the set of classes or components

    Room is divided into: • Entity • The data access object (DAO)
  6. Room Classes These are the set of classes or components

    Room is divided into: • Entity • The data access object (DAO) • The database.
  7. Room Classes: The Entity class POJOs that model the data

    you’re transferring in and out of the database. For example, entities representing students, departments and faculties.
  8. Room @Entity(tableName = "persons") public class Person { @PrimaryKey @NonNull

    public final String id; public final String firstName; public final String lastName; @Ignore Person(String firstName, String lastName) { this(UUID.randomUUID().toString(), firstName, l) } Person(String id, String firstName, String lastName) { this.id = id; this.firstName = firstName; this.lastName = lastName; } }
  9. Room Classes: DAO “Data access object” is simply the API

    into the data. Contains methods for the database operations that you need: inserts, updates etc.
  10. Room Classes: DAO “Data access object” is simply the API

    into the data. Contains methods for the database operations that you need: inserts, updates etc.
  11. DAO @Dao interface PersonStore { @Query("SELECT * FROM persons ORDER

    BY firstName") List<Person> selectAll(); @Query("SELECT * FROM persons WHERE id=:id") Person findById(String id); @Insert void insert(Person... persons); @Update void update(Person... persons); @Delete void delete(Person... persons); }
  12. Room Classes: Database @Database(entities = {Person.class}, version = 1) abstract

    class PersonDatabase extends RoomDatabase { abstract PersonStore personStore(); private static final String DB_NAME = "persons.db"; private static volatile PersonDatabase INSTANCE = null; synchronized static PersonDatabase get(Context ctxt) { if (INSTANCE == null) { INSTANCE = create(ctxt, false); } return (INSTANCE); } static PersonDatabase create(Context ctx, boolean memoryOnly) { RoomDatabase.Builder<PersonDatabase> b; if (memoryOnly) { b = Room.inMemoryDatabaseBuilder(ctx.getApplicationContext(), PersonDatabase.class); } else { b = Room.databaseBuilder(ctx.getApplicationContext(), PersonDatabase.class, DB_NAME); } return (b.build()); } }
  13. Room Classes: Database @Database(entities = (Person.class), version = 1) abstract

    class PersonDatabase extends RoomDatabase { abstract PersonStore personStore(); private static final String DB_NAME = "persons.db"; private static volatile PersonDatabase INSTANCE = null; synchronized static PersonDatabase get(Context ctxt) { if (INSTANCE == null) { INSTANCE = create(ctxt, false); } return (INSTANCE); } static PersonDatabase create(Context ctx, boolean memoryOnly) { RoomDatabase.Builder<PersonDatabase> b; if (memoryOnly) { b = Room.inMemoryDatabaseBuilder(ctx.getApplicationContext(), PersonDatabase.class); } else { b = Room.databaseBuilder(ctx.getApplicationContext(), PersonDatabase.class, DB_NAME); } return (b.build()); } }
  14. Room Classes: Database @Database(entities = (Person.class), version = 1) abstract

    class PersonDatabase extends RoomDatabase { abstract PersonStore personStore(); private static final String DB_NAME = "persons.db"; private static volatile PersonDatabase INSTANCE = null; synchronized static PersonDatabase get(Context ctxt) { if (INSTANCE == null) { INSTANCE = create(ctxt, false); } return (INSTANCE); } static PersonDatabase create(Context ctx, boolean memoryOnly) { RoomDatabase.Builder<PersonDatabase> b; if (memoryOnly) { b = Room.inMemoryDatabaseBuilder(ctx.getApplicationContext(), PersonDatabase.class); } else { b = Room.databaseBuilder(ctx.getApplicationContext(), PersonDatabase.class, DB_NAME); } return (b.build()); } }
  15. Room Classes: Database @Database(entities = (Person.class), version = 1) abstract

    class PersonDatabase extends RoomDatabase { abstract PersonStore personStore(); private static final String DB_NAME = "persons.db"; private static volatile PersonDatabase INSTANCE = null; synchronized static PersonDatabase get(Context ctxt) { if (INSTANCE == null) { INSTANCE = create(ctxt, false); } return (INSTANCE); } static PersonDatabase create(Context ctx, boolean memoryOnly) { RoomDatabase.Builder<PersonDatabase> b; if (memoryOnly) { b = Room.inMemoryDatabaseBuilder(ctx.getApplicationContext(), PersonDatabase.class); } else { b = Room.databaseBuilder(ctx.getApplicationContext(), PersonDatabase.class, DB_NAME); } return (b.build()); } }
  16. Room Classes: Database @Database(entities = (Person.class), version = 1) abstract

    class PersonDatabase extends RoomDatabase { abstract PersonStore personStore(); private static final String DB_NAME = "persons.db"; private static volatile PersonDatabase INSTANCE = null; synchronized static PersonDatabase get(Context ctxt) { if (INSTANCE == null) { INSTANCE = create(ctxt, false); } return (INSTANCE); } static PersonDatabase create(Context ctx, boolean memoryOnly) { RoomDatabase.Builder<PersonDatabase> b; if (memoryOnly) { b = Room.inMemoryDatabaseBuilder(ctx.getApplicationContext(), PersonDatabase.class); } else { b = Room.databaseBuilder(ctx.getApplicationContext(), PersonDatabase.class, DB_NAME); } return (b.build()); } }
  17. Room Classes: Database @Database(entities = (Person.class), version = 1) abstract

    class PersonDatabase extends RoomDatabase { abstract PersonStore personStore(); private static final String DB_NAME = "persons.db"; private static volatile PersonDatabase INSTANCE = null; synchronized static PersonDatabase get(Context ctxt) { if (INSTANCE == null) { INSTANCE = create(ctxt, false); } return (INSTANCE); } static PersonDatabase create(Context ctx, boolean memoryOnly) { RoomDatabase.Builder<PersonDatabase> b; if (memoryOnly) { b = Room.inMemoryDatabaseBuilder(ctx.getApplicationContext(), PersonDatabase.class); } else { b = Room.databaseBuilder(ctx.getApplicationContext(), PersonDatabase.class, DB_NAME); } return (b.build()); } }
  18. Testing Room Once everything is setup, it’s recommended you start

    testing your database by writing instrumentation tests.
  19. Advantages for using In-Memory Database for Instrumentation Testing: • It

    is self-contained. • Test is much faster because reading and writing to and from memory is much faster than reading and writing from disk.
  20. Writing Unit Tests via Mocks Our Room DAOs can be

    mocked using a mocking library like Mockito. This allows us to write unit tests.
  21. CREATE TABLE IF NOT EXISTS Persons (id INTEGER PRIMARY KEY

    AUTOINCREMENT NOT NULL, firstName TEXT, lastName TEXT) @PrimaryKey(autoGenerate = true) @NonNull public final String id; public final String firstName; public final String lastName; Auto-Generated Primary Keys
  22. Composite Primary Keys @Entity(primaryKeys = {"account_number", "account_type"}) public class AccountThingy

    { public final int accountNumber; public final String accountType; AccountThingy(int accountNumber, String accountType) { this.accountNumber = accountNumber; this.accountType = accountType; } } CREATE TABLE IF NOT EXISTS AccountThingy (accountNumber INTEGER, accountType String NOT NULL, PRIMARY KEY(accountNumber, accountType))
  23. Adding Indexes @Entity(indices = {@Index("regNo")}) public class Student { @PrimaryKey

    public final String id; public final String regNo; public final String name; public Student(String id, String regNo, String name) { this.id = id; this.regNo = regNo; this.name = name } } CREATE INDEX index_Student_regNo ON Student (regNo)
  24. Ignoring Fields @Entity(indices = {@Index(value = "regNo", unique = true)})

    public class Student { @PrimaryKey public final String id; public final String regNo; public final String name; @Ignore private String something; public Student(String id, String regNo, String name) { this.id = id; this.regNo = regNo; this.name = name } }
  25. Column Name @Entity(tableName = "persons") public class Person { @PrimaryKey(autoGenerate

    = true) @NonNull public final String id; @ColumnInfo(name = "first_name") public final String firstName; @ColumnInfo(name = "last_name") public final String lastName; @Ignore Person(String firstName, String lastName) { this(UUID.randomUUID().toString(), firstName, lastName); } Person(String id, String firstName, String lastName) { this.id = id; this.firstName = firstName; this.lastName = lastName; } }
  26. Return Types A @Query can return the following: • A

    single entity (e.g. findById() returning a single Person) • A collection of entity (e.g selectAll() return a List of Person entities) • A Cursor • A Flowable or Publisher from RxJava2 • A LiveData Object
  27. Transactions db.beginTransaction(); try { // bunch of DAO operation here

    db.setTransactionSuccessful(); } finally { db.endTransaction(); }
  28. Threading In Room @Query, @Insert, @update and @Delete methods are

    synchronous. They perform their work on the current thread.
  29. Relations in Room @Entity( tableName = "phones", foreignKeys = @ForeignKey(

    entity = Person.class, parentColumns = "id", childColumns = "personId", onDelete = CASCADE), indices = @Index("personId")) public class Phone { public final String name; public final String personId; public Phone(String name, String personId) { this.name = name; this.personId = personId; } }