Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
It’s All In The
Implementation
Details
@FMuntenescu
upday
MVVM
M
V
V M
odel
iew
iew odel
M
V
V M
odel
iew
iew odel
(With RxJava)
DataModelView ViewModel
DataModelView ViewModel
Network
Database
SharedPreferences
DataModelView ViewModel
Network
Database
SharedPreferences
Observable<List<Article>>
DataModelView ViewModel
Network
Database
SharedPreferences
Observable<ArticlesUiModel>
Observable<List<Article>>
JUnit testable
DataModelView ViewModel
Network
Database
SharedPreferences
Android Classes?
Static Methods?
Providers
Android Classes?
Static Methods?
context.getString(id);
context.getString(id);
String getString(@StringRes int id) {
return
}
public class ResourceProvider {
Context context;
ResourceProvider(Context context) {
this.context = context;
}
context.get...
FirebaseRemoteConfig.getInstance().getString(key)
FirebaseRemoteConfig.getInstance()
public class RemoteConfigProvider {
FirebaseRemoteConfig remoteConfig;
RemoteConfigProv...
FirebaseRemoteConfig.getInstance()
public class RemoteConfigProvider {
FirebaseRemoteConfig remoteConfig;
RemoteConfigProv...
DataModel
DataModelView ViewModel
Network
Database
SharedPreferences
News	
Articles
News	
Articles
Network
Database
SharedPreferences
GET
.../articles
Database
SharedPreferences
GET
.../articles
TABLE
articles
SharedPreferences
GET
.../articles
TABLE
articles
SharedPreferences
Articles
DataModel
GET
.../articles
TABLE
articles
SharedPreferences
Articles
DataModel
ViewModelX
ViewModelY
public class ArticlesDataModel {
RemoteDataSource remoteDataSource;
LocalDataSource localDataSource;
Observable<List<Article>> getOrFetchTopNewsArticles() {
}
public class ArticlesDataModel {
RemoteDataSource remoteDataSour...
public class ArticlesDataModel {
RemoteDataSource remoteDataSource;
LocalDataSource localDataSource;
return localDataSourc...
public class ArticlesDataModel {
RemoteDataSource remoteDataSource;
LocalDataSource localDataSource;
.flatMap(articles -> ...
public class ArticlesDataModel {
RemoteDataSource remoteDataSource;
LocalDataSource localDataSource;
.flatMap(articles -> ...
public class ArticlesDataModel {
RemoteDataSource remoteDataSource;
LocalDataSource localDataSource;
Observable<List<Artic...
public class ArticlesDataModel {
RemoteDataSource remoteDataSource;
LocalDataSource localDataSource;
return localDataSourc...
public class ArticlesDataModel {
RemoteDataSource remoteDataSource;
LocalDataSource localDataSource;
return localDataSourc...
public class ArticlesDataModel {
RemoteDataSource remoteDataSource;
LocalDataSource localDataSource;
return localDataSourc...
HomeView
Article Teaser	
View
Article Teaser	
View
ArticleTeaser
ViewModel
Articles
DataModel
Article Teaser	
View
ArticleTeaser
ViewModel
Database
Articles
DataModel
Article Teaser	
View
ArticleTeaser
ViewModel
Database
Articles
DataModel
Article Teaser	
View
ArticleTeaser
ViewModel
New	Article
Articles
DataModel
Database
Article Teaser	
View
ArticleTeaser
ViewModel
New	Article
getOrfetchTopNewsArticles
Database
Articles
DataModel
Article Teaser	
View
ArticleTeaser
ViewModel
New	Article
getTopNewsArticle
Article Teaser	
View
Database
Articles
DataModel
ArticleTeaser
ViewModel
New	Article
DataModel uses the Repository pattern
One DataModel per business model
DataModel drives the data flow
View
ViewModel
Article Teaser	
View
<LinearLayout>
<…ArticleTeaserView
…/>
</LinearLayout>
class ArticleTeaserView extends View {
@Inject
ArticleTeaserViewModel viewModel;
class ArticleTeaserView extends View {
public ArticleTeaserView(Context context) {
…
onInject();
}
@Inject
ArticleTeaserVi...
class ArticleTeaserView extends View {
public ArticleTeaserView(Context context) {
…
onInject();
}
@Inject
ArticleTeaserVi...
class ArticleTeaserView extends View {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
bind();...
class ArticleTeaserView extends View {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
bind();...
class ArticleTeaserView extends View {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
bind();...
class ArticleTeaserView extends View {
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
bind();...
class ArticleTeaserView extends View {
@Override
protected void onDetachedFromWindow() {
}
unbind();
private void unbind()...
class ArticleTeaserView extends View {
@Override
protected void onDetachedFromWindow() {
}
unbind();
subscription.clear();...
private void unbind() {
}
subscription.clear();
viewModel.dispose();
class ArticleTeaserView extends View {
@Override
prot...
TopNewsStream
View
Fragment
public class TopNewsStreamView extends Fragment {
@Inject
TopNewsStreamViewModel viewModel;
public class TopNewsStreamView extends Fragment {
@Inject
TopNewsStreamViewModel viewModel;
@Override
public void onActivi...
public class TopNewsStreamView extends Fragment {
@Inject
TopNewsStreamViewModel viewModel;
@Override
public void onResume...
Lifecycle of the ViewModel depends on the lifecycle of the View
Only the View has a reference to the corresponding ViewMod...
Model-View-ViewModel
RecyclerView
Adapter
ViewHolder
DataSet
Adapter
ViewHolder
DataSet
onCreateViewHolder
onBindViewHolder
Adapter
ViewHolder
DataSet
Adapter
ViewHolder
UiModels
TopNewsStream
View
TopNewsAdapter UiModels
ViewHolder
TopNewsStream
View
TopNewsAdapter UiModels
TopNewsViewHolder
TopNewsStream
View
TopNewsStream
View
TopNewsStream
ViewModel
public class TopNewsStreamViewModel {
Observable<TopNewsUiModel> getTopNewsUiModel() {
}
public class TopNewsStreamViewModel {
Observable<TopNewsUiModel> getTopNewsUiModel() {
}
@AutoValue
abstract class TopNews...
public class TopNewsStreamView extends Fragment {
@Inject
TopNewsStreamViewModel viewModel;
@Override
public void onResume() {
…
bind();
}
public class TopNewsStreamView extends Fragment {
@Inject
TopNewsStreamView...
private void bind() {
subscription.add(
viewModel.getTopNewsUiModel()
.subscribe(uiModel -> setupUiModel(uiModel));
}
publ...
private void setupUiModel(TopNewsUiModel model) {
// update Adapter with the UiModel
}
private void bind() {
subscription....
public class TopNewsAdapter extends
RecyclerView.Adapter<TopNewsViewHolder> {
public class TopNewsAdapter extends
RecyclerView.Adapter<TopNewsViewHolder> {
@Override
public TopNewsViewHolder onCreateV...
Open	Article
Open	Article
Share	Article
Open	Article
Share	Article
Label	should	
disappear	after	5sec
class TopNewsArticleViewModel {
void openArticle(){
…
}
void share(){
…
}
Observable<Boolean> isNewLabelVisible(){
…
}
}
public class TopNewsAdapter extends
RecyclerView.Adapter<TopNewsViewHolder> {
@Override
public TopNewsViewHolder onCreateV...
ViewModel creates and emits the Model for the View
View subscribes to the emissions of the Model
View updates the Recycler...
Open	Article
Open	Article
No	ViewModel
class TopNewsStreamViewModel {
Observable<List<UiModel>> getUiModels() {
}
@AutoValue
abstract class UiModel {
abstract Article article();
class TopNewsStreamViewModel {
Observable<List<UiModel>> g...
@AutoValue
abstract class UiModel {
abstract Article article();
abstract Action0 onClickAction();
class TopNewsStreamViewM...
@AutoValue
abstract class UiModel {
abstract Article article();
abstract Action0 onClickAction();
static UiModel create(Ar...
class TopNewsStreamViewModel {
Observable<List<UiModel>> getUiModels() {
}
class TopNewsStreamViewModel {
Observable<List<UiModel>> getUiModels() {
}
return dataModel.getTopNewsArticles()
.flatMap(...
class TopNewsStreamViewModel {
Observable<List<UiModel>> getUiModels() {
}
return dataModel.getTopNewsArticles()
.flatMap(...
class TopNewsViewHolder extends RecyclerView.ViewHolder{
class TopNewsViewHolder extends RecyclerView.ViewHolder{
TextView title;
…
class TopNewsViewHolder extends RecyclerView.ViewHolder{
TextView title;
…
void bindItem(UiModel uiModel) {
title.setText(...
void bindItem(UiModel uiModel) {
title.setText(uiModel.getTitle());
}
view.setOnClickListener(
v -> uiModel.onClickAction(...
TopNewsAdapter UiModels
TopNewsViewHolder
bindItem(UiModel)
TopNewsAdapter UiModels
TopNewsViewHolder
bindItem(UiModel)
getUiModels()
TopNewsStream
ViewModel
Define an Action in the View’s Model
Bind the Model to the View via the RecyclerView.ViewHolder
Trigger the Action
Action ...
It’s All In The
Implementation
Details
@FMuntenescu
upday
MVVM
https://goo.gl/bIwdmc
MVM - It's all in the (Implementation) Details
Upcoming SlideShare
Loading in …5
×

MVM - It's all in the (Implementation) Details

10,070 views

Published on

At upday, we’ve been successfully using the Model-View-ViewModel pattern together with RxJava for more than a year and a half. After sharing with the community a very high-level overview of what this pattern entails, we decided that it’s time to go deeper into the implementation details.

I will present our way of handling Android classes and other 3rd party dependencies, and how we make sure that our code is as unit-testable as possible. I’ll speak about our decision process on how to split classes, may they be Views, ViewModels or DataModels, and the benefits and drawbacks of these separations.

Any non-”Hello World” app will most likely have two things: multiple Activities/Fragments, of which at least one will contain a ListView/RecyclerView. Then I’ll show how we handle lists: how is the data added to the list, and how and who listens and reacts to list items taps.

Published in: Software
  • Hello! Get Your Professional Job-Winning Resume Here - Check our website! https://vk.cc/818RFv
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

MVM - It's all in the (Implementation) Details

  1. 1. It’s All In The Implementation Details @FMuntenescu upday MVVM
  2. 2. M V V M odel iew iew odel
  3. 3. M V V M odel iew iew odel (With RxJava)
  4. 4. DataModelView ViewModel
  5. 5. DataModelView ViewModel Network Database SharedPreferences
  6. 6. DataModelView ViewModel Network Database SharedPreferences Observable<List<Article>>
  7. 7. DataModelView ViewModel Network Database SharedPreferences Observable<ArticlesUiModel> Observable<List<Article>>
  8. 8. JUnit testable DataModelView ViewModel Network Database SharedPreferences
  9. 9. Android Classes? Static Methods?
  10. 10. Providers Android Classes? Static Methods?
  11. 11. context.getString(id);
  12. 12. context.getString(id); String getString(@StringRes int id) { return }
  13. 13. public class ResourceProvider { Context context; ResourceProvider(Context context) { this.context = context; } context.getString(id); String getString(@StringRes int id) { return }
  14. 14. FirebaseRemoteConfig.getInstance().getString(key)
  15. 15. FirebaseRemoteConfig.getInstance() public class RemoteConfigProvider { FirebaseRemoteConfig remoteConfig; RemoteConfigProvider() { remoteConfig = ; }
  16. 16. FirebaseRemoteConfig.getInstance() public class RemoteConfigProvider { FirebaseRemoteConfig remoteConfig; RemoteConfigProvider() { remoteConfig = ; } String getString(String key) { return remoteConfig.getString(key); }
  17. 17. DataModel
  18. 18. DataModelView ViewModel Network Database SharedPreferences
  19. 19. News Articles
  20. 20. News Articles
  21. 21. Network Database SharedPreferences
  22. 22. GET .../articles Database SharedPreferences
  23. 23. GET .../articles TABLE articles SharedPreferences
  24. 24. GET .../articles TABLE articles SharedPreferences Articles DataModel
  25. 25. GET .../articles TABLE articles SharedPreferences Articles DataModel ViewModelX ViewModelY
  26. 26. public class ArticlesDataModel { RemoteDataSource remoteDataSource; LocalDataSource localDataSource;
  27. 27. Observable<List<Article>> getOrFetchTopNewsArticles() { } public class ArticlesDataModel { RemoteDataSource remoteDataSource; LocalDataSource localDataSource;
  28. 28. public class ArticlesDataModel { RemoteDataSource remoteDataSource; LocalDataSource localDataSource; return localDataSource .getTopNews() Observable<List<Article>> getOrFetchTopNewsArticles() { }
  29. 29. public class ArticlesDataModel { RemoteDataSource remoteDataSource; LocalDataSource localDataSource; .flatMap(articles -> articles.isEmpty() ? remoteDataSource.getTopNews() : Observable.just(articles)); return localDataSource .getTopNews() Observable<List<Article>> getOrFetchTopNewsArticles() { }
  30. 30. public class ArticlesDataModel { RemoteDataSource remoteDataSource; LocalDataSource localDataSource; .flatMap(articles -> articles.isEmpty() ? remoteDataSource.getTopNews() : Observable.just(articles)); .flatMap(getValidArticles()) return localDataSource .getTopNews() Observable<List<Article>> getOrFetchTopNewsArticles() { }
  31. 31. public class ArticlesDataModel { RemoteDataSource remoteDataSource; LocalDataSource localDataSource; Observable<List<Article>> getOrFetchTopNewsArticles() { } return localDataSource .getTopNews() .flatMap(articles -> articles.isEmpty() ? remoteDataSource.getTopNews() : Observable.just(articles)); .flatMap(getValidArticles())
  32. 32. public class ArticlesDataModel { RemoteDataSource remoteDataSource; LocalDataSource localDataSource; return localDataSource .getTopNews() .flatMap(articles -> articles.isEmpty() ? remoteDataSource.getTopNews() : Observable.just(articles)); .flatMap(getValidArticles()) Observable<List<Article>> Observable<List<Article>> getOrFetchTopNewsArticles() { }
  33. 33. public class ArticlesDataModel { RemoteDataSource remoteDataSource; LocalDataSource localDataSource; return localDataSource .getTopNews() .flatMap(articles -> articles.isEmpty() ? remoteDataSource.getTopNews() : Observable.just(articles)); .flatMap(getValidArticles()) .getTopNews() Observable<List<Article>> getOrFetchTopNewsArticles() { }
  34. 34. public class ArticlesDataModel { RemoteDataSource remoteDataSource; LocalDataSource localDataSource; return localDataSource .getTopNews() .flatMap(articles -> articles.isEmpty() ? remoteDataSource.getTopNews() : Observable.just(articles)); .flatMap(getValidArticles()) .getTopNews() Observable<List<Article>> getOrFetchTopNewsArticles() { } getOrFetchTopNewsArticles()
  35. 35. HomeView
  36. 36. Article Teaser View
  37. 37. Article Teaser View ArticleTeaser ViewModel
  38. 38. Articles DataModel Article Teaser View ArticleTeaser ViewModel
  39. 39. Database Articles DataModel Article Teaser View ArticleTeaser ViewModel
  40. 40. Database Articles DataModel Article Teaser View ArticleTeaser ViewModel New Article
  41. 41. Articles DataModel Database Article Teaser View ArticleTeaser ViewModel New Article getOrfetchTopNewsArticles
  42. 42. Database Articles DataModel Article Teaser View ArticleTeaser ViewModel New Article getTopNewsArticle
  43. 43. Article Teaser View Database Articles DataModel ArticleTeaser ViewModel New Article
  44. 44. DataModel uses the Repository pattern One DataModel per business model DataModel drives the data flow
  45. 45. View ViewModel
  46. 46. Article Teaser View <LinearLayout> <…ArticleTeaserView …/> </LinearLayout>
  47. 47. class ArticleTeaserView extends View { @Inject ArticleTeaserViewModel viewModel;
  48. 48. class ArticleTeaserView extends View { public ArticleTeaserView(Context context) { … onInject(); } @Inject ArticleTeaserViewModel viewModel;
  49. 49. class ArticleTeaserView extends View { public ArticleTeaserView(Context context) { … onInject(); } @Inject ArticleTeaserViewModel viewModel; new ArticleTeaserViewModel()
  50. 50. class ArticleTeaserView extends View { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); bind(); }
  51. 51. class ArticleTeaserView extends View { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); bind(); } private void bind() { subscription.add( viewModel.getTopNewsArticle() .subscribeOn(…) .observeOn(…) .subscribe(this::showArticle, error -> Timber.e(error, “Error …”); }
  52. 52. class ArticleTeaserView extends View { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); bind(); } @Override protected void onDetachedFromWindow() { }
  53. 53. class ArticleTeaserView extends View { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); bind(); } @Override protected void onDetachedFromWindow() { } unbind();
  54. 54. class ArticleTeaserView extends View { @Override protected void onDetachedFromWindow() { } unbind(); private void unbind() { }
  55. 55. class ArticleTeaserView extends View { @Override protected void onDetachedFromWindow() { } unbind(); subscription.clear(); private void unbind() { }
  56. 56. private void unbind() { } subscription.clear(); viewModel.dispose(); class ArticleTeaserView extends View { @Override protected void onDetachedFromWindow() { } unbind();
  57. 57. TopNewsStream View Fragment
  58. 58. public class TopNewsStreamView extends Fragment { @Inject TopNewsStreamViewModel viewModel;
  59. 59. public class TopNewsStreamView extends Fragment { @Inject TopNewsStreamViewModel viewModel; @Override public void onActivityCreated(Bundle savedInstanceState) { … onInject(); }
  60. 60. public class TopNewsStreamView extends Fragment { @Inject TopNewsStreamViewModel viewModel; @Override public void onResume() { … bind(); } @Override public void onPause() { … unbind(); }
  61. 61. Lifecycle of the ViewModel depends on the lifecycle of the View Only the View has a reference to the corresponding ViewModel Only other native android classes know about the View
  62. 62. Model-View-ViewModel RecyclerView
  63. 63. Adapter ViewHolder DataSet
  64. 64. Adapter ViewHolder DataSet onCreateViewHolder onBindViewHolder
  65. 65. Adapter ViewHolder DataSet
  66. 66. Adapter ViewHolder UiModels TopNewsStream View
  67. 67. TopNewsAdapter UiModels ViewHolder TopNewsStream View
  68. 68. TopNewsAdapter UiModels TopNewsViewHolder TopNewsStream View
  69. 69. TopNewsStream View TopNewsStream ViewModel
  70. 70. public class TopNewsStreamViewModel { Observable<TopNewsUiModel> getTopNewsUiModel() { }
  71. 71. public class TopNewsStreamViewModel { Observable<TopNewsUiModel> getTopNewsUiModel() { } @AutoValue abstract class TopNewsUiModel { abstract List<UiModel> uiModels(); abstract int position(); }
  72. 72. public class TopNewsStreamView extends Fragment { @Inject TopNewsStreamViewModel viewModel;
  73. 73. @Override public void onResume() { … bind(); } public class TopNewsStreamView extends Fragment { @Inject TopNewsStreamViewModel viewModel;
  74. 74. private void bind() { subscription.add( viewModel.getTopNewsUiModel() .subscribe(uiModel -> setupUiModel(uiModel)); } public class TopNewsStreamView extends Fragment {
  75. 75. private void setupUiModel(TopNewsUiModel model) { // update Adapter with the UiModel } private void bind() { subscription.add( viewModel.getTopNewsUiModel() .subscribe(uiModel -> setupUiModel(uiModel)); } public class TopNewsStreamView extends Fragment {
  76. 76. public class TopNewsAdapter extends RecyclerView.Adapter<TopNewsViewHolder> {
  77. 77. public class TopNewsAdapter extends RecyclerView.Adapter<TopNewsViewHolder> { @Override public TopNewsViewHolder onCreateViewHolder( ViewGroup parent, int viewType) { // create a view holder with a new view // that represents items of the given type }
  78. 78. Open Article
  79. 79. Open Article Share Article
  80. 80. Open Article Share Article Label should disappear after 5sec
  81. 81. class TopNewsArticleViewModel { void openArticle(){ … } void share(){ … } Observable<Boolean> isNewLabelVisible(){ … } }
  82. 82. public class TopNewsAdapter extends RecyclerView.Adapter<TopNewsViewHolder> { @Override public TopNewsViewHolder onCreateViewHolder( ViewGroup parent, int viewType) { // create a view holder with a new view // that represents items of the given type // create the view model }
  83. 83. ViewModel creates and emits the Model for the View View subscribes to the emissions of the Model View updates the RecyclerView.Adapter The View and the corresponding ViewModel are created in Adapter.onCreateViewHolder
  84. 84. Open Article
  85. 85. Open Article No ViewModel
  86. 86. class TopNewsStreamViewModel { Observable<List<UiModel>> getUiModels() { }
  87. 87. @AutoValue abstract class UiModel { abstract Article article(); class TopNewsStreamViewModel { Observable<List<UiModel>> getUiModels() { }
  88. 88. @AutoValue abstract class UiModel { abstract Article article(); abstract Action0 onClickAction(); class TopNewsStreamViewModel { Observable<List<UiModel>> getUiModels() { }
  89. 89. @AutoValue abstract class UiModel { abstract Article article(); abstract Action0 onClickAction(); static UiModel create(Article article, Action0 onClickAction){ … } class TopNewsStreamViewModel { Observable<List<UiModel>> getUiModels() { }
  90. 90. class TopNewsStreamViewModel { Observable<List<UiModel>> getUiModels() { }
  91. 91. class TopNewsStreamViewModel { Observable<List<UiModel>> getUiModels() { } return dataModel.getTopNewsArticles() .flatMap(article -> createUiModel(article))
  92. 92. class TopNewsStreamViewModel { Observable<List<UiModel>> getUiModels() { } return dataModel.getTopNewsArticles() .flatMap(article -> createUiModel(article)) private UiModel createUiModel(Article article) { return UiModel.create(article, new Action0() { @Override public void call() { // handle item click }}); }
  93. 93. class TopNewsViewHolder extends RecyclerView.ViewHolder{
  94. 94. class TopNewsViewHolder extends RecyclerView.ViewHolder{ TextView title; …
  95. 95. class TopNewsViewHolder extends RecyclerView.ViewHolder{ TextView title; … void bindItem(UiModel uiModel) { title.setText(uiModel.getTitle()); }
  96. 96. void bindItem(UiModel uiModel) { title.setText(uiModel.getTitle()); } view.setOnClickListener( v -> uiModel.onClickAction().call()); class TopNewsViewHolder extends RecyclerView.ViewHolder{ TextView title; …
  97. 97. TopNewsAdapter UiModels TopNewsViewHolder bindItem(UiModel)
  98. 98. TopNewsAdapter UiModels TopNewsViewHolder bindItem(UiModel) getUiModels() TopNewsStream ViewModel
  99. 99. Define an Action in the View’s Model Bind the Model to the View via the RecyclerView.ViewHolder Trigger the Action Action is handled by the ViewModel
  100. 100. It’s All In The Implementation Details @FMuntenescu upday MVVM https://goo.gl/bIwdmc

×