2

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

例如,我有一个 ConversationScreen,它将一个 conversationId 作为构造函数参数。我第一次创建 ConversationScreen 并将其添加到 Flow 时,它会创建 ConversationView,它会向自己注入一个 Presenter,该 Presenter 是使用传递给屏幕的 conversationId 创建的。如果我随后创建一个具有不同会话 ID 的新 ConversationScreen,当 ConversationView 请求 Presenter 时,Dagger 将返回旧的 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();
    }
}

对话屏幕

@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到 的另一个实例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基于一个字符串,如果您创建相同的路径,它将使用相同的名称,因为它基于路径的类名。

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

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 回答