AndroidでのMVP + DI(Dagger 2)の実装のバリエーション

最近、彼はこのトピックに興味を持ち、このトピックに関するネットワークの荒野で大騒ぎしました。 英語のリソースでは、さまざまな場所に情報が散在しています。 このトピックに関するRuNetのすべてを読みました。 記事の最後にリンクを掲載します。 その結果、彼はこのアプローチをアプリケーションに適用し始めました。



まず第一に、 MVPとは何かを覚えておいてください。 MVPは、アプリケーションを3つの主要なレイヤー(コンポーネント)に「分割」できるパターンです。

  1. モデル-データが集中している場所。
  2. プレゼンテーション(表示)-アプリケーションインターフェイス(UI-要素);
  3. プレゼンターは、モデルとビューの間の関係を実装する中間コンポーネントです。


画像



MVPパターンは、より有名なパターン-MVCの後継です。 このアプローチを使用すると、アプリケーションロジックをインターフェイスから分離できます。



Androidアプリケーションで構造が実装される方法は、MVPに似たものと呼ぶことができます。 つまり モデルは任意のデータであり、ビューはlayout-xmlファイルのUI要素であり、Presenterはアクティビティとフラグメントを含めることができます。 ただし、これにより、データとこのデータの表示からロジックを完全に分離することはできません。



それでは、AndroidアプリケーションにMVPを適用してみましょう。



ビューのプレゼンターへのリンクの追加を実装するために、別のパターン-Dependency Injection(DI)を適用します。 これを行うには、Google- Dagger 2の便利なライブラリを使用します。 この記事では、ライブラリとそのコンポーネント、およびその動作原理については説明しません。 これはすでにわかっていることがわかります。



www.ted.comから会議をリストするテストアプリケーションを作成しましょう。 これには、1つのメインアクティビティと3つのフラグメントが含まれます。リストを含むフラグメント、詳細を含むフラグメント、および会議を表示するためのフラグメントです。



DIのグラフを作成するには、アプリケーションの後継者をクラスに取り込みます。



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.フラグメント用に各プレゼンターに実装されるインターフェイスを作成します。



 public interface BaseFragmentPresenter<T> { void init(T view); }
      
      





次に、アプリケーションのModuleクラスとComponentクラスを作成します。



 @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 { }
      
      





その後、アプリケーションのアクティビティクラスを記述できます。 この例では、これは主要なアクティビティの1つです。 コードには私たちにとって興味深いメソッドが含まれており、完全なコードは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); } ... }
      
      





最後の1つは、プレゼンタークラスを実装する例です。



アクティビティの場合:



 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と依存性注入の完全な説明を主張するものではありません。これは、Androidアプリケーションの構造にそれらを適用する方法の別の例です。 最初は、不必要なクラスとインターフェイスのヒープがコードの可読性を低下させるように見えるかもしれませんが、そうではありません。 実際にMVPを適用すると、アプリケーション内をナビゲートしやすくなり、コードを簡単に拡張できます。

実装ではなくインターフェイスレベルでのプログラミングが増えており、アプリケーションコンポーネントは疎結合であるため、変更中のエラーの数が減ります。 また、依存性注入を使用すると、コードがより簡潔で読みやすくなります。



現時点では、Dagger 2には1つの欠点があります。 モジュールでオーバーライドすることはできません(これはSquareのDaggerで実装されています)。 これにより、テストの作成時に問題が生じます。 ライブラリの今後の更新でこの欠陥が修正されることを願っています。 このトピックが興味深いのは、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-コミュニティ



この記事で説明されているテストプロジェクトの完全なコードは、 GitHubで確認およびダウンロードできます



All Articles