04 / 08 / 2017 Developers

MODEL-VIEW-PRESENTER

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

Pattern Model-View-Presenter (zwykli śmiertelnicy używają skrótu MVP) 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)

ź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)

ź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ę.

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

MVP jest tratwą w oceanie dlatego, że możemy skrócić activity/fragment o 50% albo więcej, więc chodźmy, pokażę, jak ja tego używam.

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 MVPPrzyjrzymy 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 MainContract {
    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 Adapter’y, których traktuję jako View nadrzędne (czyli dzieci). Adapter też ma dostęp do prezentera i View-rodzica i 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