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

[Aleksander Piotrowski] Is there a room for Room?

[Aleksander Piotrowski] Is there a room for Room?

Presentation from GDG DevFest Ukraine 2017 - the biggest community-driven Google tech conference in the CEE.

Learn more at: https://devfest.gdg.org.ua

Google Developers Group Lviv

October 14, 2017
Tweet

More Decks by Google Developers Group Lviv

Other Decks in Technology

Transcript

  1. "How should I design my Android application? What kind of

    MVC pattern should I use? What should I use for an event bus?" https://plus.google.com/+DianneHackborn/posts/FXCCYxepsDU
  2. "How should I design my Android application? What kind of

    MVC pattern should I use? What should I use for an event bus?" We often see questions from developers that are asking from the Android platform engineers about the kinds of design patterns and architectures they use in their apps. But the answer, maybe surprisingly, is we often don't have a strong opinion or really an opinion at all. https://plus.google.com/+DianneHackborn/posts/FXCCYxepsDU
  3. Why Google created Room? • … because (now) they care

    • not only Room but Android Architecture Components
  4. Data model Invoice • Reference number • Seller/buyer data •

    Tax information • Total amount • … around 25 different properties • Invoice ID
  5. Data model Invoice • Reference number • Seller/buyer data •

    Tax information • Total amount • … around 25 different properties • Invoice ID Advance payment • Amount • Date • Invoice ID • Advance payment ID
  6. Data model Invoice • Reference number • Seller/buyer data •

    Tax information • Total amount • … around 25 different properties • Invoice ID Advance payment • Amount • Date • Invoice ID • Advance payment ID
  7. Data model Invoice • Reference number • Seller/buyer data •

    Tax information • Total amount • … around 25 different properties • Invoice ID Advance payment • Amount • Date • Invoice ID • Advance payment ID
  8. Room: build.gradle dependencies { [...] implementation 'io.reactivex.rxjava2:rxjava:2.1.0' implementation 'android.arch.persistence.room:runtime:1.0.0-alpha4' implementation

    'android.arch.persistence.room:rxjava2:1.0.0-alpha4' annotationProcessor 'android.arch.persistence.room:compiler:1.0.0-alpha4' testImplementation 'android.arch.persistence.room:testing:1.0.0-alpha4' [...] }
  9. Room: build.gradle dependencies { [...] implementation 'io.reactivex.rxjava2:rxjava:2.1.2' implementation 'android.arch.persistence.room:runtime:1.0.0-alpha9-1' implementation

    'android.arch.persistence.room:rxjava2:1.0.0-alpha9-1' annotationProcessor 'android.arch.persistence.room:compiler:1.0.0-alpha9-1' testImplementation 'android.arch.persistence.room:testing:1.0.0-alpha9-1' [...] }
  10. public class InvoiceData { private long id; private String invoiceNo;

    private long amountGross; private long vatRate; // ... }
  11. public class InvoiceData { public long id; public String invoiceNo;

    public long amountGross; public long vatRate; // ... }
  12. @Entity(tableName = "Invoices") public class InvoiceData { public long id;

    public String invoiceNo; public long amountGross; public long vatRate; // ... }
  13. @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; public String invoiceNo; public long amountGross; public long vatRate; // ... }
  14. @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // ... }
  15. @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // … public long dontPersistThisOnePlease; }
  16. @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // … @Ignore public long dontPersistThisOnePlease; }
  17. public class AdvancePaymentData { public long id; public long invoiceId;

    public long amount; public long date; } @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name = "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // ... }
  18. @Entity(tableName = "AdvancePayments") public class AdvancePaymentData { public long id;

    public long invoiceId; public long amount; public long date; } @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name = "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // ... }
  19. @Entity(tableName = "AdvancePayments") public class AdvancePaymentData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; public long invoiceId; public long amount; public long date; } @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name = "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // ... }
  20. @Entity(tableName = "AdvancePayments") public class AdvancePaymentData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; @ColumnInfo(name = "invoice_id") public long invoiceId; @ColumnInfo(name = "amount") public long amount; @ColumnInfo(name = "date") public long date; } @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name = "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // ... }
  21. @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // ... } @Entity(tableName = "AdvancePayments") public class AdvancePaymentData { @PrimaryKey @ColumnInfo(name = "id") public long id; @ColumnInfo(name = "invoice_id") public long invoiceId; @ColumnInfo(name = "amount") public long amount; @ColumnInfo(name = "date") public long date; }
  22. @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // ... } @Entity(tableName = "AdvancePayments") public class AdvancePaymentData { @PrimaryKey @ColumnInfo(name = "id") public long id; @ColumnInfo(name = "invoice_id") public long invoiceId; @ColumnInfo(name = "amount") public long amount; @ColumnInfo(name = "date") public long date; }
  23. @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // ... } @Entity(tableName = "AdvancePayments") public class AdvancePaymentData { @PrimaryKey @ColumnInfo(name = "id") public long id; @ColumnInfo(name = "invoice_id") public long invoiceId; @ColumnInfo(name = "amount") public long amount; @ColumnInfo(name = "date") public long date; }
  24. @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // ... } @Entity(tableName = "AdvancePayments") public class AdvancePaymentData { @PrimaryKey @ColumnInfo(name = "id") public long id; @ColumnInfo(name = "invoice_id") public long invoiceId; @ColumnInfo(name = "amount") public long amount; @ColumnInfo(name = "date") public long date; }
  25. @Entity(tableName = "AdvancePayments") public class AdvancePaymentData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; @ColumnInfo(name = "invoice_id") public long invoiceId; @ColumnInfo(name = "amount") public long amount; @ColumnInfo(name = "date") public long date; } @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name = "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // ... } ?
  26. @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // … @OneToMany public List<AdvancePaymentData> advancePayments; } @Entity(tableName = "AdvancePayments") public class AdvancePaymentData { @PrimaryKey @ColumnInfo(name = "id") public long id; @ColumnInfo(name = "invoice_id") public long invoiceId; @ColumnInfo(name = "amount") public long amount; @ColumnInfo(name = "date") public long date; }
  27. @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // … @OneToMany public List<AdvancePaymentData> advancePayments; } @Entity(tableName = "AdvancePayments") public class AdvancePaymentData { @PrimaryKey @ColumnInfo(name = "id") public long id; @ColumnInfo(name = "invoice_id") public long invoiceId; @ColumnInfo(name = "amount") public long amount; @ColumnInfo(name = "date") public long date; }
  28. @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // … } @Entity(tableName = "AdvancePayments") public class AdvancePaymentData { @PrimaryKey @ColumnInfo(name = "id") public long id; @ColumnInfo(name = "invoice_id") public long invoiceId; @ColumnInfo(name = "amount") public long amount; @ColumnInfo(name = "date") public long date; }
  29. @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // … } @Entity(tableName = "AdvancePayments", foreignKeys = @ForeignKey( entity = InvoiceData.class, parentColumns = "id", childColumns = "invoice_id", onDelete = ForeignKey.CASCADE ) ) public class AdvancePaymentData { @PrimaryKey @ColumnInfo(name = "id") public long id; @ColumnInfo(name = "invoice_id") public long invoiceId; ...
  30. @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // … } @Entity(tableName = "AdvancePayments", foreignKeys = @ForeignKey( entity = InvoiceData.class, parentColumns = "id", childColumns = "invoice_id", onDelete = ForeignKey.CASCADE ) ) public class AdvancePaymentData { @PrimaryKey @ColumnInfo(name = "id") public long id; @ColumnInfo(name = "invoice_id") public long invoiceId; ...
  31. @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // … } @Entity(tableName = "AdvancePayments", foreignKeys = @ForeignKey( entity = InvoiceData.class, parentColumns = "id", childColumns = "invoice_id", onDelete = ForeignKey.CASCADE ) ) public class AdvancePaymentData { @PrimaryKey @ColumnInfo(name = "id") public long id; @ColumnInfo(name = "invoice_id") public long invoiceId; ...
  32. @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // … } @Entity(tableName = "AdvancePayments", foreignKeys = @ForeignKey( entity = InvoiceData.class, parentColumns = "id", childColumns = "invoice_id", onDelete = ForeignKey.CASCADE ) ) public class AdvancePaymentData { @PrimaryKey @ColumnInfo(name = "id") public long id; @ColumnInfo(name = "invoice_id") public long invoiceId; ...
  33. @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // … } @Entity(tableName = "AdvancePayments", foreignKeys = @ForeignKey( entity = InvoiceData.class, parentColumns = "id", childColumns = "invoice_id", onDelete = ForeignKey.CASCADE ) ) public class AdvancePaymentData { @PrimaryKey @ColumnInfo(name = "id") public long id; @ColumnInfo(name = "invoice_id") public long invoiceId; ...
  34. @Entity(tableName = "Invoices") public class InvoiceData { // … @ColumnInfo(name

    = "amount_gross") public long amountGross; @ColumnInfo(name = "amount_net") public long amountNet; @ColumnInfo public String currency; @ColumnInfo(name = "vat_rate") public long vatRate; @ColumnInfo(name = "vat_amount") public long vatAmount; // … }
  35. @Entity(tableName = "Invoices") public class InvoiceData { // … @ColumnInfo(name

    = "amount_gross") public long amountGross; @ColumnInfo(name = "amount_net") public long amountNet; @ColumnInfo public String currency; @ColumnInfo(name = "vat_rate") public long vatRate; @ColumnInfo(name = "vat_amount") public long vatAmount; // … } public class PriceData { @ColumnInfo(name = "amount_gross") public long amountGross; @ColumnInfo(name = "amount_net") public long amountNet; @ColumnInfo public String currency; @ColumnInfo(name = "vat_rate") public long vatRate; @ColumnInfo(name = "vat_amount") public long vatAmount; }
  36. @Entity(tableName = "Invoices") public class InvoiceData { // … @ColumnInfo(name

    = "amount_gross") public long amountGross; @ColumnInfo(name = "amount_net") public long amountNet; @ColumnInfo public String currency; @ColumnInfo(name = "vat_rate") public long vatRate; @ColumnInfo(name = "vat_amount") public long vatAmount; // … } public class PriceData { @ColumnInfo(name = "amount_gross") public long amountGross; @ColumnInfo(name = "amount_net") public long amountNet; @ColumnInfo public String currency; @ColumnInfo(name = "vat_rate") public long vatRate; @ColumnInfo(name = "vat_amount") public long vatAmount; }
  37. @Entity(tableName = "Invoices") public class InvoiceData { // … @Embedded

    public String priceData; // … } public class PriceData { @ColumnInfo(name = "amount_gross") public long amountGross; @ColumnInfo(name = "amount_net") public long amountNet; @ColumnInfo public String currency; @ColumnInfo(name = "vat_rate") public long vatRate; @ColumnInfo(name = "vat_amount") public long vatAmount; }
  38. @Entity(tableName = "Invoices") public class InvoiceData { // … public

    String invoiceStatus; // … } public enum InvoiceStatus { PAID(1, "Z"), OVERDUE(2, "P"), STORNO(3, "Q"), UNKNOWN(4, "UNKNOWN"); }
  39. @Entity(tableName = "Invoices") public class InvoiceData { // … public

    String invoiceStatus; // … } public enum InvoiceStatus { PAID(1, "Z"), OVERDUE(2, "P"), STORNO(3, "Q"), UNKNOWN(4, "UNKNOWN"); }
  40. @Entity(tableName = "Invoices") public class InvoiceData { // … public

    String invoiceStatus; // … } public enum InvoiceStatus { PAID(1, "Z"), OVERDUE(2, "P"), STORNO(3, "Q"), UNKNOWN(4, "UNKNOWN"); }
  41. @Entity(tableName = "Invoices") public class InvoiceData { // … public

    InvoiceStatus invoiceStatus; // … } public enum InvoiceStatus { PAID(1, "Z"), OVERDUE(2, "P"), STORNO(3, "Q"), UNKNOWN(4, "UNKNOWN"); }
  42. public class RoomTypeConverters { @TypeConverter public InvoiceStatus fromDatabase(Long value) {

    if (value == null) { return null; } else { return InvoiceStatus.fromDatabaseId(value); } } @TypeConverter public Long toDatabase(InvoiceStatus invoiceStatus) { if (invoiceStatus == null) { return null; } else { return invoiceStatus.getDatabaseId(); } } }
  43. public class RoomTypeConverters { @TypeConverter public InvoiceStatus fromDatabase(Long value) {

    if (value == null) { return null; } else { return InvoiceStatus.fromDatabaseId(value); } } @TypeConverter public Long toDatabase(InvoiceStatus invoiceStatus) { if (invoiceStatus == null) { return null; } else { return invoiceStatus.getDatabaseId(); } } }
  44. public class RoomTypeConverters { @TypeConverter public InvoiceStatus fromDatabase(Long value) {

    if (value == null) { return null; } else { return InvoiceStatus.fromDatabaseId(value); } } @TypeConverter public Long toDatabase(InvoiceStatus invoiceStatus) { if (invoiceStatus == null) { return null; } else { return invoiceStatus.getDatabaseId(); } } }
  45. public class RoomTypeConverters { @TypeConverter public InvoiceStatus fromDatabase(Long value) {

    if (value == null) { return null; } else { return InvoiceStatus.fromDatabaseId(value); } } @TypeConverter public Long toDatabase(InvoiceStatus invoiceStatus) { if (invoiceStatus == null) { return null; } else { return invoiceStatus.getDatabaseId(); } } }
  46. Recap • @Entity to declare models stored in database •

    Add some “structure” with @Embedded • Support all possible data types with @TypeConverter • Add relation between entities with @ForeignKey • … but not as cool as @OneToMany entities in Requery • BTW all above features are available in Requery
  47. Room @Database( entities = {InvoiceData.class, AdvancePaymentData.class}, version = 4, )

    public abstract class FaktoriaDatabase extends RoomDatabase { }
  48. Room @Database( entities = {InvoiceData.class, AdvancePaymentData.class}, version = 4, exportSchema

    = false ) public abstract class FaktoriaDatabase extends RoomDatabase { }
  49. Room @Database( entities = {InvoiceData.class, AdvancePaymentData.class}, version = 4, exportSchema

    = false ) @TypeConverters({RoomTypeConverters.class}) public abstract class FaktoriaDatabase extends RoomDatabase { }
  50. Room @Database( entities = {InvoiceData.class, AdvancePaymentData.class}, version = 4, exportSchema

    = false ) @TypeConverters({RoomTypeConverters.class}) public abstract class FaktoriaDatabase extends RoomDatabase { public abstract InvoiceDataDao invoiceDataDao(); }
  51. Room @Database( entities = {InvoiceData.class, AdvancePaymentData.class}, version = 4, exportSchema

    = false ) @TypeConverters({RoomTypeConverters.class}) public abstract class FaktoriaDatabase extends RoomDatabase { public abstract InvoiceDataDao invoiceDataDao(); public abstract AdvancePaymentDataDao advancePaymentDataDao(); }
  52. Room @Database( entities = {InvoiceData.class, AdvancePaymentData.class}, version = 4, exportSchema

    = false ) @TypeConverters({RoomTypeConverters.class}) public abstract class FaktoriaDatabase extends RoomDatabase { public abstract InvoiceDataDao invoiceDataDao(); public abstract AdvancePaymentDataDao advancePaymentDataDao(); }
  53. Recap • Database class to glue things together • Room

    generates code to open/create database file, create tables, do migrations, etc. • With Requery done in a similar fashion but with less annotations and more Java code
  54. @Dao public interface MyDao { @Query("SELECT * FROM Invoices WHERE

    id = :invoiceId") InvoiceData getInvoice(long invoiceId); }
  55. @Dao public interface MyDao { @Query("SELECT * FROM Invoices WHERE

    id = :invoiceId") InvoiceData getInvoice(long invoiceId); } public Maybe<InvoiceData> getInvoice(long invoiceId) { return database .select(InvoiceData.class) .where(InvoiceData.ID.eq(invoiceId)) .get() .maybe(); }
  56. @Dao public interface MyDao { @Query("SELECT * FROM Invoices WHERE

    id = :invoiceId") InvoiceData getInvoice(long invoiceId); } public Maybe<InvoiceData> getInvoice(long invoiceId) { return database .select(InvoiceData.class) .where(InvoiceData.ID.eq(invoiceId)) .get() .maybe(); }
  57. @Dao public interface MyDao { @Query("SELECT * FROM Invoices WHERE

    id = :invoiceId") InvoiceData getInvoice(long invoiceId); }
  58. @Dao public interface MyDao { @Query("SELECT * FROM Invoices WHERE

    id = :invoiceId") Single<InvoiceData> getInvoice(long invoiceId); }
  59. @Dao public interface MyDao { @Query("SELECT * FROM Invoices WHERE

    id = :invoiceId") Flowable<InvoiceData> getInvoice(long invoiceId); }
  60. @Dao public interface MyDao { @Query("SELECT * FROM Invoices WHERE

    id = :invoiceId") Single<InvoiceData> getInvoice(long invoiceId); @Query("SELECT * FROM Invoices WHERE id = :invoiceId") Flowable<InvoiceData> getInvoice2(long invoiceId); }
  61. @Dao public interface MyDao { @Query("SELECT * FROM Invoices WHERE

    id = :invoiceId") Single<InvoiceData> getInvoice(long invoiceId); @Query("SELECT * FROM Invoices WHERE id = :invoiceId") Flowable<InvoiceData> getInvoiceAndListenForUpdates(long invoiceId); }
  62. @Dao public interface MyDao { @Query("SELECT * FROM Invoices WHERE

    id = :invoiceId") Single<InvoiceData> getInvoice(long invoiceId); @Query("SELECT * FROM Invoices WHERE id = :invoiceId") Flowable<List<InvoiceData>> getInvoiceAndListenForUpdates(long invoiceId); }
  63. @Dao public interface MyDao { @Query("SELECT * FROM Invoices WHERE

    id = :invoiceId") Single<InvoiceData> getInvoice(long invoiceId); @Query("SELECT * FROM Invoices WHERE id = :invoiceId") Flowable<List<InvoiceData>> getInvoiceAndListenForUpdates(long invoiceId); @Query("SELECT * FROM Invoices") Flowable<List<InvoiceData>> getAllInvoicesAndListenForUpdates(); }
  64. @Dao public interface MyDao { @Update void updateInvoice(InvoiceData invoice); @Insert

    void insertInvoice(InvoiceData invoice); @Insert(onConflict = OnConflictStrategy.REPLACE) void upsertInvoice(InvoiceData invoice); }
  65. @Dao public interface MyDao { @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") List<Book> findBooksBorrowedByNameSync(String userName); }
  66. @Dao public interface InvoiceDataDao { @Query("SELECT * FROM Invoices") Flowable<List<InvoiceOverviewData>>

    getInvoices(); } public class InvoiceOverviewData { // … @ColumnInfo(name = "invoice_no") private String invoiceNo; @ColumnInfo(name = "amount_gross") private long amountGross; @ColumnInfo(name = "debtor_name") private String debtorName; // … }
  67. Warning:(25, 41) The query returns some columns [purchase_date, funding_end_date, contract_id,

    debtor_address, debtor_nip, debtor_regon, creditor_name, creditor_address, creditor_nip, creditor_regon, correction, sell_date, import_date, amount_net, currency, vat_rate, vat_amount] which are not use by online.faktoria.data.invoice.model.InvoiceOverviewData. You can use @ColumnInfo annotation on the fields to specify the mapping. You can suppress this warning by annotating the method with @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH). Columns returned by the query: id, invoice_no, purchase_date, funding_end_date, contract_id, debtor_name, debtor_address, debtor_nip, debtor_regon, creditor_name, creditor_address, creditor_nip, creditor_regon, correction, sell_date, maturity_date, invoice_status_code, import_date, amount_gross, amount_net, currency, vat_rate, vat_amount. Fields in online.faktoria.data.invoice.model.InvoiceOverviewData: id, invoice_no, amount_gross, debtor_name, maturity_date, invoice_status_code.
  68. Warning:(25, 41) The query returns some columns [...] which are

    not use by InvoiceOverviewData. You can use @ColumnInfo annotation on the fields to specify the mapping. You can suppress this warning by annotating the method with @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH). Columns returned by the query: ... Fields in InvoiceOverviewData: ...
  69. @Dao public interface InvoiceDataDao { @Query("SELECT * FROM Invoices") Flowable<List<InvoiceOverviewData>>

    getInvoices(); } public class InvoiceOverviewData { // … @ColumnInfo(name = "invoice_no") private String invoiceNo; @ColumnInfo(name = "amount_gross") private long amountGross; @ColumnInfo(name = "debtor_name") private String debtorName; // … }
  70. @Dao public interface InvoiceDataDao { @Query("SELECT * FROM Invoices") Flowable<List<InvoiceOverviewData>>

    getInvoices(); } public class InvoiceOverviewData { // … @ColumnInfo(name = "invoice_no") private String invoiceNo; @ColumnInfo(name = "amount_gross") private long amountGross; @ColumnInfo(name = "debtor_name") private String debtorName; // … }
  71. @Dao public interface InvoiceDataDao { @Query("SELECT id, invoice_no, amount_gross, debtor_name,

    debtor_name, ” + “maturity_date, invoice_status_code FROM Invoices") Flowable<List<InvoiceOverviewData>> getInvoices(); } public class InvoiceOverviewData { // … @ColumnInfo(name = "invoice_no") private String invoiceNo; @ColumnInfo(name = "amount_gross") private long amountGross; @ColumnInfo(name = "debtor_name") private String debtorName; // … }
  72. @Dao public interface InvoiceDataDao { Flowable<InvoiceAndAdvancePaymentsData> getInvoice(long invoiceId); } public

    class InvoiceAndAdvancePaymentsData { public InvoiceData invoice; public List<AdvancePaymentData> advancePayments; }
  73. @Dao public interface InvoiceDataDao { @Query("SELECT * FROM Invoices WHERE

    id = :invoiceId") Flowable<InvoiceAndAdvancePaymentsData> getInvoice(long invoiceId); } public class InvoiceAndAdvancePaymentsData { public InvoiceData invoice; public List<AdvancePaymentData> advancePayments; }
  74. @Dao public interface InvoiceDataDao { @Query("SELECT * FROM Invoices WHERE

    id = :invoiceId") Flowable<InvoiceAndAdvancePaymentsData> getInvoice(long invoiceId); } public class InvoiceAndAdvancePaymentsData { @Embedded public InvoiceData invoice; public List<AdvancePaymentData> advancePayments; }
  75. @Dao public interface InvoiceDataDao { @Query("SELECT * FROM Invoices WHERE

    id = :invoiceId") Flowable<InvoiceAndAdvancePaymentsData> getInvoice(long invoiceId); } public class InvoiceAndAdvancePaymentsData { @Embedded public InvoiceData invoice; @Relation(parentColumn = "id", entityColumn = "invoice_id") public List<AdvancePaymentData> advancePayments; }
  76. Recap • Have to write SQL queries by hand …

    and not in Requery case • Synchronous writes and asynchronous reads … and however you like with Requery • Can do advanced queries with SQL JOINs, @Relation, or custom POJOs to map results to
  77. @RunWith(AndroidJUnit4.class) public class RoomTest { @Rule public InstantTaskExecutorRule instantTaskExecutorRule =

    new InstantTaskExecutorRule(); @Before public void setup() { } @Before public void tearDown() { } }
  78. @RunWith(AndroidJUnit4.class) public class RoomTest { @Rule public InstantTaskExecutorRule instantTaskExecutorRule =

    new InstantTaskExecutorRule(); private MyDatabase database; @Before public void setup() { } @Before public void tearDown() { } }
  79. @RunWith(AndroidJUnit4.class) public class RoomTest { @Rule public InstantTaskExecutorRule instantTaskExecutorRule =

    new InstantTaskExecutorRule(); private MyDatabase database; @Before public void setup() { Context context = InstrumentationRegistry.getTargetContext(); database = Room .inMemoryDatabaseBuilder(context, MyDatabase.class) .build(); } @Before public void tearDown() { } }
  80. @RunWith(AndroidJUnit4.class) public class RoomTest { @Rule public InstantTaskExecutorRule instantTaskExecutorRule =

    new InstantTaskExecutorRule(); private MyDatabase database; @Before public void setup() { Context context = InstrumentationRegistry.getTargetContext(); database = Room .inMemoryDatabaseBuilder(context, MyDatabase.class) .build(); } @Before public void tearDown() { database.close(); } }
  81. @RunWith(AndroidJUnit4.class) public class RoomTest { @Rule public InstantTaskExecutorRule instantTaskExecutorRule =

    new InstantTaskExecutorRule(); private MyDatabase database; private ProjectDao projectDao; private TaskDao taskDao; @Before public void setup() { Context context = InstrumentationRegistry.getTargetContext(); database = Room .inMemoryDatabaseBuilder(context, MyDatabase.class) .build(); } // …
  82. @RunWith(AndroidJUnit4.class) public class RoomTest { @Rule public InstantTaskExecutorRule instantTaskExecutorRule =

    new InstantTaskExecutorRule(); private MyDatabase database; private ProjectDao projectDao; private TaskDao taskDao; @Before public void setup() { Context context = InstrumentationRegistry.getTargetContext(); database = Room .inMemoryDatabaseBuilder(context, MyDatabase.class) .build(); projectDao = database.projectDao(); taskDao = database.taskDao(); } // …
  83. @Test public void shouldEmitNewTaskForProject() { // given projectDao.insertProject(project1).test(); // when

    TestSubscriber<List<Task>> testSubscriber = taskDao.getTaskForProject(project1) .test(); // then testSubscriber.assertNotComplete(); testSubscriber.assertNoErrors(); assertEquals(1, testSubscriber.values().size()); // [ [] ] assertEquals(0, testSubscriber.values().get(0).size()); // [ [] ] // ...
  84. // ... // when task1.setProject(project1); taskDao.insertTask(task1).test(); // then testSubscriber.assertNotComplete(); testSubscriber.assertNoErrors();

    assertEquals(2, testSubscriber.values().size()); assertEquals(0, testSubscriber.values().get(0).size()); // [ [] ] assertEquals(1, testSubscriber.values().get(1).size()); // [ [ Task1 ] ] }
  85. @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // … }
  86. @Entity(tableName = "Invoices") public class InvoiceData { @PrimaryKey @ColumnInfo(name =

    "id") public long id; @ColumnInfo(name = "invoice_no") public String invoiceNo; public long amountGross; public long vatRate; // … @ColumnInfo(name = "new_field") public long newField; }
  87. CREATE TABLE `Invoices` ( `id` INTEGER NOT NULL, `invoice_no` TEXT,

    `purchase_date` INTEGER NOT NULL, // … }
  88. CREATE TABLE `Invoices` ( `id` INTEGER NOT NULL, `invoice_no` TEXT,

    `purchase_date` INTEGER NOT NULL, // … } CREATE TABLE `Invoices` ( `id` INTEGER NOT NULL, `invoice_no` TEXT, `purchase_date` INTEGER NOT NULL, // … new_field INTEGER NOT NULL DEFAULT 4 }
  89. CREATE TABLE `Invoices` ( `id` INTEGER NOT NULL, `invoice_no` TEXT,

    `purchase_date` INTEGER NOT NULL, // … } CREATE TABLE `Invoices` ( `id` INTEGER NOT NULL, `invoice_no` TEXT, `purchase_date` INTEGER NOT NULL, // … new_field INTEGER NOT NULL DEFAULT 4 }
  90. java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you’ve

    changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
  91. @Database( entities = {InvoiceData.class, AdvancePaymentData.class}, version = 4, exportSchema =

    false ) @TypeConverters({RoomTypeConverters.class}) public abstract class FaktoriaDatabase extends RoomDatabase { public abstract InvoiceDataDao invoiceDataDao(); public abstract AdvancePaymentDataDao advancePaymentDataDao(); }
  92. @Database( entities = {InvoiceData.class, AdvancePaymentData.class}, version = 5, exportSchema =

    false ) @TypeConverters({RoomTypeConverters.class}) public abstract class FaktoriaDatabase extends RoomDatabase { public abstract InvoiceDataDao invoiceDataDao(); public abstract AdvancePaymentDataDao advancePaymentDataDao(); }
  93. java.lang.IllegalStateException: A migration from 4 to 5 is necessary. Please

    provide a Migration in the builder or call fallbackToDestructiveMigration in the builder in which case Room will re-create all of the tables.
  94. static final Migration MIGRATION_4_5 = new Migration(4, 5) { @Override

    public void migrate(SupportSQLiteDatabase database) { database.execSQL( "ALTER TABLE Invoices ADD COLUMN `new_field` INTEGER NOT NULL DEFAULT 0" ); } };
  95. static final Migration MIGRATION_4_5 = new Migration(4, 5) { @Override

    public void migrate(SupportSQLiteDatabase database) { database.execSQL( "ALTER TABLE Invoices ADD COLUMN `new_field` INTEGER NOT NULL DEFAULT 0" ); } };
  96. static final Migration MIGRATION_4_5 = new Migration(4, 5) { @Override

    public void migrate(SupportSQLiteDatabase database) { database.execSQL( "ALTER TABLE Invoices ADD COLUMN `new_field` INTEGER NOT NULL DEFAULT 0" ); } }; @Provides @Singleton FaktoriaDatabase provideDatabase(@NonNull final Context context) { return Room .databaseBuilder( context.getApplicationContext(), FaktoriaDatabase.class, "database-name" ) .build(); }
  97. static final Migration MIGRATION_4_5 = new Migration(4, 5) { @Override

    public void migrate(SupportSQLiteDatabase database) { database.execSQL( "ALTER TABLE Invoices ADD COLUMN `new_field` INTEGER NOT NULL DEFAULT 0" ); } }; @Provides @Singleton FaktoriaDatabase provideDatabase(@NonNull final Context context) { return Room .databaseBuilder( context.getApplicationContext(), FaktoriaDatabase.class, "database-name" ) .addMigrations(MIGRATION_4_5) .build(); }
  98. static final Migration MIGRATION_4_5 = new Migration(4, 5) { @Override

    public void migrate(SupportSQLiteDatabase database) { database.execSQL( "ALTER TABLE Invoices ADD COLUMN `new_field` INTEGER NOT NULL DEFAULT 0" ); } }; @Provides @Singleton FaktoriaDatabase provideDatabase(@NonNull final Context context) { return Room .databaseBuilder( context.getApplicationContext(), FaktoriaDatabase.class, "database-name" ) .addMigrations(MIGRATION_4_5) .build(); }
  99. Setup 1. Don’t turn off schema export ;-) 2. Add

    few things to build.gradle file a. Schema files location for tests b. Schema export for annotation processor c. One or two extra dependencies
  100. @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { private static final String TEST_DB_NAME

    = "test-database"; @Rule public MigrationTestHelper migrationTestHelper = new MigrationTestHelper( InstrumentationRegistry.getInstrumentation(), FaktoriaDatabase.class.getCanonicalName(), new FrameworkSQLiteOpenHelperFactory() ); // ... }
  101. @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { private static final String TEST_DB_NAME

    = "test-database"; @Rule public MigrationTestHelper migrationTestHelper = new MigrationTestHelper( InstrumentationRegistry.getInstrumentation(), FaktoriaDatabase.class.getCanonicalName(), new FrameworkSQLiteOpenHelperFactory() ); // ... }
  102. @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { // … private void insertInvoice(final

    SupportSQLiteDatabase db) { final ContentValues values = new ContentValues(); values.put("id", 1); values.put("invoice_no", "Invoice NO/123"); values.put("funding_end_date", 1); values.put("purchase_date", 1); values.put("sell_date", 1); values.put("maturity_date", 1); values.put("import_date", 1); values.put("correction", false); values.put("contract_id", 1); db.insert("Invoices", SQLiteDatabase.CONFLICT_REPLACE, values); } // … }
  103. @Test public void testMigrationFrom4to5() throws IOException { final SupportSQLiteDatabase db

    = migrationTestHelper.createDatabase(TEST_DB_NAME, 4); insertInvoice(db); }
  104. @Test public void testMigrationFrom4to5() throws IOException { final SupportSQLiteDatabase db

    = migrationTestHelper.createDatabase(TEST_DB_NAME, 4); insertInvoice(db); migrationTestHelper.runMigrationsAndValidate(TEST_DB_NAME, 5, true, MIGRATION_4_5); }
  105. @Test public void testMigrationFrom4to5() throws IOException { final SupportSQLiteDatabase db

    = migrationTestHelper.createDatabase(TEST_DB_NAME, 4); insertInvoice(db); migrationTestHelper.runMigrationsAndValidate(TEST_DB_NAME, 5, true, MIGRATION_4_5); final FaktoriaDatabase database = Room .databaseBuilder(..., FaktoriaDatabase.class, TEST_DB_NAME) .addMigrations(MIGRATION_4_5) .build(); migrationTestHelper.closeWhenFinished(database); }
  106. @Test public void testMigrationFrom4to5() throws IOException { final SupportSQLiteDatabase db

    = migrationTestHelper.createDatabase(TEST_DB_NAME, 4); insertInvoice(db); migrationTestHelper.runMigrationsAndValidate(TEST_DB_NAME, 5, true, MIGRATION_4_5); final FaktoriaDatabase database = Room .databaseBuilder(..., FaktoriaDatabase.class, TEST_DB_NAME) .addMigrations(MIGRATION_4_5) .build(); migrationTestHelper.closeWhenFinished(database); InvoiceData invoiceAfterMigration = database.invoiceDataDao() .getInvoice(1).blockingGet(); }
  107. @Test public void testMigrationFrom4to5() throws IOException { final SupportSQLiteDatabase db

    = migrationTestHelper.createDatabase(TEST_DB_NAME, 4); insertInvoice(db); migrationTestHelper.runMigrationsAndValidate(TEST_DB_NAME, 5, true, MIGRATION_4_5); final FaktoriaDatabase database = Room .databaseBuilder(..., FaktoriaDatabase.class, TEST_DB_NAME) .addMigrations(MIGRATION_4_5) .build(); migrationTestHelper.closeWhenFinished(database); InvoiceData invoiceAfterMigration = database.invoiceDataDao() .getInvoice(1).blockingGet(); assertEquals(4, invoiceAfterMigration.newField); }
  108. @Test public void testMigrationFrom4to5() throws IOException { final SupportSQLiteDatabase db

    = migrationTestHelper.createDatabase(TEST_DB_NAME, 4); insertInvoice(db); migrationTestHelper.runMigrationsAndValidate(TEST_DB_NAME, 5, true, MIGRATION_4_5); final FaktoriaDatabase database = Room .databaseBuilder(..., FaktoriaDatabase.class, TEST_DB_NAME) .addMigrations(MIGRATION_4_5) .build(); migrationTestHelper.closeWhenFinished(database); InvoiceData invoiceAfterMigration = database.invoiceDataDao() .getInvoice(1).blockingGet(); assertEquals(4, invoiceAfterMigration.newField); }
  109. Room TL;DR • just use it, if you were using

    low level APIs • can be attractive for requery.io and greendao users • will definitely improve over time by adding missing features (from requery.io ;-) • … and there is The Florina Factor • https://www.youtube.com/watch?v=QrbhPcbZv0I • https://medium.com/google-developers/7-steps-to-room-27a5fe5f99b2 • https://medium.com/google-developers/room-rxjava-acb0cd4f3757 • https://github.com/googlesamples/android-architecture-components