С недавних пор заинтересовался данной темой и порылся в дебрях сети на эту тему. На англоязычных ресурсах есть раскиданная по разным местам информация. Прочитал все, что есть в рунете на эту тему. Ссылки приведу в конце статьи. В итоге, стал применять этот подход в своих приложениях.
Для начала, вспомним, что такое MVP. MVP — это паттерн, который позволяет разбивать приложение на три основных слоя (компонента):
- Модель (Model) — где сосредоточены данные;
- Представление (View) — интерфейс приложения (UI — элементы);
- Presenter — промежуточный компонент, который реализует связь между Моделью и Представлением.
MVP паттерн — это наследник более известного шаблона — MVC. Применение такого подхода позволяет отделить в приложении логику от интерфейса.
То, как сейчас реализована структура в приложении Android, с «натяжкой» можно назвать чем-то похожим на MVP. Т.е. модель — это какие-либо данные, представление — это UI-элементы в наших layout-xml файлах, а к presenter можно отнести Activity и Fragment. Но, это не так и не позволяет полностью отделить логику от данных и представления этих данных.
Теперь, давайте попробуем применить MVP в нашем Android приложении.
Для того, чтобы реализовать добавление ссылки на presenter во view применим еще один паттерн — Dependency Injection (DI). Для этого воспользуемся полезной библиотекой от Google – Dagger 2. В этой статье я не буду приводить описание библиотеки и ее компоненты, а также принципы ее работы. Подразумевается, что это нам уже известно.
Создадим тестовое приложение, которое будет выводить список конференций с сайта www.ted.com. Оно будет содержать одну главную activity и три фрагмента: фрагмент со списком, фрагмент с детализацией и фрагмент просмотра конференции.
Создание графа для DI вынесем в класс наследник Application:
public class TalksTEDApp extends Application { private ITalksTEDAppComponent appComponent; public static TalksTEDApp get(Context context) { return (TalksTEDApp) context.getApplicationContext(); } @Override public void onCreate() { super.onCreate(); buildGraphAndInject(); } public ITalksTEDAppComponent getAppComponent() { return appComponent; } public void buildGraphAndInject() { appComponent = DaggerITalksTEDAppComponent.builder() .talksTEDAppModule(new TalksTEDAppModule(this)) .build(); appComponent.inject(this); } }
В базовом абстрактном классе BaseActivity определим абстрактный метод setupComponent, который будет определять компонент для каждой активити в нашем приложении.
public abstract class BaseActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setupComponent(TalksTEDApp.get(this).getAppComponent()); } protected abstract void setupComponent(ITalksTEDAppComponent appComponent); }
Для того, чтобы мы могли применить DI и для наших фрагментов, нам нужно:
1. Создать интерфейс IHasComponent, который будет имплементирован в каждой нашей активити:
public interface IHasComponent <T> { T getComponent(); }
2. Создать базовый абстрактный класс для фрагментов:
public abstract class BaseFragment extends Fragment { @SuppressWarnings("unchecked") protected <T> T getComponent(Class<T> componentType) { return componentType.cast(((IHasComponent<T>)getActivity()).getComponent()); } }
3. Создать интерфейс, кторый будет реализован в каждом Presenter для наших фрагментов:
public interface BaseFragmentPresenter<T> { void init(T view); }
Далее, создадим Module и Component классы для нашего Application:
@Module public class TalksTEDAppModule { private final TalksTEDApp app; public TalksTEDAppModule(TalksTEDApp app) { this.app = app; } @Provides @Singleton public Application provideApplication() { return app; } }
@Singleton @Component( modules = { TalksTEDAppModule.class } ) public interface ITalksTEDAppComponent { void inject(TalksTEDApp app); }
Определим для наших активити компонентов отдельный скоуп как:
@Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope { }
После этого мы можем написать класс активити для нашего приложения. В данном примере это одна главная активити. В коде приведены интересные нам методы, а полный код вы можете посмотреть на GitHub (ссылка на проект в конце статьи.)
public class MainActivity extends BaseActivity implements IMainActivityView, IHasComponent<IMainActivityComponent> { @Inject MainActivityPresenterImpl presenter; private IMainActivityComponent mainActivityComponent; ... @Override protected void setupComponent(ITalksTEDAppComponent appComponent) { mainActivityComponent = DaggerIMainActivityComponent.builder() .iTalksTEDAppComponent(appComponent) .mainActivityModule(new MainActivityModule(this)) .build(); mainActivityComponent.inject(this); } @Override public IMainActivityComponent getComponent() { return mainActivityComponent; } ... }
Следующий шаг — это реализация наших фрагментов (приведу лишь код методов, которые нам интересны в плане реализации DI):
public class ListFragment extends BaseFragment implements IListFragmentView { @Inject ListFragmentPresenterImpl presenter; protected SpiceManager spiceManager = new SpiceManager(TalkTEDService.class); private Activity activity; private ListView listView; private TalkListAdapter talkListAdapter; private View rootView; public ListFragment() { } ... @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); this.getComponent(IMainActivityComponent.class).inject(this); } @Override public void onResume() { super.onResume(); presenter.init(this); presenter.onResume(spiceManager); } ... }
И последнее — это пример реализации наших классов presenter.
Для активити:
public class MainActivityPresenterImpl implements IMainActivityPresenter { private IMainActivityView view; @Inject public MainActivityPresenterImpl(IMainActivityView view) { this.view = view; } @Override public void onBackPressed() { view.popFragmentFromStack(); } }
Для фрагмента:
public class ListFragmentPresenterImpl implements IListFragmentPresenter { int offset = 0; private static final String URL_LIST_TALKS_API = "https://api.ted.com/v1/talks.json?api-key=umdz5qctsk4g9nmqnp5btsmf&limit=30"; private IListFragmentView view; int totalTalks; private SpiceManager spiceManager; @Inject public ListFragmentPresenterImpl() { } @Override public void init(IListFragmentView view) { this.view=view; } ... }
Эта статья не претендует на полное описание MVP и Dependency Injection, это еще один пример, как их можно применить в структуре Android приложения. Вначале может показаться, что нагромождение лишних классов и интерфейсов уменьшает читаемость кода, но это не так. После применения MVP на практике становится проще ориентироваться в приложении, код проще расширять.
Мы все больше программируем на уровне интерфейсов, а не реализации, компоненты приложения слабо связаны, что уменьшает количество ошибок при изменениях. А с использованием Dependency Injection код становится более лаконичным и читаемым.
Отмечу, что в данный момент в Dagger 2 есть один недостаток. Мы не можем делать в модуле override (это реализовано в Dagger от Square). Это создает проблемы при написании тестов. Надеюсь, что в последующих обновлениях библиотеки этот недочет будет исправлен. Кому интересна эта тема — есть пост на StackOverflow здесь и здесь.
Приведу ссылки на статьи, которые довели меня до такой жизни:
habrahabr.ru/post/202866
habrahabr.ru/post/252903
antonioleiva.com/mvp-android
antonioleiva.com/dependency-injection-android-dagger-part-1
antonioleiva.com/dagger-android-part-2
antonioleiva.com/dagger-3
Android MVP — Community
Полный код тестового проекта, который рассмотрен в статье, вы можете посмотреть и скачать на GitHub.
ссылка на оригинал статьи http://habrahabr.ru/post/263465/
Добавить комментарий