2

Mortar/Flow と Dagger 2 を使用してアプリをセットアップしました。同じクラスの 2 つのビューを切り替える場合を除いて、機能しているようです。新しいビューは、前のビューのプレゼンターで終了します。

たとえば、conversationId をコンストラクター引数として受け取る ConversationScreen があります。初めて ConversationScreen を作成して Flow に追加すると、ConversationView が作成され、画面に渡された conversationId で作成された Presenter が挿入されます。次に、別の conversationId で新しい ConversationScreen を作成すると、ConversationView がプレゼンターを要求すると、以前の ConversationScreen でスコープがまだ閉じられていないため、Dagger は古いプレゼンターを返します。

新しい画面をセットアップする前に、前の画面のスコープを手動で閉じる方法はありますか? それとも、最初から間違ったスコープを設定しただけですか?

会話ビュー

public class ConversationView extends RelativeLayout {
    @Inject
    ConversationScreen.Presenter presenter;

    public ConversationView(Context context, AttributeSet attrs) {
        super(context, attrs);
        DaggerService.<ConversationScreen.Component>getDaggerComponent(context).inject(this);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        presenter.takeView(this);
    }

    @Override
    protected void onDetachedFromWindow() {
        presenter.dropView(this);
        super.onDetachedFromWindow();
    }
}

会話画面

@Layout(R.layout.screen_conversation)
public class ConversationScreen extends Paths.ConversationPath implements ScreenComponentFactory<SomeComponent> {
    public ConversationScreen(String conversationId) {
        super(conversationId);
    }

    @Override
    public String getTitle() {
        title = Conversation.get(conversationId).getTitle();
    }

    @Override
    public Object createComponent(SomeComponent parent) {
        return DaggerConversationScreen_Component.builder()
                .someComponent(parent)
                .conversationModule(new ConversationModule())
                .build();
    }

    @dagger.Component(
            dependencies = SomeComponent.class,
            modules = ConversationModule.class
    )

    @DaggerScope(Component.class)
    public interface Component {
        void inject(ConversationView conversationView);
    }

    @DaggerScope(Component.class)
    @dagger.Module
    public class ConversationModule {
        @Provides
        @DaggerScope(Component.class)
        Presenter providePresenter() {
            return new Presenter(conversationId);
        }
    }

    @DaggerScope(Component.class)
    static public class Presenter extends BasePresenter<ConversationView> {
        private String conversationId;

        @Inject
        Presenter(String conversationId) {
            this.conversationId = conversationId;
        }

        @Override
        protected void onLoad(Bundle savedInstanceState) {
            super.onLoad(savedInstanceState);
            bindData();
        }

        void bindData() {
          // Show the messages in the conversation
        }
    }
}
4

2 に答える 2

2

Mortar/Flow サンプル プロジェクトのデフォルトクラスScreenScoperPathContextFactoryクラスを使用すると、作成する新しいスコープの名前が Screen クラスの名前であることがわかります。

ConversationScreenの 1 つのインスタンスから の別のインスタンスにナビゲートする必要があるためConversationScreen、新しいスコープの名前は以前のスコープの名前と同じになります。したがって、新しい Mortar スコープを作成するのではなく、以前のものを再利用するだけです。つまり、同じプレゼンターを再利用します。

必要なのは、新しいスコープの命名ポリシーを変更することです。新しい画面クラスの名前だけを使用するのではなく、別のものを追加します。
最も簡単な修正は、インスタンス識別子を使用することです: myScreen.toString().

別のより良い修正は、スクリーン/スコープ名を追跡することです。https://github.com/lukaspili/Mortar-architectから抽出された次の例

class EntryCounter {

   private final SimpleArrayMap<Class, Integer> ids = new SimpleArrayMap<>();

   int get(History.Entry entry) {
       Class cls = entry.path.getClass();
       return ids.containsKey(cls) ? ids.get(cls) : 0;
   }

   void increment(History.Entry entry) {
       update(entry, true);
   }

   void decrement(History.Entry entry) {
       update(entry, false);
   }

   private void update(History.Entry entry, boolean increment) {
       Class cls = entry.path.getClass();
       int id = ids.containsKey(cls) ? ids.get(cls) : 0;
       ids.put(cls, id + (increment ? 1 : -1));
   }
}

次に、新しいスコープを作成するときにこのカウンターを使用します。

private ScopedEntry buildScopedEntry(History.Entry entry) {
    String scopeName = String.format("ARCHITECT_SCOPE_%s_%d", entry.path.getClass().getName(), entryCounter.get(entry));
    return new ScopedEntry(entry, MortarFactory.createScope(navigator.getScope(), entry.path, scopeName));
}

また、他の場所では、新しいスコープがプッシュされた場合、またはスコープが破棄された場合に、カウンターをインクリメント/デクリメントしています。

于 2015-10-16T05:32:56.337 に答える
2

のスコープScreenScoperは文字列に基づいています。同じパスを作成すると、パスのクラス名に基づいているため、同じ名前が使用されます。

とにかく Dagger2 駆動のプロジェクトで使用していないことを考慮して、 ScreenScoperからノイズを除去することでこれを解決しました。@ModuleFactory

public abstract class BasePath
        extends Path {
    public abstract int getLayout();

    public abstract Object createComponent();

    public abstract String getScopeName();
}

public class ScreenScoper {
    public MortarScope getScreenScope(Context context, String name, Object screen) {
        MortarScope parentScope = MortarScope.getScope(context);
        return getScreenScope(parentScope, name, screen);
    }

    /**
     * Finds or creates the scope for the given screen.
     */
    public MortarScope getScreenScope(MortarScope parentScope, final String name, final Object screen) {
        MortarScope childScope = parentScope.findChild(name);
        if (childScope == null) {
            BasePath basePath = (BasePath) screen;
            childScope = parentScope.buildChild()
                    .withService(DaggerService.TAG, basePath.createComponent())
                    .build(name);
        }
        return childScope;
    }
}
于 2015-10-16T17:16:05.190 に答える