8. ViewModel
! The Lifecycle Library’s ViewModel class
! android.arch.lifecycle -> androidx.lifecycle
! Provides data for UI components and survive configuration changes.
9. ViewModel can help
! Avoiding memory leaks
! Solving common Android lifecycle challenges
10. ViewModel can help
! Avoiding memory leaks
! Solving common Android lifecycle challenges
! Share data between fragments
11. class UserViewModel : ViewModel() {
val name = MutableLiveData<String>()
}
class UserFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
val userViewModel =
ViewModelProviders.of(getActivity()).get(UserViewModel::class.java)
loginButton.setOnClickListener({ item -> userViewModel.name = item })
}
}
class UserProfileFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
val userViewModel =
ViewModelProviders.of(getActivity()).get(UserViewModel::class.java)
userViewModel.name.observe(this, { name ->
// Update the UI.
})
}
}
12. ViewModel can help
! Avoiding memory leaks
! Solving common Android lifecycle challenges
! Share data between fragments
! Design good software (SRP, testing etc)
15. Data Loading
Drawing UI
Components
Processing Data
Handling all UI
Interactions
Saving and
restoring the UI
Activity
Activity
Single Responsibility Principle
https://stackify.com/solid-design-principles
17. Activity
Drawing UI
Receiving User Interactions
ViewModel
Hold UI Data
Repository
API for saving and loading app data
Presenter
Process data for UI
User Case
full fledged clean architecture
And more…
as Android Developer
as Java Developer
18. Activity
Drawing UI
Receiving User Interactions
ViewModel
Hold UI Data
Repository
API for saving and loading app data
Presenter
Process data for UI
User Case
full fledged clean architecture
And more…
as Android Developer
as Java Developer
Provides
APIs
Testable
Code
19. Reactive UI
! ViewModel
! LiveData
! Data Binding
! Android Studio 3.1 +
! Support for ViewModel and LiveData
! setLifecycleOwner(LifecycleOwner)
20. ViewModel could do
! Handle configuration changes
! Replace onSaveInstanceState()
47. @Override
public final Object onRetainNonConfigurationInstance() {
···
Object custom = onRetainCustomNonConfigurationInstance();
FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
if (fragments == null && mViewModelStore == null && custom == null) {
return null;
}
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.custom = custom;
nci.viewModelStore = mViewModelStore;
nci.fragments = fragments;
return nci;
}
FragmentActivity Retain All fragment state
48. FragmentActivity Restore All fragment state
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
···
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
mViewModelStore = nc.viewModelStore;
}
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
···
}
49. FragmentActivity Restore All fragment state
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
···
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
mViewModelStore = nc.viewModelStore;
}
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
···
}
50. Restore All fragment state
public void restoreAllState(Parcelable state, FragmentManagerNonConfig
nonConfig) {
mHost.mFragmentManager.restoreAllState(state, nonConfig);
}
FragmentController
51. Restore All fragment state
FragmentController
public void restoreAllState(Parcelable state, FragmentManagerNonConfig
nonConfig) {
mHost.mFragmentManager.restoreAllState(state, nonConfig);
}
52. FragmentManager
void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
···
if (nonConfig != null) {
List<Fragment> nonConfigFragments = nonConfig.getFragments();
childNonConfigs = nonConfig.getChildNonConfigs();
viewModelStores = nonConfig.getViewModelStores();
···
mActive = new SparseArray<>(fms.mActive.length);
for (int i=0; i<fms.mActive.length; i++) {
FragmentState fs = fms.mActive[i];
if (fs != null) {
FragmentManagerNonConfig childNonConfig = null;
if (childNonConfigs != null && i < childNonConfigs.size()) {
childNonConfig = childNonConfigs.get(i);
}
ViewModelStore viewModelStore = null;
if (viewModelStores != null && i < viewModelStores.size()) {
viewModelStore = viewModelStores.get(i);
}
Fragment f = fs.instantiate(mHost, mContainer, mParent,
childNonConfig, viewModelStore);
Restore All fragment state
55. ViewModel could do
! Handle configuration changes? Yes!
! Replace onSaveInstanceState()?
56. ViewModel could do
! Handle configuration changes? Yes!
! Replace onSaveInstanceState()? No!
57. Use ViewModel with onSaveInstanceState
• Survives configuration changes
• Hold lots of data
• Survives configuration changes and process death
• Requires serialization
• Hold small amount of data (less than 50k of data)
All data for Activity UI
Data to reload Activity data
in emergency
ViewModel onSaveInstanceState
58. Use ViewModel with onSaveInstanceState
• Survives configuration changes
• Hold lots of data
• Survives configuration changes and process death
• Requires serialization
• Hold small amount of data (less than 50k of data)
All the user’s data:
User id, first name, last name, birthday,
address, profile images…
User id
ViewModel onSaveInstanceState
63. Wiki & Docs
How to use Search feature
• Handling network errors
• ERROR CODE : 1003
• Do something!
•
•
64. Wiki & Docs
How to use Search feature
• Handling network errors
• ERROR CODE : 1003
• Do something!
•
•
Hard to keep document the latest,
whenever requirement changes
65. Why writing Unit tests
! Think of unit tests as documentation for future developers
! Help developers to refactor safely
74. @Test
fun givenErrorCode1003_whenSearch_shouldHandleError() {
···
val mockObserver = mock<Observer<Boolean>>()
viewModel.loading.observeForever(mockObserver)
viewModel.search(INVALID_QUERY)
verify(mockObserver).onChanged(false)
reset(mockObserver)
// For making sure interaction(s) never happened on mock
verifyNoMoreInteractions(mockObserver)
verify(mockObserver, never()).onChanged(any())
// For finding redundant invocations
verifyZeroInteractions(mockData1, mockData2)
}