Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Clean - Moving forward, evolving our architecture

M2mobi
April 21, 2017

Clean - Moving forward, evolving our architecture

Describing the way our projects evolved over time, from GodActivities to clean architecture. The pitfalls and benefits.

M2mobi

April 21, 2017
Tweet

More Decks by M2mobi

Other Decks in Programming

Transcript

  1. Spaghetti • Hard to make changes • Almost impossible to

    unit test • Hard to debug • NOT re-usable
  2. MVP (God)Activity Interact with system Data management UI rendering UI

    event handling UI logic View navigation Interact with model Model View Presenter
  3. MVP (God)Activity Interact with system Data management UI rendering UI

    event handling UI logic View navigation Interact with model Model View Presenter
  4. The View public interface AverageFuelPriceView {
 
 void showAveragePrice(final String

    pAveragePrice);
 
 void showError(final String pError);
 } @Override
 public void showAveragePrice(final String pAveragePrice) {
 mAveragePriceTextView.setText(pAveragePrice);
 }
  5. The Presenter public class AverageFuelPricePresenter implements GetGasStationsListener {
 public void

    onStart() {
 mFuelPriceRepository.loadGasStations(FuelType.Euro95, this); } 
 @Override
 public void onGasStationsLoaded(final List<GasStation> pGasStations) {
 Price averagePrice = calculateAveragePrice(pGasStations); String formattedPrice = formatPrice(averagePrice);
 mAveragePriceView.showAveragePrice(formattedPrice);
 } @Override
 public void onErrorLoadingGasStations() {
 mAveragePriceView.showError( // .. Error message)
 } private Price calculateAveragePrice(final List<GasStation> pGasStations) {
 // .. Calculate average price
 } private String formatPrice(final Price pPrice) {
 // .. Format price to nice String
 } } public void onStart() {
 mGasStationRepository.loadGasStations(FuelType.Euro95, this); }
  6. The Presenter public class AverageFuelPricePresenter implements GetGasStationsListener {
 public void

    onStart() {
 mFuelPriceRepository.loadGasStations(FuelType.Euro95, this); } 
 @Override
 public void onGasStationsLoaded(final List<GasStation> pGasStations) {
 Price averagePrice = calculateAveragePrice(pGasStations); String formattedPrice = formatPrice(averagePrice);
 mAveragePriceView.showAveragePrice(formattedPrice);
 } @Override
 public void onErrorLoadingGasStations() {
 mAveragePriceView.showError( // .. Error message)
 } private Price calculateAveragePrice(final List<GasStation> pGasStations) {
 // .. Calculate average price
 } private String formatPrice(final Price pPrice) {
 // .. Format price to nice String
 } } public void onStart() {
 mGasStationRepository.loadGasStations(FuelType.Euro95, this); }
  7. The Presenter public class AverageFuelPricePresenter implements GetGasStationsListener {
 public void

    onStart() {
 mFuelPriceRepository.loadGasStations(FuelType.Euro95, this); } 
 @Override
 public void onGasStationsLoaded(final List<GasStation> pGasStations) {
 Price averagePrice = calculateAveragePrice(pGasStations); String formattedPrice = formatPrice(averagePrice);
 mAveragePriceView.showAveragePrice(formattedPrice);
 } @Override
 public void onErrorLoadingGasStations() {
 mAveragePriceView.showError( // .. Error message)
 } private Price calculateAveragePrice(final List<GasStation> pGasStations) {
 // .. Calculate average price
 } private String formatPrice(final Price pPrice) {
 // .. Format price to nice String
 } } @Override
 public void onGasStationsLoaded(final List<GasStation> pGasStations) {
 Price averagePrice = calculateAveragePrice(pGasStations); String formattedPrice = formatPrice(averagePrice);
 mAveragePriceView.showAveragePrice(formattedPrice);
 } @Override
 public void onErrorLoadingGasStations() {
 mAveragePriceView.showError( // .. Error message)
 } implements GetGasStationsListener
  8. The Presenter public class AverageFuelPricePresenter implements GetGasStationsListener {
 public void

    onStart() {
 mFuelPriceRepository.loadGasStations(FuelType.Euro95, this); } 
 @Override
 public void onGasStationsLoaded(final List<GasStation> pGasStations) {
 Price averagePrice = calculateAveragePrice(pGasStations); String formattedPrice = formatPrice(averagePrice);
 mAveragePriceView.showAveragePrice(formattedPrice);
 } @Override
 public void onErrorLoadingGasStations() {
 mAveragePriceView.showError( // .. Error message)
 } private Price calculateAveragePrice(final List<GasStation> pGasStations) {
 // .. Calculate average price
 } private String formatPrice(final Price pPrice) {
 // .. Format price to nice String
 } } private Price calculateAveragePrice(final List<GasStation> pGasStations) {
 // .. Calculate average price
 } private String formatPrice(final Price pPrice) {
 // .. Format price to nice String
 }
  9. MVP (God)Activity Interact with system Data management UI rendering UI

    event handling UI logic View navigation Interact with model Model View Presenter
  10. public interface CalculateAverageFuelPriceInput {
 
 void calculateAveragePrice(FuelType pFuelType);
 
 }

    Display average fuel price Input interface CalculateAverageFuelPriceInput
  11. public interface CalculateAverageFuelPriceInput {
 
 void calculateAveragePrice(FuelType pFuelType);
 
 }

    Display average fuel price Input void calculateAveragePrice(FuelType pFuelType);
  12. public interface CalculateAverageFuelPriceInput {
 
 void calculateAveragePrice(FuelType pFuelType);
 
 }

    Display average fuel price Input Output public interface CalculateAverageFuelPriceOutput {
 
 void onAveragePriceCalculated(final Price pPrice);
 
 void onErrorCalculatingAveragePrice();
 
 } interface CalculateAverageFuelPriceOutput
  13. public interface CalculateAverageFuelPriceInput {
 
 void calculateAveragePrice(FuelType pFuelType);
 
 }

    Display average fuel price Input Output public interface CalculateAverageFuelPriceOutput {
 
 void onAveragePriceCalculated(final Price pPrice);
 
 void onErrorCalculatingAveragePrice();
 
 } void onAveragePriceCalculated(final Price pPrice);
  14. public interface CalculateAverageFuelPriceInput {
 
 void calculateAveragePrice(FuelType pFuelType);
 
 }

    Display average fuel price Input Output public interface CalculateAverageFuelPriceOutput {
 
 void onAveragePriceCalculated(final Price pPrice);
 
 void onErrorCalculatingAveragePrice();
 
 } void onErrorCalculatingAveragePrice();
  15. The use case public class CalculateAverageFuelPriceUseCase implements CalculateAverageFuelPriceInput {
 


    CalculateAverageFuelPriceOutput mCalculateAverageFuelPriceOutput; GasStationRepository mGasStationRepository;
 
 @Override
 public void calculateAveragePrice(final FuelType pFuelType) {
 mGasStationRepository.loadGasStations(pFuelType, new GasStationsLoadedListener() {
 
 @Override
 public void onGasStationsLoaded(final List<GasStation> pGasStations) {
 Price averagePrice = calculateAveragePrice(pGasStations); mCalculateAverageFuelPriceOutput.onAveragePriceCalculated(averagePrice);
 }
 
 @Override
 public void onErrorLoadingGasStations() {
 mCalculateAverageFuelPriceOutput.onErrorCalculatingAveragePrice();
 }
 }); private Price calculateAveragePrice(final List<GasStation> pGasStations) {
 // .. Calculate average price
 } }
  16. The use case public class CalculateAverageFuelPriceUseCase implements CalculateAverageFuelPriceInput {
 


    CalculateAverageFuelPriceOutput mCalculateAverageFuelPriceOutput; GasStationRepository mGasStationRepository;
 
 @Override
 public void calculateAveragePrice(final FuelType pFuelType) {
 mGasStationRepository.loadGasStations(pFuelType, new GasStationsLoadedListener() {
 
 @Override
 public void onGasStationsLoaded(final List<GasStation> pGasStations) {
 Price averagePrice = calculateAveragePrice(pGasStations); mCalculateAverageFuelPriceOutput.onAveragePriceCalculated(averagePrice);
 }
 
 @Override
 public void onErrorLoadingGasStations() {
 mCalculateAverageFuelPriceOutput.onErrorCalculatingAveragePrice();
 }
 }); private Price calculateAveragePrice(final List<GasStation> pGasStations) {
 // .. Calculate average price
 } } implements CalculateAverageFuelPriceInput @Override
 public void calculateAveragePrice(final FuelType pFuelType) {
  17. The use case public class CalculateAverageFuelPriceUseCase implements CalculateAverageFuelPriceInput {
 


    CalculateAverageFuelPriceOutput mCalculateAverageFuelPriceOutput; GasStationRepository mGasStationRepository;
 
 @Override
 public void calculateAveragePrice(final FuelType pFuelType) {
 mGasStationRepository.loadGasStations(pFuelType, new GasStationsLoadedListener() {
 
 @Override
 public void onGasStationsLoaded(final List<GasStation> pGasStations) {
 Price averagePrice = calculateAveragePrice(pGasStations); mCalculateAverageFuelPriceOutput.onAveragePriceCalculated(averagePrice);
 }
 
 @Override
 public void onErrorLoadingGasStations() {
 mCalculateAverageFuelPriceOutput.onErrorCalculatingAveragePrice();
 }
 }); private Price calculateAveragePrice(final List<GasStation> pGasStations) {
 // .. Calculate average price
 } } mGasStationRepository.loadGasStations(pFuelType, new GasStationsLoadedListener() {
  18. The use case public class CalculateAverageFuelPriceUseCase implements CalculateAverageFuelPriceInput {
 


    CalculateAverageFuelPriceOutput mCalculateAverageFuelPriceOutput; GasStationRepository mGasStationRepository;
 
 @Override
 public void calculateAveragePrice(final FuelType pFuelType) {
 mGasStationRepository.loadGasStations(pFuelType, new GasStationsLoadedListener() {
 
 @Override
 public void onGasStationsLoaded(final List<GasStation> pGasStations) {
 Price averagePrice = calculateAveragePrice(pGasStations); mCalculateAverageFuelPriceOutput.onAveragePriceCalculated(averagePrice);
 }
 
 @Override
 public void onErrorLoadingGasStations() {
 mCalculateAverageFuelPriceOutput.onErrorCalculatingAveragePrice();
 }
 }); private Price calculateAveragePrice(final List<GasStation> pGasStations) {
 // .. Calculate average price
 } } Price averagePrice = calculateAveragePrice(pGasStations); private Price calculateAveragePrice(final List<GasStation> pGasStations) {
 // .. Calculate average price
 }
  19. The use case public class CalculateAverageFuelPriceUseCase implements CalculateAverageFuelPriceInput {
 


    CalculateAverageFuelPriceOutput mCalculateAverageFuelPriceOutput; GasStationRepository mGasStationRepository;
 
 @Override
 public void calculateAveragePrice(final FuelType pFuelType) {
 mGasStationRepository.loadGasStations(pFuelType, new GasStationsLoadedListener() {
 
 @Override
 public void onGasStationsLoaded(final List<GasStation> pGasStations) {
 Price averagePrice = calculateAveragePrice(pGasStations); mCalculateAverageFuelPriceOutput.onAveragePriceCalculated(averagePrice);
 }
 
 @Override
 public void onErrorLoadingGasStations() {
 mCalculateAverageFuelPriceOutput.onErrorCalculatingAveragePrice();
 }
 }); private Price calculateAveragePrice(final List<GasStation> pGasStations) {
 // .. Calculate average price
 } } mCalculateAverageFuelPriceOutput.onAveragePriceCalculated(averagePrice); mCalculateAverageFuelPriceOutput.onErrorCalculatingAveragePrice(); CalculateAverageFuelPriceOutput mCalculateAverageFuelPriceOutput;
  20. The presenter public class AveragePricePresenter implements CalculateAverageFuelPriceOutput {
 
 AveragePriceView

    mAveragePriceView;
 
 AverageFuelPriceUseCase mAverageFuelPriceUseCase;
 
 public void loadAveragePrice() {
 mAverageFuelPriceUseCase.getAverageFuelPrice(FuelType.Euro95);
 }
 
 @Override
 public void onAveragePriceCalculated(final Price pPrice) {
 mAveragePriceView.showAveragePrice(formatPrice(pPrice));
 }
 
 @Override
 public void onErrorCalculatingAveragePrice() {
 mAveragePriceView.showError(”Unable to calculate average price”)
 }
 }
  21. The presenter public class AveragePricePresenter implements CalculateAverageFuelPriceOutput {
 
 AveragePriceView

    mAveragePriceView;
 
 AverageFuelPriceUseCase mAverageFuelPriceUseCase;
 
 public void loadAveragePrice() {
 mAverageFuelPriceUseCase.getAverageFuelPrice(FuelType.Euro95);
 }
 
 @Override
 public void onAveragePriceCalculated(final Price pPrice) {
 mAveragePriceView.showAveragePrice(formatPrice(pPrice));
 }
 
 @Override
 public void onErrorCalculatingAveragePrice() {
 mAveragePriceView.showError(”Unable to calculate average price”)
 }
 } implements CalculateAverageFuelPriceOutput @Override
 public void onAveragePriceCalculated(final Price pPrice) {
 mAveragePriceView.showAveragePrice(formatPrice(pPrice));
 }
 
 @Override
 public void onErrorCalculatingAveragePrice() {
 mAveragePriceView.showError(”Unable to calculate average price”)
 }

  22. Example Show Toast instead of text bar @Override
 public void

    showAveragePrice(final String pAveragePrice) {
 mAveragePriceTextView.setText(pAveragePrice);
 }
  23. Example Show Toast instead of text bar @Override
 public void

    showAveragePrice(final String pAveragePrice) {
 mAveragePriceTextView.setText(pAveragePrice);
 } @Override
 public void showAveragePrice(final String pAveragePrice) {
 Toast.makeText(this, pAveragePrice, Toast.LENGTH_SHORT).show();
 }
  24. UI Data sources Presenters Repositories Example Show Toast instead of

    text bar @Override
 public void showAveragePrice(final String pAveragePrice) {
 mAveragePriceTextView.setText(pAveragePrice);
 } @Override
 public void showAveragePrice(final String pAveragePrice) {
 Toast.makeText(this, pAveragePrice, Toast.LENGTH_SHORT).show();
 } Entities Use cases Abstraction
  25. Example Load average price from webservice public class CalculateAverageFuelPriceUseCase implements

    CalculateAverageFuelPriceInput {
 
 GasStationRepository mGasStationRepository; 
 @Override
 public void calculateAveragePrice(final FuelType pFuelType) {
 mGasStationRepository.loadGasStations(pFuelType, new GasStationsLoadedListener() { // .. Code that calculates the average price
 } } GasStationRepository mGasStationRepository; mGasStationRepository.loadGasStations(pFuelType, new GasStationsLoadedListener() {
  26. Example Load average price from webservice public class CalculateAverageFuelPriceUseCase implements

    CalculateAverageFuelPriceInput {
 
 GasStationRepository mGasStationRepository; 
 @Override
 public void calculateAveragePrice(final FuelType pFuelType) {
 mGasStationRepository.loadGasStations(pFuelType, new GasStationsLoadedListener() { // .. Code that calculates the average price
 } } GasStationRepository mGasStationRepository; mGasStationRepository.loadGasStations(pFuelType, new GasStationsLoadedListener() {
  27. Example Load average price from webservice public class CalculateAverageFuelPriceUseCase implements

    CalculateAverageFuelPriceInput {
 
 GasStationRepository mGasStationRepository; 
 @Override
 public void calculateAveragePrice(final FuelType pFuelType) {
 mGasStationRepository.loadGasStations(pFuelType, new GasStationsLoadedListener() { // .. Code that calculates the average price
 } } UI Data sources Presenters Repositories Entities Use cases
  28. Example Show loading state public interface AverageFuelPriceView {
 
 void

    showAveragePrice(final String pAveragePrice);
 
 void showError(String pError); 
 }
  29. Example Show loading state public interface AverageFuelPriceView {
 
 void

    showAveragePrice(final String pAveragePrice);
 
 void showError(String pError); 
 } void showLoading(); void hideLoading();
  30. public class AveragePricePresenter implements CalculateAverageFuelPriceOutput {
 
 AveragePriceView mAveragePriceView;
 


    AverageFuelPriceUseCase mAverageFuelPriceUseCase;
 
 public void loadAveragePrice() {
 mAveragePriceView.showLoading();
 mAverageFuelPriceUseCase.getAverageFuelPrice(FuelType.Euro95);
 }
 
 @Override
 public void onAveragePriceCalculated(final Price pPrice) {
 mAveragePriceView.hideLoading();
 mAveragePriceView.showAveragePrice(formatPrice(pPrice));
 }
 
 @Override
 public void onErrorCalculatingAveragePrice() {
 mAveragePriceView.hideLoading();
 mAveragePriceView.showError(”Error message”)
 }
 } Example Show loading state
  31. public class AveragePricePresenter implements CalculateAverageFuelPriceOutput {
 
 AveragePriceView mAveragePriceView;
 


    AverageFuelPriceUseCase mAverageFuelPriceUseCase;
 
 public void loadAveragePrice() {
 mAveragePriceView.showLoading();
 mAverageFuelPriceUseCase.getAverageFuelPrice(FuelType.Euro95);
 }
 
 @Override
 public void onAveragePriceCalculated(final Price pPrice) {
 mAveragePriceView.hideLoading();
 mAveragePriceView.showAveragePrice(formatPrice(pPrice));
 }
 
 @Override
 public void onErrorCalculatingAveragePrice() {
 mAveragePriceView.hideLoading();
 mAveragePriceView.showError(”Error message”)
 }
 } Example Show loading state mAveragePriceView.showLoading(); mAveragePriceView.hideLoading(); mAveragePriceView.hideLoading(); loadAveragePrice() onAveragePriceCalculated(final Price pPrice) onErrorCalculatingAveragePrice()
  32. public class AveragePricePresenter implements CalculateAverageFuelPriceOutput {
 
 AveragePriceView mAveragePriceView;
 


    AverageFuelPriceUseCase mAverageFuelPriceUseCase;
 
 public void loadAveragePrice() {
 mAveragePriceView.showLoading();
 mAverageFuelPriceUseCase.getAverageFuelPrice(FuelType.Euro95);
 }
 
 @Override
 public void onAveragePriceCalculated(final Price pPrice) {
 mAveragePriceView.hideLoading();
 mAveragePriceView.showAveragePrice(formatPrice(pPrice));
 }
 
 @Override
 public void onErrorCalculatingAveragePrice() {
 mAveragePriceView.hideLoading();
 mAveragePriceView.showError(”Error message”)
 }
 } Example Show loading state mAveragePriceView.showLoading(); mAveragePriceView.hideLoading(); mAveragePriceView.hideLoading(); loadAveragePrice() onAveragePriceCalculated(final Price pPrice) onErrorCalculatingAveragePrice() UI Data sources Presenters Repositories Entities Use cases
  33. public interface
 } void calculateAveragePrice(FuelType pFuelType); RxJava Combine input/output boundaries

    public interface CalculateAverageFuelPriceOutput {
 
 void onAveragePriceCalculated(final Price pPrice);
 
 void onErrorCalculatingAveragePrice();
 
 } Price CalculateAverageFuelPriceInput {
  34. public interface
 } void calculateAveragePrice(FuelType pFuelType); Observable< > RxJava Combine

    input/output boundaries public interface CalculateAverageFuelPriceOutput {
 
 void onAveragePriceCalculated(final Price pPrice);
 
 void onErrorCalculatingAveragePrice();
 
 } Price CalculateAverageFuelPrice {
  35. RxJava Managing subscriptions public class AveragePricePresenter implements CalculateAverageFuelPriceOutput {
 


    Subscription mSubscription;
 
 public void loadAveragePrice() {
 mSubscription = mAverageFuelPriceUseCase.
 // RxStuff
 .subscribe(// Subscription handling)
 }
 
 public void onPause() {
 mSubscription.unsubscribe();
 } }
  36. RxJava Managing subscriptions public class AveragePricePresenter implements CalculateAverageFuelPriceOutput {
 


    Subscription mSubscription;
 
 public void loadAveragePrice() {
 mSubscription = mAverageFuelPriceUseCase.
 // RxStuff
 .subscribe(// Subscription handling)
 }
 
 public void onPause() {
 mSubscription.unsubscribe();
 } } public void onPause() {
 mSubscription.unsubscribe();
 } public void loadAveragePrice() {
 mSubscription = mAverageFuelPriceUseCase.
 // RxStuff
 .subscribe(// Subscription handling)
 }
  37. Recommended watching Architecture The Lost Years - Robert C. Martin

    (Uncle Bob) Managing State with RxJava - Jake Wharton Architecting Android - Fernando Cejas