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

A Dive Into Android Architecture Components

A Dive Into Android Architecture Components

In this talk we will cover some problems you might face as a developer, and how the Android Architecture Components can help you solve these.

Christian Göllner

August 24, 2017
Tweet

Other Decks in Technology

Transcript

  1. Architecture Components • Lifecycle • Lets you build lifecycle-aware components

    • LiveData • Provides Observable data • ViewModel • Store and manage UI-related data so that the data survives configuration changes such as screen rotations. • Room • Abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.
  2. Architecture Components • Lifecycle • Lets you build lifecycle-aware components

    • LiveData • Provides Observable data • ViewModel • Store and manage UI-related data so that the data survives configuration changes such as screen rotations. • Room • Abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.
  3. Lifecycle public class MainActivity extends AppCompatActivity { private LocationListener locationListener;

    @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); locationListener = new LocationListener(this); } @Override protected void onStart() { super.onStart(); locationListener.start(); } @Override protected void onStop() { locationListener.stop(); super.onStop(); } }
  4. Lifecycle public class MainActivity extends AppCompatActivity { private LocationListener locationListener;

    @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); locationListener = new LocationListener(this); } @Override protected void onStart() { super.onStart(); locationListener.start(); } @Override protected void onStop() { // OOPS locationListener.stop(); super.onStop(); } } • Every Activity/Fragment that wants to use the LocationListener will have to correctly call it on onStart/onStop • Easy to forget/break • Ideally the LocationListener component could handle all this on its own
  5. Lifecycle public class LocationListener implements LifecycleObserver { void start() {

    // start listening for updates } void stop() { // stop listening for updates } Allows us to observe lifecycle events
  6. Lifecycle public class LocationListener implements LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_START) void start()

    { // start listening for updates } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) void stop() { // stop listening for updates } Lifecycle events we want to consume
  7. Lifecycle public class MainActivity extends LifecycleActivity { private LocationListener locationListener;

    @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); locationListener = new LocationListener(this); getLifecycle().addObserver(locationListener); } } Lifecycle we are observing
  8. Lifecycle public class LifecycleActivity extends FragmentActivity implements LifecycleRegistryOwner { private

    final LifecycleRegistry mRegistry = new LifecycleRegistry(this); @Override public LifecycleRegistry getLifecycle() { return mRegistry; } } android.support.v4.app FragmentActivity/Fragment only
  9. Lifecycle public class LifecycleAppCompatActivity extends AppCompatActivity implements LifecycleRegistryOwner { private

    final LifecycleRegistry mRegistry = new LifecycleRegistry(this); @Override public LifecycleRegistry getLifecycle() { return mRegistry; } } LifecycleRegistry works automatically with support FragmentActivity/Fragments only
  10. Lifecycle public class LifecycleFrameworkActivity extends Activity implements LifecycleRegistryOwner { private

    final LifecycleRegistry mRegistry = new LifecycleRegistry(this); @Override public LifecycleRegistry getLifecycle() { return mRegistry; } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE); } @Override protected void onDestroy() { mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY); super.onDestroy(); } } Non support FragmentActivities/Fragments can implement their own lifecycle callbacks
  11. Lifecycle • Use Lifecycle + Lifecycle observer if you have

    components that are tied to lifecycle events • Removes responsibility from calling Activities and Fragments • Allows your components to automatically respond to lifecycle events • Be careful with LifecycleActivity • It doesn’t extend from AppCompatActivity • If using your own components you may need to implement your own LifecycleRegistry and create your own lifecycle events
  12. Architecture Components • Lifecycle • Lets you build lifecycle-aware components

    • LiveData • Provides Observable data • ViewModel • Store and manage UI-related data so that the data survives configuration changes such as screen rotations. • Room • Abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.
  13. LiveData Problem example: • Load data asynchronously • If the

    app is closed or in the background, don’t process results
  14. LiveData Problem example: • Load data asynchronously • If the

    app is closed or in the background, don’t process results • When the app comes back, update UI with the latest result without performing new request
  15. LiveData – Example using RxJava public class MainActivity extends Activity

    { private UserService userService; private final CompositeDisposable disposables = new CompositeDisposable(); @Override protected void onStart() { super.onStart(); disposables.add(userService.getUser().subscribe(this::onUserChanges)); } @Override protected void onStop() { disposables.clear(); super.onStop(); } }
  16. LiveData When Trello first started using RxJava, we were dismayed

    with how easy it was to leak memory when using it. Seemingly any Subscription you setup would leak unless you explicitly cleared it. As such, we were constantly juggling Subscriptions and unsubscribing when we were done using them. Dan Lew. Author of RxLifecycle. http://blog.danlew.net/2017/08/02/why-not-rxlifecycle/
  17. LiveData – Ideal scenario public class MainActivity extends Activity {

    private UserService userService; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); userService.getUser().subscribe(this::onUserChanged); } private void onUserChanged(User user) { // do something with user } }
  18. LiveData @Singleton public class UserService { private final Subject<User> userSubject

    = BehaviorSubject.create(); public void setUser(User user) { userSubject.onNext(user); } public Observable<User> getUser() { return userSubject; } }
  19. LiveData @Singleton public class UserService { private final MutableLiveData<User> userData

    = new MutableLiveData<User>(); public void setUser(User user) { userData.setValue(user); } public LiveData<User> getUser() { return userData; } }
  20. LiveData – Ideal scenario achieved public class MainActivity extends LifecycleActivity

    { private UserService userService; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); userService.getUser().observe(this, this::onUserChanges); } private void onUserChanges(User user) { // do something with user } } • No need to worry about subscriptions • If the Lifecycle is not in an active state, the observer isn’t called • If the Lifecycle is destroyed, the observer is removed automatically
  21. LiveData Problem: • I’m using an asynchronous API/SDK, and I

    want to turn it into an observable stream
  22. LiveData – async APIs into Observables return Observable.create(new Observable.OnSubscribe<SensorEvent>() {

    @Override public void call(final Subscriber<? super SensorEvent> subscriber) { final Sensor sensor = sensorManager.getDefaultSensor(sensorType); final SensorEventListener sensorEventListener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { if (!subscriber.isUnsubscribed()) { subscriber.onNext(event); } } }; sensorManager.registerListener(sensorEventListener, sensor, samplingPeriodUs); subscriber.add(new Subscription() { @Override public void unsubscribe() { sensorManager.unregisterListener(sensorEventListener); isUnsubscribed = true; } @Override public boolean isUnsubscribed() { return isUnsubscribed; } } } }
  23. LiveData – async APIs into Observables return Observable.create(new Observable.OnSubscribe<SensorEvent>() {

    @Override public void call(final Subscriber<? super SensorEvent> subscriber) { final Sensor sensor = sensorManager.getDefaultSensor(sensorType); final SensorEventListener sensorEventListener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { if (!subscriber.isUnsubscribed()) { subscriber.onNext(event); } } }; sensorManager.registerListener(sensorEventListener, sensor, samplingPeriodUs); subscriber.add(new Subscription() { @Override public void unsubscribe() { sensorManager.unregisterListener(sensorEventListener); isUnsubscribed = true; } @Override public boolean isUnsubscribed() { return isUnsubscribed; } } } } • Difficult to read code • Lots of boilerplate for subscription management
  24. LiveData public class LiveSensorData extends LiveData<SensorEvent> { @Override protected void

    onActive() { super.onActive(); sensorManager.registerListener(sensorEventListener, sensor, delay); } @Override protected void onInactive() { sensorManager.unregisterListener(sensorEventListener); super.onInactive(); } private SensorEventListener sensorEventListener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { setValue(event); } }; } • onActive() -> first active observer
  25. LiveData public class LiveSensorData extends LiveData<SensorEvent> { @Override protected void

    onActive() { super.onActive(); sensorManager.registerListener(sensorEventListener, sensor, delay); } @Override protected void onInactive() { sensorManager.unregisterListener(sensorEventListener); super.onInactive(); } private SensorEventListener sensorEventListener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { setValue(event); } }; } • onActive() -> first active observer • onInactive() -> no more active observers
  26. LiveData public class LiveSensorData extends LiveData<SensorEvent> { @Override protected void

    onActive() { super.onActive(); sensorManager.registerListener(sensorEventListener, sensor, delay); } @Override protected void onInactive() { sensorManager.unregisterListener(sensorEventListener); super.onInactive(); } private SensorEventListener sensorEventListener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { setValue(event); } }; } • onActive() -> first active observer • onInactive() -> no more active observers • setValue() -> notify active observers
  27. LiveData No memory leaks - Observers bound to their own

    lifecycle objects No need to worry about unsubscribing - No updates if the observers lifecycle is inactive
  28. LiveData No memory leaks - Observers bound to their own

    lifecycle objects No need to worry about unsubscribing - No updates if the observers lifecycle is inactive Up-to-date data - Receive the latest data when subscribing
  29. LiveData No memory leaks - Observers bound to their own

    lifecycle objects No need to worry about unsubscribing - No updates if the observers lifecycle is inactive Up-to-date data - Receive the latest data when subscribing Better configuration changes - Receive last available data during recreation
  30. Architecture Components • Lifecycle • Lets you build lifecycle-aware components

    • LiveData • Provides Observable data • ViewModel • Store and manage UI-related data so that the data survives configuration changes such as screen rotations. • Room • Abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.
  31. ViewModel Consider a simple task: • Load some data •

    Retain the data through configuration changes
  32. ViewModel public class MainActivity extends AppCompatActivity { private MyData myData;

    @Override protected void onCreate(@NonNull Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); if (myData == null) { myData = this.createMyData(); }
  33. ViewModel • onSaveInstanceState • Size limits • Data has to

    be Parcelable or primitive (int, String, boolean, etc) • onRetainCustomNonConfigurationInstance • Can save a single object • Only available for Activities • Fragment.setRetainInstance • Consumer becomes responsible for managing and creating fragments • Cant use with nested fragments • If holding onto Context, Views, etc. this can introduce memory leaks
  34. ViewModel public class MyViewModel extends ViewModel { private final MyData

    myData; public MyViewModel() { this.myData = createMyData(); } public MyData getMyData() { return myData; }
  35. ViewModel public class MainActivity extends AppCompatActivity { private MyData myData;

    @Override protected void onCreate(@NonNull Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class); myData = viewModel.getMyData(); } ViewModelProvider creates and retains your ViewModel
  36. ViewModel public class MainActivity extends AppCompatActivity { private MyData myData;

    @Override protected void onCreate(@NonNull Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class); myData = viewModel.getMyData(); } ViewModelProvider expects a FragmentActivity or Fragment
  37. ViewModel public class FragmentA extends Fragment { private SharedViewModel viewModel;

    @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class); } private void updateMessage(String message) { viewModel.setMessage(message); } } public class FragmentB extends LifecycleFragment { private SharedViewModel viewModel; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class); viewModel.getMessage().observe(this, this::onMessageUpdated); } private void onMessageUpdated(String newMessage) { } } When using Fragments we can use the Activity as an owner. This makes it easy to share viewmodels between Fragments
  38. ViewModel public class FragmentA extends Fragment { private SharedViewModel viewModel;

    @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class); } private void updateMessage(String message) { viewModel.setMessage(message); } } public class FragmentB extends LifecycleFragment { private SharedViewModel viewModel; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class); viewModel.getMessage().observe(this, this::onMessageUpdated); } private void onMessageUpdated(String newMessage) { } } The same viewmodel is used by both Fragments, making it easy to share information across Fragments
  39. ViewModel public class MyViewModel extends ViewModel { private final MyData

    myData; public MyViewModel(@NonNull DataCreator dataCreator) { this.myData = dataCreator.createMyData(); } public MyData getMyData() { return myData; } } When using a non-empty constructor we need to supply a ViewModel factory
  40. ViewModel public class MainActivity extends AppCompatActivity { private ComplexData complexData;

    private ViewModelFactory viewModelFactory; @Override protected void onCreate(@NonNull Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); MyViewModel viewModel = ViewModelProviders.of(this, viewModelFactory).get(MyViewModel.class); complexData= viewModel.getComplexData(); } Factory is supplied to the ViewModelProvider
  41. ViewModel public class ViewModelFactory implements ViewModelProvider.Factory { // more code

    needed to initialize required dependencies private DataCreator dataCreator; @Override public <T extends ViewModel> T create(Class<T> modelClass) { if (modelClass.isAssignableFrom(MyViewModel.class)) { return (T) new MyViewModel(dataCreator); } throw new IllegalArgumentException("unknown model class " + modelClass); } } Factory responsible for creating a new ViewModel instance
  42. ViewModel public class ContextViewModel extends AndroidViewModel { public ContextViewModel(Application application)

    { super(application); } } Use AndroidViewModel if you need access to a Context object. This supplies the application context – no factory required
  43. ViewModel • Makes it easy to retain information across configuration

    changes • Makes it easy to share information across Fragments • Best practices should still apply with critical data • If owning Activity is killed by the OS the ViewModel and its data will be lost. • onSaveInstanceState should used to keep critical information. • To save larger amounts of information you should consider other options. • E.g. Database storage
  44. Architecture Components • Lifecycle • Lets you build lifecycle-aware components

    • LiveData • Provides Observable data • ViewModel • Store and manage UI-related data so that the data survives configuration changes such as screen rotations. • Room • Abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.
  45. Room public class FeedReaderContract { public FeedReaderContract() {} public static

    abstract class FeedEntry implements BaseColumns { public static final String TABLE_NAME = "entry"; public static final String COLUMN_NAME_ENTRY_ID = "entryid"; public static final String COLUMN_NAME_TITLE = "title"; public static final String COLUMN_NAME_SUBTITLE = "subtitle"; } public static final String TEXT_TYPE = " TEXT"; public static final String COMMA_SEP = ","; public static final String SQL_CREATE_ENTRIES = "CREATE TABLE " + FeedEntry.TABLE_NAME + " (" + FeedEntry._ID + "INTEGER PRIMARY KEY," + FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP + FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP + FeedEntry.COLUMN_NAME_SUBTITLE + TEXT_TYPE + ")"; public static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME; }
  46. Room public class FeedReaderContract { public FeedReaderContract() {} public static

    abstract class FeedEntry implements BaseColumns { public static final String TABLE_NAME = "entry"; public static final String COLUMN_NAME_ENTRY_ID = "entryid"; public static final String COLUMN_NAME_TITLE = "title"; public static final String COLUMN_NAME_SUBTITLE = "subtitle"; } public static final String TEXT_TYPE = " TEXT"; public static final String COMMA_SEP = ","; public static final String SQL_CREATE_ENTRIES = "CREATE TABLE " + FeedEntry.TABLE_NAME + " (" + FeedEntry._ID + "INTEGER PRIMARY KEY," + FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP + FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP + FeedEntry.COLUMN_NAME_SUBTITLE + TEXT_TYPE + ")"; public static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME; } @Entity public class FeedEntry { @PrimaryKey private int uid; private String entryid; private String title; private String subtitle; }
  47. Room public class FeedReaderContract { public FeedReaderContract() {} public static

    abstract class FeedEntry implements BaseColumns { public static final String TABLE_NAME = "entry"; public static final String COLUMN_NAME_ENTRY_ID = "entryid"; public static final String COLUMN_NAME_TITLE = "title"; public static final String COLUMN_NAME_SUBTITLE = "subtitle"; } public static final String TEXT_TYPE = " TEXT"; public static final String COMMA_SEP = ","; public static final String SQL_CREATE_ENTRIES = "CREATE TABLE " + FeedEntry.TABLE_NAME + " (" + FeedEntry._ID + "INTEGER PRIMARY KEY," + FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP + FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP + FeedEntry.COLUMN_NAME_SUBTITLE + TEXT_TYPE + ")"; public static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME; } @Entity public class FeedEntry { @PrimaryKey private int uid; private String entryid; private String title; private String subtitle; }
  48. Room private FeedEntry getEntryById(String id){ Cursor c = db.query( FeedEntry.TABLE_NAME,

    // Table name null, // The columns for return ENTRY_ID + “=” + id, // The columns for WHERE clause null, // The values for WHERE clause null, // Don't group the rows null, // Don't filter by row group null // The sort order ); c.moveToFirst(); int itemId = c.getInt(c.getColumnIndexOrThrow(ENTRY_ID)); String title = c.getString(c.getColumnIndexOrThrow(ENTRY_TITLE); String subtitle = c.getString(c.getColumnIndexOrThrow(ENTRY_SUBTITLE); c.close(); return new FeedEntry(itemId, title, subtitle); }
  49. Room private FeedEntry getEntryById(String id){ Cursor c = db.query( FeedEntry.TABLE_NAME,

    // Table name null, // The columns for return ENTRY_ID + “=” + id, // The columns for WHERE clause null, // The values for WHERE clause null, // Don't group the rows null, // Don't filter by row group null // The sort order ); c.moveToFirst(); int itemId = c.getInt(c.getColumnIndexOrThrow(ENTRY_ID)); String title = c.getString(c.getColumnIndexOrThrow(ENTRY_TITLE); String subtitle = c.getString(c.getColumnIndexOrThrow(ENTRY_SUBTITLE); c.close(); return new FeedEntry(itemId, title, subtitle); } @Dao public interface FeedEntryDao { @Query("SELECT * FROM entry WHERE id=:id") FeedEntry findById(String id); }
  50. Room private FeedEntry getEntryById(String id) { Cursor c = db.query(

    FeedEntry.TABLE_NAME, // Table name null, // The columns for return ENTRY_ID + “=” + id, // The columns for WHERE clause null, // The values for WHERE clause null, // Don't group the rows null, // Don't filter by row group null // The sort order ); c.moveToFirst(); int itemId = c.getInt(c.getColumnIndexOrThrow(ENTRY_ID)); String title = c.getString(c.getColumnIndexOrThrow(ENTRY_TITLE); String subtitle = c.getString(c.getColumnIndexOrThrow(ENTRY_SUBTITLE); c.close(); return new FeedEntry(itemId, title, subtitle); } @Dao public interface FeedEntryDao { @Query("SELECT * FROM entry WHERE id=:id") FeedEntry findById(String id); }
  51. Room – Compatible with Rx and LiveData @Dao public interface

    FeedEntryDao { @Query("SELECT * FROM entry") List<FeedEntry> getAll(); @Query("SELECT * FROM entry") Flowable<List<FeedEntry>> getAll(); @Query("SELECT * FROM entry") LiveData<List<FeedEntry>> getAll(); @Query("SELECT * FROM entry") Cursor getAll(); //You can still use Cursor }
  52. Room public class FeedReaderDbHelper extends SQLiteOpenHelper { public static final

    int DATABASE_VERSION = 1; public static final String DATABASE_NAME = "FeedReader.db"; public FeedReaderDbHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(FeedReaderContract.SQL_CREATE_ENTRIES); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(FeedReaderContract.SQL_DELETE_ENTRIES); onCreate(db); } @Override public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { onUpgrade(db, oldVersion, newVersion); } }
  53. Room public class FeedReaderDbHelper extends SQLiteOpenHelper { public static final

    int DATABASE_VERSION = 1; public static final String DATABASE_NAME = "FeedReader.db"; public FeedReaderDbHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(FeedReaderContract.SQL_CREATE_ENTRIES); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(FeedReaderContract.SQL_DELETE_ENTRIES); onCreate(db); } @Override public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { onUpgrade(db, oldVersion, newVersion); } } @Database(entities = { FeedEntry.class }, version = 1) public abstract class FeedEntryDb extends RoomDatabase { public abstract FeedEntryDao feedEntryDao(); }
  54. Room public class FeedReaderDbHelper extends SQLiteOpenHelper { public static final

    int DATABASE_VERSION = 1; public static final String DATABASE_NAME = "FeedReader.db"; public FeedReaderDbHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(FeedReaderContract.SQL_CREATE_ENTRIES); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(FeedReaderContract.SQL_DELETE_ENTRIES); onCreate(db); } @Override public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { onUpgrade(db, oldVersion, newVersion); } } @Database(entities = { FeedEntry.class }, version = 1) public abstract class FeedEntryDb extends RoomDatabase { public abstract FeedEntryDao feedEntryDao(); } FeedEntryDb db = Room.databaseBuilder( application, FeedEntryDb.class, “FeedReader.db” ).build()
  55. Room public class FeedEntrActivity extends LifecycleActivity { FeedEntryDb db; @Override

    protected void onCreate(@NonNull Bundle savedInstanceState) { super.onCreate(savedInstanceState); db.feedEntryDao().findById(myId).observe(this, this::onFeedEntry); } private void onFeedEntry(FeedEntry feedEntry) { } }
  56. Room – Inserting data private void insertData(FeedEntry entry) { ContentValues

    values = new ContentValues(); values.put(FeedEntry.COLUMN_NAME_ENTRY_ID, entry.getId()); values.put(FeedEntry.COLUMN_NAME_SUBTITLE, entry.getTitle()); values.put(FeedEntry.COLUMN_NAME_TITLE, entry.getSubtitle()); db.insert(FeedEntry.TABLE_NAME, null, values); } //If you add a new column to a table, you have to remember to update this method and //add the new field
  57. Room – Inserting data private void insertData(FeedEntry entry) { ContentValues

    values = new ContentValues(); values.put(FeedEntry.COLUMN_NAME_ENTRY_ID, entry.getId()); values.put(FeedEntry.COLUMN_NAME_SUBTITLE, entry.getTitle()); values.put(FeedEntry.COLUMN_NAME_TITLE, entry.getSubtitle()); db.insert(FeedEntry.TABLE_NAME, null, values); } @Dao public interface FeedEntryDao { @Insert void insertAll(FeedEntry... feedEntries); }
  58. Room - Threading • By default Room does not allow

    accessing the database on the main thread
  59. Room - Threading • By default Room does not allow

    accessing the database on the main thread • You can allow it with allowMainThreadQueries() on the builder
  60. Room - Threading • By default Room does not allow

    accessing the database on the main thread • You can allow it with allowMainThreadQueries() on the builder • Observable queries (LiveData & Flowable) will run on a background thread by default
  61. Room - Threading • By default Room does not allow

    accessing the database on the main thread • You can allow it with allowMainThreadQueries() on the builder • Observable queries (LiveData & Flowable) will run on a background thread by default • All other operations have to be manually accessed on a background thread
  62. Room - Summary • Massive boilerplate reduction • Fluent database

    access • No more manual conversion between Java and Cursor/ContentValue
  63. Room - Summary • Massive boilerplate reduction • Fluent database

    access • No more manual conversion between Java and Cursor/ContentValue • Compile time error checks • Syntax mistakes • Wrong table names, column names
  64. Room - Summary • Massive boilerplate reduction • Fluent database

    access • No more manual conversion between Java and Cursor/ContentValue • Compile time error checks • Syntax mistakes • Wrong table names, column names • Compatibility with RxJava and LiveData
  65. Room - Summary • Massive boilerplate reduction • Fluent database

    access • No more manual conversion between Java and Cursor/ContentValue • Compile time error checks • Syntax mistakes • Wrong table names, column names • Compatibility with RxJava and LiveData • Support for testing • In memory DB • Migration test helper