Moxyの戦略(パート2)



パート1ではMoxyで戦略が必要な理由と、それぞれの戦略を適用するのが適切なケースを特定しました。 この記事では、内部からの戦略作業のメカニズムを検討し、どのような場合にカスタム戦略が必要かを理解し、独自の戦略を作成してみます。







カスタム戦略が必要な理由



Moxyが作成をまったくサポートしないのはなぜですか?

カスタム戦略? ライブラリを設計するとき、私は(I)考えられるすべてのケースを考慮に入れようとしましたが、組み込みの戦略はほぼ100%をカバーしています。 ただし、場合によっては、状況に対するより多くの権限が必要になることがあり、制限することは望みませんでした。 これらのケースの1つを検討してください。







プレゼンターは、ハンバーガーとドリンクで構成されるビジネスランチを選択する責任があります。

持っている機能に応じたコマンドは、次のタイプに分類されます。









そのため、ハンバーガーとドリンクのチームのキューを個別に管理できるようにしたいと考えています。 このためのデフォルトの戦略では十分ではなく、さらにハンバーガーとドリンクが必要です! それらを発明しましょうが、最初に、コマンドを使用するためのメカニズムが一般的にどのように構成され、どのような戦略が影響するかを理解します。







Moxy Team Mechanics



遠くから始めましょう。ViewStateはプレゼンターコンストラクターで作成され、すべてのコマンドがプロキシーを介してプロキシされます。 ViewStateには、コマンドキュー-ViewCommands (コマンドと戦略のリストを担当するクラス)とビューリストが含まれています。 GlobalまたはWeakのようなプレゼンターを使用する場合、またはフラグメントがバックスタックに移動した場合に、ビューリストに複数のビューが含まれることがあります。







グローバルプレゼンターとウィークプレゼンター

私たちの間では、彼ら自身の責任範囲にintoいているので、それらに基づいてアーキテクチャを構築しないでください。 画面間でデータをシャッフルすることは、クリーンアーキテクチャのインタラクターなどの一般的なエンティティの方が優れています。 クリーンアーキテクチャに関する良い記事があります。







まず、戦略を構成するものを把握しましょう。







国家戦略
public interface StateStrategy { <View extends MvpView> void beforeApply( List<ViewCommand<View>> currentState, ViewCommand<View> incomingCommand); <View extends MvpView> void afterApply( List<ViewCommand<View>> currentState, ViewCommand<View> incomingCommand); }
      
      





これは、 beforeApplyafterApplyの 2つのメソッドを持つインターフェースです 。 各入力メソッドは、新しいコマンドとコマンドの現在のリストを受け入れます。これらはメソッドの本体で変更されます(または変更されません)。 各チームについて、タグ(これはStateStrategyTypeアノテーションで指定できる文字列です)と戦略のタイプ(以下のリストを参照)を取得できます。 この情報のみに基づいてリストを変更する方法を決定します。







ViewCommand
 public abstract class ViewCommand<View extends MvpView> { private final String mTag; private final Class<? extends StateStrategy> mStateStrategyType; protected ViewCommand( String tag, Class<? extends StateStrategy> stateStrategyType) { mTag = tag; mStateStrategyType = stateStrategyType; } public abstract void apply(View view); public String getTag() { return mTag; } public Class<? extends StateStrategy> getStrategyType() { return mStateStrategyType; } }
      
      





これらのメソッドがいつ呼び出されるかを理解しましょう。 そのため、 SimpleBurgerViewインターフェースがあり、これはほんの少しのチーズ(II)のみを追加できます。







 interface SimpleBurgerView : BaseView { @StateStrategyType(value = AddToEndSingleStrategy::class, tag = "Cheese") fun toggleCheese(enable: Boolean) }
      
      





生成されたLaunchView $$ StateクラスでtoggleCheeseメソッドが呼び出されたときに何が起こるかを考えます(リストを参照)。







LaunchView $$状態
 @Override public void toggleCheese( boolean p0_32355860) { ToggleCheeseCommand toggleCheeseCommand = new ToggleCheeseCommand(p0_32355860); mViewCommands.beforeApply(toggleCheeseCommand); if (mViews == null || mViews.isEmpty()) { return; } for(com.redmadrobot.app.presentation.launch.LaunchView view : mViews) { view.toggleCheese(p0_32355860); } mViewCommands.afterApply(toggleCheeseCommand); }
      
      





1)ToggleCheeseCommandコマンドが作成されます(以下のリストを参照)







ToggleCheeseCommand
 public class ToggleCheeseCommand extends ViewCommand<com.redmadrobot.app.presentation.launch.SomeView> { public final boolean enable; ToggleCheeseCommand( boolean enable) { super("toggleCheese", com.arellomobile.mvp.viewstate.strategy.AddToEndStrategy.class); this.enable = enable; } @Override public void apply(com.redmadrobot.app.presentation.launch.SomeView mvpView) { mvpView.toggleCheese(enable); } }
      
      





2) beforeApplyメソッドは、このコマンドのViewCommandsクラスに対して呼び出されます。 その中で、戦略を取得し、そのbeforeApplyメソッドを呼び出します。 (以下のリストを参照)







ViewCommands.beforeApply
 public void beforeApply(ViewCommand<View> viewCommand) { StateStrategy stateStrategy = getStateStrategy(viewCommand); stateStrategy.beforeApply(mState, viewCommand); }
      
      





やった! これで、ストラテジーのbeforeApplyメソッドがいつ実行されるかがわかります。ViewStateのメソッドへの対応する呼び出しの直後で、それだけです。 ダイビングを続けてください!

ビューがある場合:







3)toggleCheeseメソッドは1つずつプロキシされます。







4)afterApplyメソッドは、このチームのViewCommandsクラスに対して呼び出されます。 その中で、戦略を取得し、afterApplyメソッドを呼び出します。







ただし、 afterApplyはこの場合だけなく呼び出されます。 新しいView添付ファイルの場合にも呼び出されます。 このケースを見てみましょう。 添付ファイルビューはattachView(ビュービュー)を呼び出します







attachView(ビュービュー)メソッドは、 MvpDelegateクラスのonAttach()メソッドから呼び出されます。 これは非常に頻繁に呼び出されます: onStart()およびonResume()メソッドから。 ただし、ライブラリは、 アタッチされたビューに対してafterApplyが 1回呼び出されるようにします(以下のリストを参照)。









Mvpviewstate.attachview
 public void attachView(View view) { if (view == null) { throw new IllegalArgumentException("Mvp view must be not null"); } boolean isViewAdded = mViews.add(view); if (!isViewAdded) { return; } mInRestoreState.add(view); Set<ViewCommand<View>> currentState = mViewStates.get(view); currentState = currentState == null ? Collections.<ViewCommand<View>>emptySet() : currentState; restoreState(view, currentState); mViewStates.remove(view); mInRestoreState.remove(view); }
      
      





1)ビューがリストに追加されます。

2)このリストになかった場合、ビューはStateRestoring状態に転送されます







StateRestoring Stateが必要な理由



これにより、アクティビティ/ビュー/フラグメントは、状態が復元されていることを理解できます。 プレゼンターにはisInRestoreState()メソッドがあります。 このメカニズムは、一部のアクションを2回実行しないようにするために必要です(たとえば、アニメーションを開始してビューを目的の状態に変換する)。 これはvoidを返さない唯一のプレゼンターメソッドです。 このメソッドはプレゼンターに属します viewmvpDelegateは複数のプレゼンターを持つことができ、それらをこれらのクラスに配置すると衝突が発生します。







3)次に、状態が復元されます







afterApplyメソッドを数回呼び出すことができることに注意してください 。 カスタム戦略を作成するときは、これに注意してください。







戦略がどのように機能するかを知りました。実際にスキルを統合するときです。







カスタム戦略を作成する



作業スキーム

したがって、まずは、どのような戦略を取得するのかを理解しましょう。 表記に同意します。









このスキームは最初の部分のスキームに似ていますが、重要な違いがあります—タグの指定が表示されます。 コマンドにタグがない場合は、タグを指定せず、デフォルト値を受け入れたことを意味します-null







次の戦略を実装したいと思います。









プレゼンターがAddToEndSingleTagStrategyストラテジーでコマンド(2)を呼び出す場合:









ビューを再作成するとき:









実装

1)StateStrategyインターフェースを実装します。 これを行うには、beforeApplyおよびafterApplyメソッドを再定義します

2)beforeApplyメソッドの実装は、 AddToEndSingleStrategyクラスの同様のメソッドの実装に非常に似ています。

このタグを持つすべてのチームをキューから完全に削除したい、つまり 戦略は異なるがタグが類似しているチームでも削除されます。

したがって、 entry.class == incomingCommand.classの行の代わりに、 entry.tag == incomingCommand.tagを使用します。







Kotlin以外のユーザーへのコメント

Kotlinでは、==はJavaの.equalsと同等です。







AddToEndSingleStrategyとは異なり、キューで削除するコマンドがいくつかあるため、 ブレークラインを削除する必要があります。

3)コマンドを適用した後にキューを変更する必要がないため、 afterApplyメソッドの実装を空のままにします。







それで、私たちは何を得ました:







 class AddToEndSingleTagStrategy() : StateStrategy { override fun <View : MvpView> beforeApply( currentState: MutableList<ViewCommand<View>>, incomingCommand: ViewCommand<View>) { val iterator = currentState.iterator() while (iterator.hasNext()) { val entry = iterator.next() if (entry.tag == incomingCommand.tag) { iterator.remove() } } currentState.add(incomingCommand) } override fun <View : MvpView> afterApply( currentState: MutableList<ViewCommand<View>>, incomingCommand: ViewCommand<View>) { //Just do nothing } }
      
      





以上で、戦略の使用方法を説明することができます(以下のリストを参照)。







 interface LaunchView : MvpView { @StateStrategyType(AddToEndSingleStrategy::class, tag = BURGER_TAG) fun setBreadType(breadType: BreadType) @StateStrategyType(AddToEndSingleStrategy::class, tag = BURGER_TAG) fun toggleCheese(enable: Boolean) @StateStrategyType(AddToEndSingleTagStrategy::class, tag = BURGER_TAG) fun clearBurger(breadType: BreadType, cheeseSelected: Boolean) //  companion object { const val BURGER_TAG = "BURGER" } }
      
      





完全なコード例は、 Moxyリポジトリにあります。 これはサンプルであり、その中のソリューションはフレームワークの機能を説明するためだけに与えられていることを思い出してください







宛先..



他にカスタム戦略を使用できる理由:

1)前のコマンドを1つに接着します。

2)コマンドが可換でない場合(a•b!= B•a)、コマンドの実行順序を変更します。

3)現在のタグを含まないすべてのコマンドを破棄します。

4)..







コマンドを頻繁に使用するが、デフォルトのリストにない場合-書き込み、それらの追加について説明します。

コミュニティチャットで Moxyについて議論できます







記事とライブラリに関するコメントや提案を待っています;)










(I)以下、「私たち」はMoxyの著者です。Xanderblinovsenneco 、およびアドバイス、コメント、プルクエストを支援したコミュニティのすべての人たちです。 貢献者の完全なリストはここにあります。







(II)Kotlinで書かれたコードのリスト



All Articles