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

Post on 11-Apr-2017

64 views 1 download

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