JavaRush/Java блог/Random UA/MVP в Android для найменших

MVP в Android для найменших

Стаття з групи Random UA
учасників
Коли я починав свій шлях Android-розробника, слова «Архітектура мобільного додатка» викликали в мене глибоке здивування, гугл і статті на Хабре вганяли ще більшу депресію - дивлюся в книгу, бачу фігу. Думаю, якщо ти читаєш цю статтю, то вже не раз вивчав цю картинку і намагався зрозуміти, що відбувається: MVP в Android для найменших - 1Проблема розуміння архітектурного підходу в мобільній розробці, як на мене, криється в абстрактності самої архітектури. У кожного розробника своє бачення того, як правильно реалізувати той чи інший патерн. Більш-менш пристойні приклади реалізації MVP знайшлися в англомовному секторі інтернету, що не дивно. Коротенько розберемо, що є що, і перейдемо, наприклад. Model – рівень даних. Не люблю використовувати термін «бізнес логіка», тому у своїх додатках я називаю йогоRepository і він спілкується з базою даних та мережею. View – рівень відображення. Це буде Activity , Fragment або Custom View , якщо ви не любите танців з бубном та взаємодії з життєвим циклом. Нагадаю, що спочатку всі додатки Android підпорядковані структурі MVC , де Controller це Activity або Fragment . Presenter- Прошарок між View і Model. View передає йому події, що відбуваються, презентер обробляє їх, при необхідності звертається до Model і повертає View дані на малювання. Стосовно Android і конкретного прикладу, виділю важливу частину - Contract. Це інтерфейс, який описує всі взаємодії між перерахованими компонентами. Резюмуючи теоретичну частину:
  • View знає про Presenter;
  • Presenter знає про View та Model (Repository);
  • Model сама по собі;
  • Контракт регулює взаємодії між ними.
Власне, сам приклад, для простоти експерименту за натисканням на кнопку завантажувати рядок з БД і відображати в TextView . Наприклад, БД містить список найкращих ресторанів міста. Почнемо з контракту: Створимо інтерфейс MainContract:
public interface MainContract {
    interface View {
        void showText();
    }

    interface Presenter {
        void onButtonWasClicked();
        void onDestroy();
    }

    interface Repository {
        String loadMessage();
    }
}
Поки що ми просто виділяємо 3 компоненти нашого майбутнього додатку і що вони робитимуть. Далі опишемо Repository:
public class MainRepository implements MainContract.Repository {

    private static final String TAG = "MainRepository";
    @Override
    public String loadMessage() {
        Log.d(TAG, "loadMessage()");
        /** Здесь обращаемся к БД або сети.
         * Я специально ничего не пишу, чтобы не загромождать пример
         * DBHelper'ами и прочими не относяшимеся к теме об'єктами.
         * Поэтому я буду возвращать строку Сосисочная =)
         */
        return "Сосисочная у Лёхи»;
    }
}
З ним все зрозуміло, просто завантаження – вивантаження даних. Далі на черзі Presenter:
public class MainPresenter implements MainContract.Presenter {
    private static final String TAG = "MainPresenter";

    //Компоненты MVP програми
    private MainContract.View mView;
    private MainContract.Repository mRepository;

    //Сообщение
    private String message;


    //Обрати внимание на аргументы конструктора - мы передаем экземпляр View, а Repository просто создаём конструктором.
    public MainPresenter(MainContract.View mView) {
        this.mView = mView;
        this.mRepository = new MainRepository();
        Log.d(TAG, "Constructor");
    }

    //View сообщает, что кнопка была нажата
    @Override
    public void onButtonWasClicked() {
        message = mRepository.loadMessage();
        mView.showText(message);
        Log.d(TAG, "onButtonWasClicked()");
    }

    @Override
    public void onDestroy() {
        /**
         * Если бы мы работали например с RxJava, в этом классе стоило бы отписываться от подписок
         * Кроме того, при работе с другими методами асинхронного андроида,здесь мы боремся с утечкой контекста
         */

        Log.d(TAG, "onDestroy()");
    }
}
Пам'ятаєш, я писав про танці з бубном та життєвий цикл? Presenter живе до тих пір, поки живе його View, при розробці складних користувальницьких сценаріїв, раджу дублювати всі колбеки View в Presenter'e і викликати їх у відповідні моменти, дублюючи ЖЦ Activity/Fragment, щоб вчасно зрозуміти, що потрібно зробити з тими даними, які висять зараз у «прошарку». І нарешті, View:
public class MainActivity extends AppCompatActivity implements MainContract.View {

    private static final String TAG = "MainActivity";

    private MainContract.Presenter mPresenter;

    private Button mButton;

    private TextView myTv;

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

        //Создаём Presenter и в аргументе передаём ему this - эта Activity расширяет интерфейс MainContract.View
        mPresenter = new MainPresenter(this);

        myTv = (TextView) findViewById(R.id.text_view);
        mButton = (Button) findViewById(R.id.button);

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPresenter.onButtonWasClicked();
            }
        });
        Log.d(TAG, "onCreate()");
    }

    @Override
    public void showText(String message) {
        myTv.setText(message);
        Log.d(TAG, "showMessage()");
    }

    //Вызываем у Presenter метод onDestroy, чтобы избежать утечек контекста и прочих неприятностей.
    @Override
    public void onDestroy() {
        super.onDestroy();
        mPresenter.onDestroy();
        Log.d(TAG, "onDestroy()");
    }
}
Що ж відбувається?
  • Activity, вона ж View, у методі onCreate()створює примірник Presenter і передає йому конструктор себе.
  • Presenter при створенні явно отримує View і створює екземпляр Repository (його, до речі, можна зробити Singleton)
  • При натисканні на кнопку View стукає презентеру і повідомляє: «Кнопка була натиснута».
  • Presenter звертається до Repository: «Завантаж мені цю шнягу».
  • Repository вантажить і віддає "шнягу" Presenter'у.
  • Presenter звертається до View: "Ось тобі дані, малюй"
Ось і все, хлопці. PS Важливо чітко розмежувати обов'язки компонентів. Наприклад, в одному з моїх навчальних проектів натискання на кнопку необхідно було змінити дані в БД. Модель описувалася POJO – класом, я передавав інформацію про розташування view, що відповідає за інформацію про об'єкт на екрані, Presenter шукав цей об'єкт у списку та віддавав його на запис у Repository. Здається, все логічно? Але мій наставник звернув увагу на наступне: Repository повинен займатися ТІЛЬКИ записом та читанням, він не повинен витягувати з POJO потрібну інформацію та приймати рішення, що йому потрібно писати. Presenter повинен віддати йому лише інформацію на запис і нічого більше. Нема жорстких рамок реалізації архітектури: експериментуй, пробуй, шукай те, що зручно особисто тобі. І не забудь показати старшим товаришам на code review =) Приклад доступний на GitHub: https://github.com/admitrevskiy/MVP_Example
Коментарі
  • популярні
  • нові
  • старі
Щоб залишити коментар, потрібно ввійти в систему
Для цієї сторінки немає коментарів.