New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Are there any examples on PresentationModels and PresentationModels transformations? #85

Closed
renanferrari opened this Issue Sep 18, 2015 · 6 comments

Comments

Projects
None yet
4 participants
@renanferrari

renanferrari commented Sep 18, 2015

I know there's a quite large explanation on what PresentationModels are and how to manage them, both here and on the Stinson's Playbook for Mosby, but I couldn't find any other example on this repo. Is there any? If not, why?

I'd really like to see some other scenario like the one described on the last paragraph of Mosby's MVP page (the first link I shared above), where a Presenter receives different implementations of PresentationModelTransformers from the View, depending on specific conditions determined by the View itself.

@sockeqwe

This comment has been minimized.

Show comment
Hide comment
@sockeqwe

sockeqwe Sep 18, 2015

Owner

No there is no example in the repo as it's not part of the library. Mosby doesn't provide something like a PresentationModelTransformation class you can use.

I just wanted to point out that the presenter (as the name already suggests) is not only responsible to coordinate the view (showLoading(), showError(), showContent(), etc.) but is responsible to transform a model to that kind of data structure that the view can present/display afterwards.

I can give you an example. Are you familiar with RxJava? (would make the example a little bit easier to write)

Owner

sockeqwe commented Sep 18, 2015

No there is no example in the repo as it's not part of the library. Mosby doesn't provide something like a PresentationModelTransformation class you can use.

I just wanted to point out that the presenter (as the name already suggests) is not only responsible to coordinate the view (showLoading(), showError(), showContent(), etc.) but is responsible to transform a model to that kind of data structure that the view can present/display afterwards.

I can give you an example. Are you familiar with RxJava? (would make the example a little bit easier to write)

@renanferrari

This comment has been minimized.

Show comment
Hide comment
@renanferrari

renanferrari Sep 18, 2015

Hey @sockeqwe, thank you for your answer. Yeah, now that I've come to think about it, it makes sense it's not part of the library, for sure.

I think I understand most of PresentationModel's concept, but I still would like to see another example, if you don't mind. I never really used RxJava, but I'm sort of familiar with its syntax, so feel free to use it in the example.

And thanks in advance :)

renanferrari commented Sep 18, 2015

Hey @sockeqwe, thank you for your answer. Yeah, now that I've come to think about it, it makes sense it's not part of the library, for sure.

I think I understand most of PresentationModel's concept, but I still would like to see another example, if you don't mind. I never really used RxJava, but I'm sort of familiar with its syntax, so feel free to use it in the example.

And thanks in advance :)

@sockeqwe

This comment has been minimized.

Show comment
Hide comment
@sockeqwe

sockeqwe Sep 20, 2015

Owner

Example (pseudo code, the code may not compile):
Lets assume we are building an app for a restaurant to allow users of the app order a meal online. The app has a DishesView to display a List<Dish>. A Dish is basically just a superclass. There are VegetarianDish extends Dish and FishDish extends Dish and MeatDish extends Dish. Lets that the restaurant is providing a bill of fare over a restful http backend with a json like that:

{
  "vegetarian": [
    { "id": 1,  "name": "Black Bean Enchilada", "price":12.5},
    ...
  ],

"fish": [
    { "id": 20,  "name": "Salmon", "price":20.5},
    ...
  ],

"meat": [
    { "id": 40,  "name": "Argentina Steak", "price":19.7},
    ...
  ]
}

So the json is divided into categories, however we want to display a complete List<Dish> in our app.
Furthermore, the user has the possibility to mark a certain dish as one of his favorites which is stores into a local database (on the users device). So basically the app loads the bill of faire from backend, combines its data with a local database (just stores the id of favorite dishes so that we can set a boolean flag "favorite" to true of false on the Dish class) and then display it as single list.

A naive approach would be like that:

class DishesPresenter extends MvpBasePresenter<DishesView> {

  private RxHttpBackend backend;
  private RxDatabase database;

  public void getDishes(){
    getView().showLoading();

    // loadData from backend and local database
    Observable.comineLatest(
       backend.getBillOfFaire(),  // returns Observable<BillOfFaire>
       database.getFavoriteDishes(), // returns Observable<List<String>> containing a list with all ids of dishes the user has marked as favorite
      new Func2<BillOfFaire, List<String>, List<Dish>>() { // method to combine both
           public List<Dish> call(BillOfFaire bof, List<String> favorites){
               List<Dish> dishes = new ArrayList<>();
               dishes.addAll(bof.getVegetarianDishes());
               dishes.addAll(bof.getFishDishes());
               dishes.addAll(bof.getMeatDishes());

              for (Dish d : dishes) 
                  if (favorites.contains( d.getId() ))
                    d.setFavorite(true);

              return dishes;
           }
      })
    .map(new Func1<List<Dish>, List<Dish>>(){ // Sort alphabetically
           public List<Dish> call(List<Dish> dishes){
               Collections.sort(dishes);
               return dishes; 
           }
    })
   .subscribe(new Subscriber< List<Dish> >() {
       public void onNext(List<Dish> dishes){
           if (isViewAttached())
             getView().setData(dishes);
       }

       public void onCompleted(){
           if (isViewAttached())
             getView().showContent();
        }

       public void onError(Throwable t){
           if (isViewAttached())
             getView().showError(t);
        }
    });

  }

}

The code sample above is very naive and you should not do it like that. why? First it mixes all your "Model layer" responsibilities into the Presenter. Why is it bad? Because you cant test your model layer anymore since there is no separated model layer (everything is "hardcoded" in the presenter). So lets refactor the code shown above and introduce a new class DishesRepo which is basically responsible to load the bill of faire from backend and query the local database with favorit dishes to mark dishes as favorite or not. So basically move the combineLatest() code into DishesRepo:

class DishesRepo {
  private RxHttpBackend backend;
  private RxDatabase database;

  public Observable<List<Dish>> getAllDishes(){
    return Observable.comineLatest(
       backend.getBillOfFaire(),  // returns Observable<BillOfFaire>
       database.getFavoriteDishes(), // returns Observable<List<String>> containing a list with all ids of dishes the user has marked as favorite
      new Func2<BillOfFaire, List<String>, List<Dish>>() { // method to combine both
           public List<Dish> call(BillOfFaire bof, List<String> favorites){
               List<Dish> dishes = new ArrayList<>();
               dishes.addAll(bof.getVegetarianDishes());
               dishes.addAll(bof.getFishDishes());
               dishes.addAll(bof.getMeatDishes());

              for (Dish d : dishes) 
                  if (favorites.contains( d.getId() ))
                    d.setFavorite(true);

              return dishes;
           }
      });
  }

}

Now DishesRepo is the "model" of MVP and can be tested (unit test). The Presenter now looks more clear:

class DishesPresenter extends MvpBasePresenter<DishesView> {

  private DishesRepo dishesRepo;

  public void getDishes(){
    getView().showLoading();

    // loadData from backend and local database
    dishesRepo.getAllDishes()
    .map(new Func1<List<Dish>, List<Dish>>(){ // Sort alphabetically
           public List<Dish> call(List<Dish> dishes){
               Collections.sort(dishes);
               return dishes; 
           }
    })
   .subscribe(new Subscriber< List<Dish> >() {
       public void onNext(List<Dish> dishes){
           if (isViewAttached())
             getView().setData(dishes);
       }

       public void onCompleted(){
           if (isViewAttached())
             getView().showContent();
        }

       public void onError(Throwable t){
           if (isViewAttached())
             getView().showError(t);
        }
    });

  }

}

So in this very simple use case we just sort the list of all dishes alphabetically because we know the DishesView wants to display it in alphabetical order. So sorting is the job of the presenter (it's not part of the model because there might be other Views / Presenter that want to display the "raw" (unsorted) data).

But what about the PresentationModel we talked about earlier. The discussed scenario is so simple (sort a list alphabetically) that there is no need for an additional PresentationModel class.
Lets make the things a little bit more complex. Lets say that the dishes backend also provides a boolean flag lunchOffer. If a dish is marked as favorite by the user and marked as lunchOffer by backend and its lunch time (midday) then the view should display this dish as a special one (i.e. increase text size, display a image of the dish and a offer ribbon instead of just the name of the dish).

To make it for our View easier to distinguish between normal dishes and special offer we decide to introduce a DishesPresentationModel. Now its the job of the Presenter to determine whether or not a dish is a lunch offer or not:

class DishesPresenter extends MvpBasePresenter<DishesView> {

  private DishesRepo dishesRepo;

  public void getDishes(){
    getView().showLoading();

    // loadData from backend and local database
    dishesRepo.getAllDishes()
    .map(new Func1<List<DishPresentationModel>, List<Dish>>(){ // Sort alphabetically
           public List<Dish> call(List<Dish> dishes){
               Collections.sort(dishes);
               List<DishPresentationModel> pmDishes = new ArrayList<>();
               for (Dish d : dishes){
                  DishPresentationModel pm = new DishPresentationModel(d);
                  if ( d.isFavorite() && d.isLunchOffer() && isLunchTimeRightNow() )
                     pm.setShowAsOffer(true);

                  pmDishes.add(pm);
               }
               return pmDishes; 
           }
    })
   .subscribe(new Subscriber< List<DishPresentationModel> >() {
       public void onNext(List<DishPresentationModel> dishes){
           if (isViewAttached())
             getView().setData(dishes);
       }

       public void onCompleted(){
           if (isViewAttached())
             getView().showContent();
        }

       public void onError(Throwable t){
           if (isViewAttached())
             getView().showError(t);
        }
    });

  }

}

So now the presenter is "transforming" a List<Dish> into List<DishPresentationModel> which is specialized and optimized for DishesView to be displayed (i.e. the Adapter of the RecyclerView doesn't have to compute if its a lunch offer or not during scrolling).

I know it's just a very basic example and may not require a PresentationModel at all but I haven't found a better one for now 😄

Then I have also mentioned that we could have two different Presenter for phone and tablet or use one single presenter and use different PresentationModelTransformer for phone or tablet. Lets discuss how the later one could be achieved. With RxJava its easy since we only have to pass a Func1<List<DishPresentationModel> , List<Dish> as constructor parameter like this:

class DishesPresenter extends MvpBasePresenter<DishesView> {

  private DishesRepo dishesRepo;
  private Func1<List<DishPresentationModel> , List<Dish> presentationModelTransformation;

 public DishesPresenter( Func1<List<DishPresentationModel> , List<Dish>> transformer){
    this.presentationModelTransformation = transformer;
  }

  public void getDishes(){
    getView().showLoading();

    // loadData from backend and local database
    dishesRepo.getAllDishes()
    .map(presentationModelTransformation) // PresentationModel 
    .subscribe(new Subscriber< List<DishPresentationModel> >() {
       public void onNext(List<DishPresentationModel> dishes){
           if (isViewAttached())
             getView().setData(dishes);
       }

       public void onCompleted(){
           if (isViewAttached())
             getView().showContent();
        }

       public void onError(Throwable t){
           if (isViewAttached())
             getView().showError(t);
        }
    });

  }

}

And then your View could do something like that:

public class DishesActivity extends MvpActivity implements DishesView {

    @Override
     public DishesPresenter createPresenter(){
          if (isTablet()){
              Func1<List<DishPresentationModel> , List<Dish>> tabletTransformer = ... ;
              return new DishesPresenter(tabletTransformer);
           } else {
            // its a phone
            Func1<List<DishPresentationModel> , List<Dish>> phoneTransformer = ... ;
              return new DishesPresenter(phoneTransformer);
          }
     }

   ...
}

The "disadvantage" of this approach (passing different "transformer") is that the view (since we write the transformer code as part of the Activity) now also have some kind of knowledge of the model (knows about Dish even if the view only displays DishPresentationModel). The View should be as dump as possible and only worry about UI related things and not how to compose the data (that might be a strict point of view and could be relaxed depending on the concrete scenario). Hence I would recommend to think about another approach (having two different presenter classes for phone and tablet) like this:

interface DishesPresenter {
    void getDishes();
}
class PhoneDishesPresenter extends MvpBasePresenter<DishesView> implements DishesPresenter {

  private DishesRepo dishesRepo;

  @Override
  public void getDishes(){
    getView().showLoading();

    // loadData from backend and local database
    dishesRepo.getAllDishes()
    .map( ... ) // PresentationModel  transformation for phone
    .subscribe(new Subscriber< List<DishPresentationModel> >() {
       public void onNext(List<DishPresentationModel> dishes){
           if (isViewAttached())
             getView().setData(dishes);
       }

       public void onCompleted(){
           if (isViewAttached())
             getView().showContent();
        }

       public void onError(Throwable t){
           if (isViewAttached())
             getView().showError(t);
        }
    });

  }

}
class TabletDishesPresenter extends MvpBasePresenter<DishesView> implements DishesPresenter {

  private DishesRepo dishesRepo;

  @Override
  public void getDishes(){
    getView().showLoading();

    // loadData from backend and local database
    dishesRepo.getAllDishes()
    .map( ... ) // PresentationModel  transformation for tablet
    .subscribe(new Subscriber< List<DishPresentationModel> >() {
       public void onNext(List<DishPresentationModel> dishes){
           if (isViewAttached())
             getView().setData(dishes);
       }

       public void onCompleted(){
           if (isViewAttached())
             getView().showContent();
        }

       public void onError(Throwable t){
           if (isViewAttached())
             getView().showError(t);
        }
    });

  }

}
public class DishesActivity extends MvpActivity implements DishesView {

    @Override
     public DishesPresenter createPresenter(){
          if (isTablet())
              return new TabletDishesPresenter();
           else 
              return new PhoneDishesPresenter();       
     }

   ...
}

The disadvantage here is that you have some duplicated code that you might have to maintain, i.e. if you do a refactoring (more complicated than just renaming a method) then you have to refactor TabletDishesPresenter and PhoneDishesPresenter. Same for bug fixes. However, that might could be reduced with another level of abstraction and inheritence (i.e. TabletDishesPresenter and PhoneDishesPresenter have a common superclass and only have to override a single method where the presentation model transformation happens).

To conclude: I think its just a personal preference which one you prefer over the other. I, personally, like more the approach of having tow different presenter classes TabletDishesPresenter and PhoneDishesPresenter simply because I would expect to search and find all "presentation model code" in the corresponding Presenter class. As already said, both approaches are fine, use whatever you feel more comfortable with and don't over-engineer to much 😃

Owner

sockeqwe commented Sep 20, 2015

Example (pseudo code, the code may not compile):
Lets assume we are building an app for a restaurant to allow users of the app order a meal online. The app has a DishesView to display a List<Dish>. A Dish is basically just a superclass. There are VegetarianDish extends Dish and FishDish extends Dish and MeatDish extends Dish. Lets that the restaurant is providing a bill of fare over a restful http backend with a json like that:

{
  "vegetarian": [
    { "id": 1,  "name": "Black Bean Enchilada", "price":12.5},
    ...
  ],

"fish": [
    { "id": 20,  "name": "Salmon", "price":20.5},
    ...
  ],

"meat": [
    { "id": 40,  "name": "Argentina Steak", "price":19.7},
    ...
  ]
}

So the json is divided into categories, however we want to display a complete List<Dish> in our app.
Furthermore, the user has the possibility to mark a certain dish as one of his favorites which is stores into a local database (on the users device). So basically the app loads the bill of faire from backend, combines its data with a local database (just stores the id of favorite dishes so that we can set a boolean flag "favorite" to true of false on the Dish class) and then display it as single list.

A naive approach would be like that:

class DishesPresenter extends MvpBasePresenter<DishesView> {

  private RxHttpBackend backend;
  private RxDatabase database;

  public void getDishes(){
    getView().showLoading();

    // loadData from backend and local database
    Observable.comineLatest(
       backend.getBillOfFaire(),  // returns Observable<BillOfFaire>
       database.getFavoriteDishes(), // returns Observable<List<String>> containing a list with all ids of dishes the user has marked as favorite
      new Func2<BillOfFaire, List<String>, List<Dish>>() { // method to combine both
           public List<Dish> call(BillOfFaire bof, List<String> favorites){
               List<Dish> dishes = new ArrayList<>();
               dishes.addAll(bof.getVegetarianDishes());
               dishes.addAll(bof.getFishDishes());
               dishes.addAll(bof.getMeatDishes());

              for (Dish d : dishes) 
                  if (favorites.contains( d.getId() ))
                    d.setFavorite(true);

              return dishes;
           }
      })
    .map(new Func1<List<Dish>, List<Dish>>(){ // Sort alphabetically
           public List<Dish> call(List<Dish> dishes){
               Collections.sort(dishes);
               return dishes; 
           }
    })
   .subscribe(new Subscriber< List<Dish> >() {
       public void onNext(List<Dish> dishes){
           if (isViewAttached())
             getView().setData(dishes);
       }

       public void onCompleted(){
           if (isViewAttached())
             getView().showContent();
        }

       public void onError(Throwable t){
           if (isViewAttached())
             getView().showError(t);
        }
    });

  }

}

The code sample above is very naive and you should not do it like that. why? First it mixes all your "Model layer" responsibilities into the Presenter. Why is it bad? Because you cant test your model layer anymore since there is no separated model layer (everything is "hardcoded" in the presenter). So lets refactor the code shown above and introduce a new class DishesRepo which is basically responsible to load the bill of faire from backend and query the local database with favorit dishes to mark dishes as favorite or not. So basically move the combineLatest() code into DishesRepo:

class DishesRepo {
  private RxHttpBackend backend;
  private RxDatabase database;

  public Observable<List<Dish>> getAllDishes(){
    return Observable.comineLatest(
       backend.getBillOfFaire(),  // returns Observable<BillOfFaire>
       database.getFavoriteDishes(), // returns Observable<List<String>> containing a list with all ids of dishes the user has marked as favorite
      new Func2<BillOfFaire, List<String>, List<Dish>>() { // method to combine both
           public List<Dish> call(BillOfFaire bof, List<String> favorites){
               List<Dish> dishes = new ArrayList<>();
               dishes.addAll(bof.getVegetarianDishes());
               dishes.addAll(bof.getFishDishes());
               dishes.addAll(bof.getMeatDishes());

              for (Dish d : dishes) 
                  if (favorites.contains( d.getId() ))
                    d.setFavorite(true);

              return dishes;
           }
      });
  }

}

Now DishesRepo is the "model" of MVP and can be tested (unit test). The Presenter now looks more clear:

class DishesPresenter extends MvpBasePresenter<DishesView> {

  private DishesRepo dishesRepo;

  public void getDishes(){
    getView().showLoading();

    // loadData from backend and local database
    dishesRepo.getAllDishes()
    .map(new Func1<List<Dish>, List<Dish>>(){ // Sort alphabetically
           public List<Dish> call(List<Dish> dishes){
               Collections.sort(dishes);
               return dishes; 
           }
    })
   .subscribe(new Subscriber< List<Dish> >() {
       public void onNext(List<Dish> dishes){
           if (isViewAttached())
             getView().setData(dishes);
       }

       public void onCompleted(){
           if (isViewAttached())
             getView().showContent();
        }

       public void onError(Throwable t){
           if (isViewAttached())
             getView().showError(t);
        }
    });

  }

}

So in this very simple use case we just sort the list of all dishes alphabetically because we know the DishesView wants to display it in alphabetical order. So sorting is the job of the presenter (it's not part of the model because there might be other Views / Presenter that want to display the "raw" (unsorted) data).

But what about the PresentationModel we talked about earlier. The discussed scenario is so simple (sort a list alphabetically) that there is no need for an additional PresentationModel class.
Lets make the things a little bit more complex. Lets say that the dishes backend also provides a boolean flag lunchOffer. If a dish is marked as favorite by the user and marked as lunchOffer by backend and its lunch time (midday) then the view should display this dish as a special one (i.e. increase text size, display a image of the dish and a offer ribbon instead of just the name of the dish).

To make it for our View easier to distinguish between normal dishes and special offer we decide to introduce a DishesPresentationModel. Now its the job of the Presenter to determine whether or not a dish is a lunch offer or not:

class DishesPresenter extends MvpBasePresenter<DishesView> {

  private DishesRepo dishesRepo;

  public void getDishes(){
    getView().showLoading();

    // loadData from backend and local database
    dishesRepo.getAllDishes()
    .map(new Func1<List<DishPresentationModel>, List<Dish>>(){ // Sort alphabetically
           public List<Dish> call(List<Dish> dishes){
               Collections.sort(dishes);
               List<DishPresentationModel> pmDishes = new ArrayList<>();
               for (Dish d : dishes){
                  DishPresentationModel pm = new DishPresentationModel(d);
                  if ( d.isFavorite() && d.isLunchOffer() && isLunchTimeRightNow() )
                     pm.setShowAsOffer(true);

                  pmDishes.add(pm);
               }
               return pmDishes; 
           }
    })
   .subscribe(new Subscriber< List<DishPresentationModel> >() {
       public void onNext(List<DishPresentationModel> dishes){
           if (isViewAttached())
             getView().setData(dishes);
       }

       public void onCompleted(){
           if (isViewAttached())
             getView().showContent();
        }

       public void onError(Throwable t){
           if (isViewAttached())
             getView().showError(t);
        }
    });

  }

}

So now the presenter is "transforming" a List<Dish> into List<DishPresentationModel> which is specialized and optimized for DishesView to be displayed (i.e. the Adapter of the RecyclerView doesn't have to compute if its a lunch offer or not during scrolling).

I know it's just a very basic example and may not require a PresentationModel at all but I haven't found a better one for now 😄

Then I have also mentioned that we could have two different Presenter for phone and tablet or use one single presenter and use different PresentationModelTransformer for phone or tablet. Lets discuss how the later one could be achieved. With RxJava its easy since we only have to pass a Func1<List<DishPresentationModel> , List<Dish> as constructor parameter like this:

class DishesPresenter extends MvpBasePresenter<DishesView> {

  private DishesRepo dishesRepo;
  private Func1<List<DishPresentationModel> , List<Dish> presentationModelTransformation;

 public DishesPresenter( Func1<List<DishPresentationModel> , List<Dish>> transformer){
    this.presentationModelTransformation = transformer;
  }

  public void getDishes(){
    getView().showLoading();

    // loadData from backend and local database
    dishesRepo.getAllDishes()
    .map(presentationModelTransformation) // PresentationModel 
    .subscribe(new Subscriber< List<DishPresentationModel> >() {
       public void onNext(List<DishPresentationModel> dishes){
           if (isViewAttached())
             getView().setData(dishes);
       }

       public void onCompleted(){
           if (isViewAttached())
             getView().showContent();
        }

       public void onError(Throwable t){
           if (isViewAttached())
             getView().showError(t);
        }
    });

  }

}

And then your View could do something like that:

public class DishesActivity extends MvpActivity implements DishesView {

    @Override
     public DishesPresenter createPresenter(){
          if (isTablet()){
              Func1<List<DishPresentationModel> , List<Dish>> tabletTransformer = ... ;
              return new DishesPresenter(tabletTransformer);
           } else {
            // its a phone
            Func1<List<DishPresentationModel> , List<Dish>> phoneTransformer = ... ;
              return new DishesPresenter(phoneTransformer);
          }
     }

   ...
}

The "disadvantage" of this approach (passing different "transformer") is that the view (since we write the transformer code as part of the Activity) now also have some kind of knowledge of the model (knows about Dish even if the view only displays DishPresentationModel). The View should be as dump as possible and only worry about UI related things and not how to compose the data (that might be a strict point of view and could be relaxed depending on the concrete scenario). Hence I would recommend to think about another approach (having two different presenter classes for phone and tablet) like this:

interface DishesPresenter {
    void getDishes();
}
class PhoneDishesPresenter extends MvpBasePresenter<DishesView> implements DishesPresenter {

  private DishesRepo dishesRepo;

  @Override
  public void getDishes(){
    getView().showLoading();

    // loadData from backend and local database
    dishesRepo.getAllDishes()
    .map( ... ) // PresentationModel  transformation for phone
    .subscribe(new Subscriber< List<DishPresentationModel> >() {
       public void onNext(List<DishPresentationModel> dishes){
           if (isViewAttached())
             getView().setData(dishes);
       }

       public void onCompleted(){
           if (isViewAttached())
             getView().showContent();
        }

       public void onError(Throwable t){
           if (isViewAttached())
             getView().showError(t);
        }
    });

  }

}
class TabletDishesPresenter extends MvpBasePresenter<DishesView> implements DishesPresenter {

  private DishesRepo dishesRepo;

  @Override
  public void getDishes(){
    getView().showLoading();

    // loadData from backend and local database
    dishesRepo.getAllDishes()
    .map( ... ) // PresentationModel  transformation for tablet
    .subscribe(new Subscriber< List<DishPresentationModel> >() {
       public void onNext(List<DishPresentationModel> dishes){
           if (isViewAttached())
             getView().setData(dishes);
       }

       public void onCompleted(){
           if (isViewAttached())
             getView().showContent();
        }

       public void onError(Throwable t){
           if (isViewAttached())
             getView().showError(t);
        }
    });

  }

}
public class DishesActivity extends MvpActivity implements DishesView {

    @Override
     public DishesPresenter createPresenter(){
          if (isTablet())
              return new TabletDishesPresenter();
           else 
              return new PhoneDishesPresenter();       
     }

   ...
}

The disadvantage here is that you have some duplicated code that you might have to maintain, i.e. if you do a refactoring (more complicated than just renaming a method) then you have to refactor TabletDishesPresenter and PhoneDishesPresenter. Same for bug fixes. However, that might could be reduced with another level of abstraction and inheritence (i.e. TabletDishesPresenter and PhoneDishesPresenter have a common superclass and only have to override a single method where the presentation model transformation happens).

To conclude: I think its just a personal preference which one you prefer over the other. I, personally, like more the approach of having tow different presenter classes TabletDishesPresenter and PhoneDishesPresenter simply because I would expect to search and find all "presentation model code" in the corresponding Presenter class. As already said, both approaches are fine, use whatever you feel more comfortable with and don't over-engineer to much 😃

@sockeqwe sockeqwe closed this Sep 20, 2015

@renanferrari

This comment has been minimized.

Show comment
Hide comment
@renanferrari

renanferrari Sep 21, 2015

Wow, that was an AMAZING answer @sockeqwe, really, really helpful example! Everything is clearer now.

Thank you so much for taking the time to write this :D

renanferrari commented Sep 21, 2015

Wow, that was an AMAZING answer @sockeqwe, really, really helpful example! Everything is clearer now.

Thank you so much for taking the time to write this :D

@wayanjimmy

This comment has been minimized.

Show comment
Hide comment
@wayanjimmy

wayanjimmy Jan 27, 2016

Wow, just get interested in presentation model, and found this discussion, thanks @sockeqwe

wayanjimmy commented Jan 27, 2016

Wow, just get interested in presentation model, and found this discussion, thanks @sockeqwe

@BayramInanc

This comment has been minimized.

Show comment
Hide comment
@BayramInanc

BayramInanc Mar 8, 2017

it's a fantastic explanation, thank you man!

BayramInanc commented Mar 8, 2017

it's a fantastic explanation, thank you man!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment