Presenter从Android MVP模式中打开Activity,这是反模式吗?

34

如果我在Presenter层打开一个Activity,这算是反模式吗?

如果是的话,我应该从View层管理应用程序的导航吗?

4个回答

39
是的,这是一种反MVP模式。基于MVP中的被动视图,您失去了测试性,因为您不必在Presenter中处理Android框架。
因此,最好从View层管理应用程序的导航。
class MyPresenter {
    MyPresenter.View view;

    void backButtonClicked() {
        view.navigateToHomeScreen();
    }

    public interface View {
        void navigateToHomeScreen();
    }
}

class MyActivity extends Activity implements MyPresenter.View {
    @Override
    void navigateToHomeScreen() {
        startActivity(...)
    }

    @OnClick(R.id.my_button)
    void onClick() {
        presenter.backButtonClicked();
    }
} 

另外,这种方式的另一个优点是容易将活动替换为片段或视图。

编辑1:

Morgwai 表示这种方法会破坏关注点分离和单一责任,但你不能在所有地方都保持单一责任。有时需要违反它。下面是 Google 提供的 MVP 示例:

TaskDetailPresenter 调用 ShowEditTask,后者负责在 TaskDetailFragment 中打开新的 Activity

但也可以使用命令模式, 这是一种更好的方法。

interface NavigationCommand {
    void navigate();
}

因此,当Presenter需要时会使用它。


1
如果我想通过extras传递一些数据怎么办?这不会再次违反MVP模式吗?因为视图不应该知道任何关于模型的信息。 - cylon
在我看来,视图应该仅包含更新当前 UI 屏幕的方法,导航不应该是视图的责任,因为这会违反关注点分离原则。在经典的 MVP 中,当前 presenter 决定将控制权传递给哪个其他 presenter(通常使用消息总线),然后目标 presenter 将其视图放置在“画布”上。在 Android 中,通常使用 activity 或 fragment 对象进行导航,由于视图通常也是由 fragment 或 activity 实现的,因此视图对象通常也用于执行导航。但是,在我看来,这明显违反了关注点分离原则。 - morgwai
1
先生,您是否有Android MVP + Command模式的示例@SaeedMasoumi? - ericn

10
正如我在对已被接受的答案的评论中所写,我认为从视图层管理导航是明显违反分离关注点规则的:视图应仅包含更新当前UI界面的方法。
问题源于Android平台设计,因为ActivityFragment类既包含操作UI屏幕的方法,还包含发送意图对象以启动其他活动(如startActivity)的方法。
解决此问题的一种干净方法是创建一些Navigator接口,其中包含与导航相关的方法,并使活动实现它并将其注入到演示者中。这样,至少从演示者的角度来看,导航和UI操作将被分开。但是,从活动的角度来看可能会很奇怪:现在他们经常同时实现两个接口(导航和视图)并将其引用传递给演示者两次。如果因此你决定从你的视图层管理导航,那么至少要将用于导航的方法与用于操纵UI的方法分开:永远不要在同一个方法中执行导航和UI操作。

1
您是否有实现此导航器接口的示例项目? - Etienne Lawlor

7

在我看来,最好从视图层打开一个活动。我希望Presenter尽可能少地了解Activity。

如果有某些条件需要启动活动,您可以使用类似以下代码:

public class Presenter {

    private ViewsPresentation mViewsPresentation;

    public void someButtonClicked() {
        if (/*some condition*/) {
            mViewsPresentation.startFirstActivity();
        } else {
            mViewsPresentation.startSecondActivity();
        }
    }

    public interface ViewsPresentation {
        void startFirstActivity();
        void startSecondActivity();
    }

}

4
我已经制作了这个解决方案(用Kotlin编写):
我创建了一个名为ViewNavigator的接口。
interface ViewNavigator {
    fun navigateTo(target: Class<*>)
}

然后我实现了视图接口。
interface View : ViewNavigator {
    //...
}

然后实际视图(即活动)可以重写 navigateTo 函数。

override fun navigateTo(target: Class<*>) {
    startActivity(Intent(this, target))
}

因此,每当我想导航到任何活动时,我只需在Presenter类中编写即可。例如:

override fun onAnimationFinished() {
    view.navigateTo(HomeActivity::class.java)
}

我看到没有人评论或点赞您的解决方案。非常感谢您提供的示例。我正在开发一个应用程序,之前一直困扰于这个问题的解决方案,而您的建议是我找到的最好的解决方案。 - ThomasFromUganda
1
非常感激!由于我的声望低于50,所以我不能早些时候回复:D - Mohamed Medhat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接