• 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.
• 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.
@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
final LifecycleRegistry mRegistry = new LifecycleRegistry(this); @Override public LifecycleRegistry getLifecycle() { return mRegistry; } } android.support.v4.app FragmentActivity/Fragment only
final LifecycleRegistry mRegistry = new LifecycleRegistry(this); @Override public LifecycleRegistry getLifecycle() { return mRegistry; } } LifecycleRegistry works automatically with support FragmentActivity/Fragments only
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
• 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.
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/
{ 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
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
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
• 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.
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
@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
@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
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
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
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
• 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.
// 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); }
// 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); }
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); }
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 }
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
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
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
access • No more manual conversion between Java and Cursor/ContentValue • Compile time error checks • Syntax mistakes • Wrong table names, column names
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
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