如何防止 Mortar 范围在屏幕上持久存在?

How do I prevent Mortar scopes from persisting across screens?

我有一个使用 Mortar/Flow 和 Dagger 2 设置的应用程序。除了我在同一 class 的两个视图之间切换时,它似乎可以正常工作。新视图以先前视图的演示者结束。

例如,我有一个将 conversationId 作为构造函数参数的 ConversationScreen。我第一次创建 ConversationScreen 并将其添加到 Flow 时,它创建了 ConversationView,它向自己注入了 Presenter,Presenter 是使用传递到屏幕的 conversationId 创建的。如果我随后创建一个具有不同 conversationId 的新 ConversationScreen,当 ConversationView 要求 Presenter 时,Dagger returns 旧的 Presenter,因为范围尚未在之前的 ConversationScreen 上关闭。

有没有办法让我在设置新屏幕之前手动关闭前一个屏幕的范围?还是我一开始就设置了错误的范围?

对话视图

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();
    }
}

ConversationScreen

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

如果您使用 Mortar/Flow 示例项目中的默认 ScreenScoperPathContextFactory classes,您将看到要创建的新作用域的名称是屏幕名称 class.

因为您想从 ConversationScreen 的一个实例导航到 ConversationScreen 的另一个实例,新作用域的名称将与先前作用域的名称相同。因此,您不会创建新的 Mortar 瞄准镜,而只是重复使用之前的瞄准镜,这意味着重复使用同一个演示器。

您需要更改新作用域的命名策略。不要只使用新屏幕的名称 class,而是添加其他名称。
最简单的解决方法是使用实​​例标识符:myScreen.toString()

另一个更好的解决方法是跟踪 screen/scope 名称。 以下示例摘自 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));
}

在其他地方,如果新作用域被推入或作用域被销毁,我是 incrementing/decrementing 计数器。

ScreenScoper 中的范围基于一个字符串,如果您创建相同的路径,它将使用相同的名称,因为它基于您路径的 class 名称。

考虑到我在我的 Dagger2 驱动项目中没有使用 @ModuleFactory,我通过从 ScreenScoper 中移除一些噪音解决了这个问题。

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;
    }
}