MODEL-VIEW-PRESENTER

..czyli jak zadbać o właściwą architekturę androidowej aplikacji i nie zgubić się w over999 linijkach kodu w Activity.

Model-View-Presenter jest jednym z najbardziej popularnych rozwiązań architektonicznych aplikacji mobilnych. Dzisiaj porozmawiamy o Androidzie i o tym, w jaki sposób ja korzystam z tego wzorca i ułatwiam życie sobie i osobom, które mają styczność z moim kodem.

What is MVP? (Krótko)

https://cdn-images-1.medium.com/max/1600/1*3JERTTFmC35Rhx-C0uvECA.png źródło: https://cdn-images-1.medium.com/max/1600/1*3JERTTFmC35Rhx-C0uvECA.png

  • Model – jest warstwą zarządzania danymi. Do obowiązków modeli wchodzą: korzystanie z API, buforowanie danych, zarządzanie bazami danych etc.
  • View – jest warstwą, która wyświetla dane i reaguje na działania użytkownika. W systemie Android to może być Activity, Fragment, custom-owe View lub Dialog.
  • Presenter – jest warstwą pośrednikiem pomiędzy View a model. Presenter zawiera całą waszą logikę, czyli komunikowanie się View z model, działania w tle aplikacji, przetwarzanie danych do wyświetlenia we View etc.

Where is Logic? (Nie krótko)

http://www.materniak.com.pl/wp-content/uploads/2017/02/logika3.jpg Źródło: http://www.materniak.com.pl/klasyfikacja-podzial-pojec-cz-3/

Traktuję interfejs MVP jako stereotypową, konserwatywną rodzinę, czyli;

  • View – to kobieta, która nie pracuje, tylko dba o swój wygląd i dzieci.
  • Presenter – to facet, który w rodzinie jest odpowiedzialny za ciężką pracę i komunikowaniem ze światem.
  • Model – to jest świat, do którego tylko presenter ma dostęp.

Dzięki takiej hierarchii możemy łatwo się poruszać po aplikacji. Jak często się zdarzało, że próbowaliście rozwikłać co się dzieje w kilkuset linijkach kodu w metodzie? A jak często aktywności zawierali kupę wewnętrznych klas, odpowiadających za komunikowanie się z API etc.? No, niestety domyślam się.

https://media.giphy.com/media/UqPhCdioYHmdq/giphy.gif źródło: https://giphy.com/gifs/csak-art-rubiks-cube-csaba-klement-UqPhCdioYHmdq

How to use it?

Zaczynam zawsze od tego, że tworzę package o nazwie mojego view (np MainActivity). Ta paczka zawiera w sobie trzy klasy: View, Presenter i Contract – interfejs, za pomocą którego komunikujemy się pomiędzy view i presenter. Do tego dodaję paczkę Model, która właśnie reprezentuje Model w rozumieniu MVP. 

Android Przyjrzymy się nieco dokładniej do MainContract. To jest interfejs, który zawiera w sobie obowiązki, które należą do naszego małżeństwa.

    interface MainContract {
        interface View {
        }

        interface UserActionListener {
        }
    }

Interfejs View odpowiada za View, z kolei UserActionListener za Presenter.

    public class MainActivity extends AppCompatActivity implements MainContract.View {

        private MainContract.UserActionListener mainPresenter;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            mainPresenter = new MainPresenter(this);
        }
    }

    class MainPresenter implements MainContract.UserActionListener {
        private MainContract.View mainActivityView;

        MainPresenter(MainContract.View mainActivityView){
            this.mainActivityView = mainActivityView;
        }
    }

Podpisujemy umowę pomiędzy naszymi członkami rodziny czyli: implementujemy odpowiednie interfejsy, tworzymy instancję interfejsu prezentera w aktywności i przekazujemy prezentowi instancję aktywności.  Widzimy, że mamy dostęp tylko taki, na który będzie nam pozwalał nasz Contract (czyli coś w stylu “pokaż mi swój context, dziubasku” bez umawianiu o tym w standardowy pakiet nie wchodzi). PS. View tworzy dla siebie Presentera. To jest ważne!

More examples, Carl!

Chcę policzyć 2+2 i wyświetlić to na ekranie.

    @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mainPresenter = new MainPresenter(this);

            double sum = mainPresenter.countSum(2, 2);
            textView.setText(sum);
        }

Activity prosi prezentera o policzeniu mi sumy dwóch liczb. Dopisujemy do umowy, że prezenter powinien umieć policzyć sumę tych liczb.

     {
        interface View {
        }

        interface UserActionListener {
            double countSum(int firstNumber, int secondNumber);
        }
    }

I dodajemy do prezentera realizację tego działania:

    class MainPresenter implements MainContract.UserActionListener {
        private MainContract.View mainActivityView;

        MainPresenter(MainContract.View mainActivityView){
            this.mainActivityView = mainActivityView;
        }

        @Override
        public double countSum(int firstNumber, int secondNumber) {
            return firstNumber + secondNumber;
        }
    }

Potężny prezenter liczy to co mu kazano i wysyła wynik z powrotem. Aktywność otrzymuje wynik i używa go do swoich “widokowych” cele nie mając pojęcia jak ten wynik był zrobiony. Tak samo działa i w drugą stronę, kiedy prezenter potrzebuje od View< pewne dane (np Context lub FragmentManager). Chciałbym też zwrócić uwagę na Adaptery, których traktuję jako View nadrzędne (czyli dzieci). Adapter też ma dostęp do prezentera i View-rodzicai znajduje się w tej samej paczce-rodzinie. Na przykład RecyclerViewAdapter jest dzieckiem pewnej Activity i ma egzemplarz prezentera i View.

Summary

Dzięki MVP kod staje się bardziej czytelny, krótszy i logiczny. Dodatkowo do tego, kod można łatwiej testować. Logika prezentera częściej nie dotyczy warstwy androidowej i może być dokładniej przetestowane przez Java’owe framework’i testowe.

Przydatne linki

http://konmik.com/post/introduction_to_model_view_presenter_on_android/

https://medium.com/@cervonefrancesco/model-view-presenter-android-guidelines-94970b430ddf

FacebookTwitterGoogle+LinkedIn