Вариант реализации MVP + DI(Dagger 2) в Android

от автора

С недавних пор заинтересовался данной темой и порылся в дебрях сети на эту тему. На англоязычных ресурсах есть раскиданная по разным местам информация. Прочитал все, что есть в рунете на эту тему. Ссылки приведу в конце статьи. В итоге, стал применять этот подход в своих приложениях.

Для начала, вспомним, что такое MVP. MVP — это паттерн, который позволяет разбивать приложение на три основных слоя (компонента):

  1. Модель (Model) — где сосредоточены данные;
  2. Представление (View) — интерфейс приложения (UI — элементы);
  3. Presenter — промежуточный компонент, который реализует связь между Моделью и Представлением.

image

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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *