mvm - it's all in the (implementation) details
TRANSCRIPT
Mobile Architecture
From The Patterns To The Deployment
MVVMIt’s All In The ImplementationDetails
ModelViewViewModel
DataModelView ViewModel1 … *
Android Classes?Static Methods?
Android Classes?Static Methods?
Providers
context.getString(id);
public class ResourceProvider {
Context context;
ResourceProvider(Context context){this.context = context;
}
}
public class ResourceProvider {
Context context;
ResourceProvider(Context context){this.context = context;
}
}
String getString(@StringRes int id) { return }
context.getString(id);
FirebaseRemoteConfig.getInstance() .getString(key);
class RemoteConfigProvider { FirebaseRemoteConfig remoteConfig;
}
RemoteConfigProvider() { remoteConfig =
}
FirebaseRemoteConfig.getInstance()
class RemoteConfigProvider { FirebaseRemoteConfig remoteConfig;
}
String getString(String key) { return remoteConfig.getString(key);}
RemoteConfigProvider() { remoteConfig =
}
FirebaseRemoteConfig.getInstance()
DataModel
View DataModelViewModel
Network
Database
SharedPreferences
Articles
Articles ArticlesDataModel
Category
Category CategoriesDataModel
Articles
DataModel
class ArticlesDataModel {
RemoteDataSource remoteDataSource; LocalDataSource localDataSource;
}
class ArticlesDataModel {
RemoteDataSource remoteDataSource; LocalDataSource localDataSource;
}
Observable<List<Article>> getOrfetchTopNewsArticles() {
}
class ArticlesDataModel {
RemoteDataSource remoteDataSource; LocalDataSource localDataSource;
}
Observable<List<Article>> getOrfetchTopNewsArticles() {
}
return localDataSource.getTopNews()
class ArticlesDataModel {
RemoteDataSource remoteDataSource; LocalDataSource localDataSource;
}
Observable<List<Article>> getOrfetchTopNewsArticles() {
}
return localDataSource.getTopNews().flatMap(articles -> articles.isEmpty()? remoteDataSource.getTopNews(): Observable.just(articles));
class ArticlesDataModel {
RemoteDataSource remoteDataSource; LocalDataSource localDataSource;
}
Observable<List<Article>> getOrfetchTopNewsArticles() {
}
return localDataSource.getTopNews()
.flatMap(articles -> articles.isEmpty()? remoteDataSource.getTopNews(): Observable.just(articles));
.compose(new ArticleAgeFilterTransformer(filter))
class ArticlesDataModel {
RemoteDataSource remoteDataSource; LocalDataSource localDataSource;
}
Observable<List<Article>> getOrfetchTopNewsArticles() {
}
return localDataSource.getTopNews()
.flatMap(articles -> articles.isEmpty()? remoteDataSource.getTopNews(): Observable.just(articles));
.compose(new ArticleAgeFilterTransformer(filter))
localDataSource
.getTopNews()
class ArticlesDataModel {
RemoteDataSource remoteDataSource; LocalDataSource localDataSource;
}
Observable<List<Article>> getOrfetchTopNewsArticles() {
}
return localDataSource.getTopNews()
.flatMap(articles -> articles.isEmpty()? remoteDataSource.getTopNews(): Observable.just(articles));
.compose(new ArticleAgeFilterTransformer(filter))
localDataSource
.getTopNews()
Observable<List<Article>>
class ArticlesDataModel {
RemoteDataSource remoteDataSource; LocalDataSource localDataSource;
}
Observable<List<Article>> getOrfetchTopNewsArticles() {
}
return localDataSource.getTopNews()
.flatMap(articles -> articles.isEmpty()? remoteDataSource.getTopNews(): Observable.just(articles));
.compose(new ArticleAgeFilterTransformer(filter))
.getTopNews()
class ArticlesDataModel {
RemoteDataSource remoteDataSource; LocalDataSource localDataSource;
}
Observable<List<Article>> getOrfetchTopNewsArticles() {
}
return localDataSource.getTopNews()
.flatMap(articles -> articles.isEmpty()? remoteDataSource.getTopNews(): Observable.just(articles));
.compose(new ArticleAgeFilterTransformer(filter))
getOrfetchTopNewsArticles
.getTopNews()
HomeView
Article Teaser View
Article Teaser View
ArticleTeaserViewModel
ArticlesDataModel
Article Teaser View
ArticleTeaserViewModel
DatabaseArticles
DataModel
Article Teaser View
ArticleTeaserViewModel
Database
New Article
ArticlesDataModel
Article Teaser View
ArticleTeaserViewModel
Articles
DataModel
New Article
Database
Article Teaser View
ArticleTeaserViewModel
getOrfetchTopNewsArticles
ArticleTeaserViewModel
New Article
DatabaseArticlesDataModel
Article Teaser View
getTopNewsArticle
Article Teaser View
New Article
DatabaseArticlesDataModel
ArticleTeaserViewModel
DataModel uses the Repository pattern
One DataModel per business model
DataModel drives the data flow
ViewViewModel
Article Teaser View
<LinearLayout>
<…ArticleTeaserView … />
</LinearLayout>
class ArticleTeaserView extends View{
ArticleTeaserView(Context context){...onInject();
}
}
class ArticleTeaserView extends View{
}
new ArticleTeaserViewModel()
ArticleTeaserView(Context context){...onInject();
}
class ArticleTeaserView extends View{
}
void onAttachedToWindow(){ bind();}
class ArticleTeaserView extends View{
}
void onAttachedToWindow(){ bind();}
void bind(){ subscription.add(
viewModel.getTopNewsArticle() .subscribeOn(…)
.observeOn(…) .subscribe(this::showArticle,
error -> Timber.e(error, “Error ...”);}
class ArticleTeaserView extends View{
}
void onDettachedFromWindow(){ subscription.clear();
}
class ArticleTeaserView extends View{
}
void onDettachedFromWindow(){ subscription.clear();
} viewModel.dispose();
Lifecycle of the ViewModel depends on the lifecycle of the View
Only the View has a reference to the corresponding ViewModel
View is declared in the XML
Only other native android classes know about the View
Model-View-ViewModelLists
TopNewsStream View
class TopNewsStreamViewModel {
}
class TopNewsStreamViewModel {
}
Observable<TopNewsPages> getTopNewsPages(){
… }
TopNewsStream View
class TopNewsStreamViewModel {
}
@AutoValueabstract class TopNewsPages {
List<Displayable> displayables();
int position();}
Observable<TopNewsPages> getTopNewsPages(){
… }
TopNewsStream View
class TopNewsStreamView extends Fragment {
}
@InjectTopNewsStreamViewModel viewModel;
void onResume(){ bind();}
void onPause(){ unbind();}
class TopNewsStreamView extends Fragment{
}
class TopNewsStreamView extends Fragment{
}
void bind(){ subscription.add(
viewModel.getTopNewsPages() .subscribe(pages -> setupPages(pages))
}
class TopNewsStreamView extends Fragment{
}
void bind(){ subscription.add(
viewModel.getTopNewsPages() .subscribe(pages -> setupPages(pages))
}
void setupPages(TopNewsPages pages){ // based on pages.displayables() // update RecyclerView.Adapter // using DiffUtil.calculateDiff
}
void bind(){ subscription.add(
viewModel.getTopNewsPages() .subscribe(pages -> setupPages(pages))
}
void setupPages(TopNewsPages pages){ // based on pages.displayables() // update RecyclerView.Adapter // using DiffUtil.calculateDiff
}// based on pages.position()// update position
class TopNewsStreamView extends Fragment{
}
class DisplayablesRecyclerViewAdapterextends RecyclerView.Adapter<BoundViewHolder> {
}
class DisplayablesRecyclerViewAdapterextends RecyclerView.Adapter<BoundViewHolder> {
}
onCreateItemView(ViewGroup parent, int viewType){
// create View // create the ViewModel for the View
}
Open Article
Open Article
Share Article
Open Article
Share Article
Label should disappear after 5sec
class TopNewsArticleViewModel {
void openArticle(){…
}
void share(){…
}
Observable<Boolean> isNewLabelVisible(){…
}}
ViewModel creates and emits the Model of the View
View subscribes to the emissions of the Model
View updates the RecyclerView.Adapter
In Adapter.onCreateItemView the RecylerView’s item and the corresponding ViewModel are created.
Open Article
no ViewModel
Open Article
class TopNewsStreamViewModel {
Observable<List<Displayable>> getDisplayables(){
}
}
class TopNewsStreamViewModel {
Observable<List<Displayable>> getDisplayables(){
}
@AutoValueabstract class Displayable (){
Article article();
}
}
@AutoValueabstract class Displayable (){
Article article();
}
Action0 onClickAction();
}
class TopNewsStreamViewModel {
Observable<List<Displayable>> getDisplayables(){
}
Displayable create(Article article, Action0 action){ …}
@AutoValueabstract class Displayable (){
Article article();
}
Action0 onClickAction();
}
class TopNewsStreamViewModel {
Observable<List<Displayable>> getDisplayables(){
}
class TopNewsStreamViewModel {
Observable<List<Displayable>> getDisplayables(){
}
return dataModel.getTopNewsArticles() .flatMap(article ->
createDisplayable(article)) ... }
class TopNewsStreamViewModel {
Observable<List<Displayable>> getDisplayables(){
}
return dataModel.getTopNewsArticles() .flatMap(article ->
createDisplayable(article)) ... }
Displayable createDisplayable(Article article){ return Displayable.create(article,
new Action0() { @Override public void call() { // handle item click }}
class TopNewsStreamViewModel {
Observable<List<Displayable>> getDisplayables(){
}
return dataModel.getTopNewsArticles() .flatMap(article ->
createDisplayable(article)) ... }
Displayable createDisplayable(Article article){ return Displayable.create(article,
new Action0() { @Override public void call() { // handle item click }}
class TopNewsViewHolder implements RecyclerView.ViewHolder {
}
class TopNewsViewHolder implements RecyclerView.ViewHolder {
}
TextView title;…
class TopNewsViewHolder implements RecyclerView.ViewHolder {
}
TextView title;…
void bindItem(Displayable displayable){title.setText(displayable.article().getTitle())
}
class TopNewsViewHolder implements RecyclerView.ViewHolder {
}
TextView title;…
void bindItem(Displayable displayable){title.setText(displayable.article().getTitle())
}
view.setOnClickListener( v -> displayable.onClickAction().call())
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
MVVMIt’s All In The ImplementationDetails