在MVP模式中,主持人是否有关于活动/背景的知识?

时间:2021-03-13 21:06:21

I've been playing around with the MVP pattern for a few weeks now and I've come to the point where I need context to start a service and access Shared Preferences.

我花了几周时间研究MVP模式,现在我需要上下文来启动服务并访问共享首选项。

I've read that the purpose of MVP is to decouple the view from the logic and having context within a Presenter may defeat that purpose (correct me if I'm wrong on this).

我读到过MVP的目的是将观点从逻辑中分离出来,而在演讲者内部设置上下文可能会破坏这个目的(如果我说错了,请纠正我)。

Currently, I have a LoginActivity that looks something like this:

目前,我有一个LoginActivity,看起来是这样的:

LoginActivity.java

LoginActivity.java

public class LoginActivity extends Activity implements ILoginView {

    private final String LOG_TAG = "LOGIN_ACTIVITY";

    @Inject
    ILoginPresenter mPresenter;
    @Bind(R.id.edit_login_password)
    EditText editLoginPassword;
    @Bind(R.id.edit_login_username)
    EditText editLoginUsername;
    @Bind(R.id.progress)
    ProgressBar mProgressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        MyApplication.getObjectGraphPresenters().inject(this);
        mPresenter.setLoginView(this, getApplicationContext());
    }

    @Override
    public void onStart() {
        mPresenter.onStart();
        ButterKnife.bind(this);
        super.onStart();
    }

    @Override
    public void onResume() {
        mPresenter.onResume();
        super.onResume();
    }

    @Override
    public void onPause() {
        mPresenter.onPause();
        super.onPause();
    }

    @Override
    public void onStop() {
        mPresenter.onStop();
        super.onStop();
    }

    @Override
    public void onDestroy() {
        ButterKnife.unbind(this);
        super.onDestroy();
    }

    @OnClick(R.id.button_login)
    public void onClickLogin(View view) {
        mPresenter.validateCredentials(editLoginUsername.getText().toString(),
                editLoginPassword.getText().toString());
    }

    @Override public void showProgress() { mProgressBar.setVisibility(View.VISIBLE); }

    @Override public void hideProgress() {
        mProgressBar.setVisibility(View.GONE);
    }

    @Override public void setUsernameError() { editLoginUsername.setError("Username Error"); }

    @Override public void setPasswordError() { editLoginPassword.setError("Password Error"); }

    @Override public void navigateToHome() {
        startActivity(new Intent(this, HomeActivity.class));
        finish();
    }
}

Presenter Interface ILoginPresenter.java

主持人接口ILoginPresenter.java

public interface ILoginPresenter {
    public void validateCredentials(String username, String password);


    public void onUsernameError();

    public void onPasswordError();

    public void onSuccess(LoginEvent event);

    public void setLoginView(ILoginView loginView, Context context);

    public void onResume();

    public void onPause();

    public void onStart();

    public void onStop();
}

Lastly, my Presenter:

最后,我的主持人:

LoginPresenterImpl.java

LoginPresenterImpl.java

public class LoginPresenterImpl implements ILoginPresenter {

    @Inject
    Bus bus;

    private final String LOG_TAG = "LOGIN_PRESENTER";
    private ILoginView loginView;
    private Context context;
    private LoginInteractorImpl loginInteractor;

    public LoginPresenterImpl() {
        MyApplication.getObjectGraph().inject(this);
        this.loginInteractor = new LoginInteractorImpl();
    }

    /**
     * This method is set by the activity so that way we have context of the interface
     * for the activity while being able to inject this presenter into the activity.
     *
     * @param loginView
     */
    @Override
    public void setLoginView(ILoginView loginView, Context context) {
        this.loginView = loginView;
        this.context = context;

        if(SessionUtil.isLoggedIn(this.context)) {
            Log.i(LOG_TAG, "User logged in already");
            this.loginView.navigateToHome();
        }
    }

    @Override
    public void validateCredentials(String username, String password) {
        loginView.showProgress();
        loginInteractor.login(username, password, this);
    }

    @Override
    public void onUsernameError() {
        loginView.setUsernameError();
        loginView.hideProgress();
    }

    @Override
    public void onPasswordError() {
        loginView.setPasswordError();
        loginView.hideProgress();
    }

    @Subscribe
    @Override
    public void onSuccess(LoginEvent event) {
        if (event.getIsSuccess()) {
            SharedPreferences.Editor editor =
                    context.getSharedPreferences(SharedPrefs.LOGIN_PREFERENCES
                            .isLoggedIn, 0).edit();
            editor.putString("logged_in", "true");
            editor.commit();

            loginView.navigateToHome();
            loginView.hideProgress();
        }
    }

    @Override
    public void onStart() {
        bus.register(this);
    }

    @Override
    public void onStop() {
        bus.unregister(this);

    }

    @Override
    public void onPause() {

    }

    @Override
    public void onResume() {
    }
}

As you can see, I passed the context from the Activity into my Presenter just so I can access the Shared Preferences. I'm quite worried about passing the context into my presenter. Is this an okay thing to do? Or should I be doing it some other way?

如您所见,我将活动中的上下文传递给了我的演示者,以便访问共享首选项。我很担心把上下文传递给我的演示者。这样做可以吗?或者我应该用别的方式来做?

EDIT Implemented Jahnold's 3rd preference

编辑实现Jahnold的第三个首选项

So let's ignore the interface and implementation because it's pretty much the entire thing. So now I'm injecting the interface for the Sharedpreference into my presenter. Here's my code for the AppModule

让我们忽略接口和实现因为它几乎是整个东西。现在我将Sharedpreference接口注入到演示程序中。这是我的AppModule代码

AppModule.java

AppModule.java

@Module(library = true,
    injects = {
            LoginInteractorImpl.class,
            LoginPresenterImpl.class,
            HomeInteractorImpl.class,
            HomePresenterImpl.class,

    }
)
public class AppModule {

    private MyApplication application;

    public AppModule(MyApplication application) {
        this.application = application;
    }

    @Provides
    @Singleton
    public RestClient getRestClient() {
        return new RestClient();
    }

    @Provides
    @Singleton
    public Bus getBus() {
        return new Bus(ThreadEnforcer.ANY);
    }

    @Provides
    @Singleton
    public ISharedPreferencesRepository getSharedPreferenceRepository() { return new SharedPreferencesRepositoryImpl(application.getBaseContext()); }

    }
}

The way I get the context is from MyApplication.java

我获取上下文的方法来自MyApplication.java

When the application begins, I make sure to create this Object graph with this line of code:

当应用程序开始时,我确保用这行代码创建这个对象图:

objectGraph = ObjectGraph.create(new AppModule(this));

Is this okay? I mean I now don't have to pass the context from the activity into my presenter, but I still have context of the application.

这是好的吗?我的意思是,我现在不需要将活动中的上下文传递给演示者,但是我仍然有应用程序的上下文。

3 个解决方案

#1


63  

It has been some time since you asked this question but I thought it would be useful to provide an answer anyway. I would strongly suggest that the presenter should have no concept of the Android Context (or any other Android classes). By completely separating your Presenter code from the Android system code you are able to test it on the JVM without the complication of mocking system components.

你问这个问题已经有一段时间了,但我认为无论如何提供一个答案是有用的。我强烈建议演讲者不要考虑Android上下文(或任何其他Android类)。通过将演示程序代码与Android系统代码完全分离,您就能够在JVM上测试它,而不需要模拟系统组件。

To achieve this I think you have three options.

要做到这一点,我认为你有三个选择。

Access SharedPreferences from the View

从视图中访问SharedPreferences

This is my least favourite of the three as accessing SharedPreferences is not a view action. However it does keep the Android system code in the Activity away from the Presenter. In your view interface have a method:

这是我最不喜欢的,因为访问SharedPreferences不是视图操作。然而,它确实使活动中的Android系统代码远离演示者。在你的视图界面有一个方法:

boolean isLoggedIn();

which can be called from the presenter.

可以从演讲者那里调用。

Inject SharedPreferences Using Dagger

注射使用SharedPreferences匕首

As you are already using Dagger to inject the event bus you could add SharedPreferences to your ObjectGraph and as such would get a SharedPreferences instance which has been constructed using the ApplicationContext. This was you get the them without having to pass a Context into your presenter.

由于您已经使用了Dagger来注入事件总线,您可以向ObjectGraph添加SharedPreferences,这样就会得到一个使用ApplicationContext构造的SharedPreferences实例。这是你得到它们,而不需要将上下文传递给你的演示者。

The downside of this approach is that you are still passing in an Android system class (SharedPreferences) and would have to mock it when you wanted to test the Presenter.

这种方法的缺点是您仍然在传递一个Android系统类(SharedPreferences),并且当您想要测试演示者时必须模仿它。

Create a SharePreferencesRepository Interface

创建一个SharePreferencesRepository接口

This is my preferred method for accessing SharedPreferences data from within a Presenter. Basically you treat SharedPreferences as a model and have a repository interface for it.

这是我从演示程序内部访问共享首选项数据的首选方法。基本上,您将SharedPreferences视为一个模型,并为其提供一个存储库接口。

Your interface would be similar to:

您的界面将类似于:

public interface SharedPreferencesRepository {

    boolean isLoggedIn();
}

You can then have a concrete implementation of this:

然后你可以有一个具体的实施:

public class SharedPreferencesRepositoryImpl implements SharedPreferencesRepository {

    private SharedPreferences prefs;

    public SharedPreferencesRepositoryImpl(Context context) {

        prefs = PreferenceManager.getDefaultSharedPreferences(context);
    }

    @Override
    public boolean isLoggedIn() {

        return prefs.getBoolean(Constants.IS_LOGGED_IN, false);
    }

}

It is the SharedPreferencesRepository interface that you then inject with Dagger into your Presenter. This way a very simple mock can be provided at runtime during tests. During normal operation the concrete implementation is provided.

它是SharedPreferencesRepository接口,然后您可以将Dagger插入到演示者中。这样,在测试期间可以在运行时提供一个非常简单的mock。在正常运行期间,提供了具体的实现。

#2


3  

This question was answered some time ago, and, assuming that the definition of MVP is what OP used in his code, the answer by @Jahnold is really good.

这个问题在一段时间前就得到了答案,假设OP在他的代码中使用的是MVP的定义,@Jahnold的答案是非常好的。

However, it should be pointed out that MVP is a high level concept, and there can be many implementations following MVP principles - there is more than one way to skin the cat.

然而,应该指出的是,MVP是一个高水平的概念,并且可以有许多实现遵循MVP原则——有不止一种方法可以使cat脱皮。

There is another implementation of MVP, which is based on the idea that Activities in Android are not UI Elements, which designates Activity and Fragment as MVP presenters. In this configuration, MVP presenters have a direct access to Context.

还有另一个MVP的实现,它基于在Android中的活动不是UI元素的想法,它将活动和片段指定为MVP演讲者。在这个配置中,MVP主持人可以直接访问上下文。

By the way, even in the aforementioned implementation of MVP, I wouldn't use Context in order to get access to SharedPreferences in presenter - I would still define a wrapper class for SharedPreferences and inject it into presenter.

顺便说一下,即使在前面提到的MVP实现中,我也不会使用上下文来访问presenter中的SharedPreferences——我仍然会为SharedPreferences定义一个包装器类并将其注入到presenter中。

#3


1  

Most of the domain elements, like DB or network, needs Context to be built. Thay cannot be created in View because View cannot have any knowledge about Model. They must be then created in Presenter. They can be injected by Dagger, but is it also using Context. So Context is used in Presenter xP

大多数域元素,如DB或网络,都需要构建上下文。视图不能在视图中创建,因为视图对模型没有任何了解。它们必须在演示者中创建。他们可以被匕首注射,但它也使用上下文。所以Context在演示xP中使用

The hack is that if we want to avoid Context in Presenter then we can just make the constructor that is creating all these Model objects from Context and not saving it. But in my opinion, it is stupid. New JUnit in Android has access to Context.

如果我们想避免在演示程序中使用上下文,那么我们就可以创建构造函数,从上下文创建所有这些模型对象,而不是保存它。但在我看来,这是愚蠢的。Android中的新JUnit可以访问上下文。

Another hack is to make Context nullable, and in domain objects there should be mechanism to provide testing instance in case of null in context. I also don't like this hack.

另一种方法是使上下文无效,在域对象中应该有机制来提供测试实例,以在上下文环境中为空。我也不喜欢这个黑客。

#1


63  

It has been some time since you asked this question but I thought it would be useful to provide an answer anyway. I would strongly suggest that the presenter should have no concept of the Android Context (or any other Android classes). By completely separating your Presenter code from the Android system code you are able to test it on the JVM without the complication of mocking system components.

你问这个问题已经有一段时间了,但我认为无论如何提供一个答案是有用的。我强烈建议演讲者不要考虑Android上下文(或任何其他Android类)。通过将演示程序代码与Android系统代码完全分离,您就能够在JVM上测试它,而不需要模拟系统组件。

To achieve this I think you have three options.

要做到这一点,我认为你有三个选择。

Access SharedPreferences from the View

从视图中访问SharedPreferences

This is my least favourite of the three as accessing SharedPreferences is not a view action. However it does keep the Android system code in the Activity away from the Presenter. In your view interface have a method:

这是我最不喜欢的,因为访问SharedPreferences不是视图操作。然而,它确实使活动中的Android系统代码远离演示者。在你的视图界面有一个方法:

boolean isLoggedIn();

which can be called from the presenter.

可以从演讲者那里调用。

Inject SharedPreferences Using Dagger

注射使用SharedPreferences匕首

As you are already using Dagger to inject the event bus you could add SharedPreferences to your ObjectGraph and as such would get a SharedPreferences instance which has been constructed using the ApplicationContext. This was you get the them without having to pass a Context into your presenter.

由于您已经使用了Dagger来注入事件总线,您可以向ObjectGraph添加SharedPreferences,这样就会得到一个使用ApplicationContext构造的SharedPreferences实例。这是你得到它们,而不需要将上下文传递给你的演示者。

The downside of this approach is that you are still passing in an Android system class (SharedPreferences) and would have to mock it when you wanted to test the Presenter.

这种方法的缺点是您仍然在传递一个Android系统类(SharedPreferences),并且当您想要测试演示者时必须模仿它。

Create a SharePreferencesRepository Interface

创建一个SharePreferencesRepository接口

This is my preferred method for accessing SharedPreferences data from within a Presenter. Basically you treat SharedPreferences as a model and have a repository interface for it.

这是我从演示程序内部访问共享首选项数据的首选方法。基本上,您将SharedPreferences视为一个模型,并为其提供一个存储库接口。

Your interface would be similar to:

您的界面将类似于:

public interface SharedPreferencesRepository {

    boolean isLoggedIn();
}

You can then have a concrete implementation of this:

然后你可以有一个具体的实施:

public class SharedPreferencesRepositoryImpl implements SharedPreferencesRepository {

    private SharedPreferences prefs;

    public SharedPreferencesRepositoryImpl(Context context) {

        prefs = PreferenceManager.getDefaultSharedPreferences(context);
    }

    @Override
    public boolean isLoggedIn() {

        return prefs.getBoolean(Constants.IS_LOGGED_IN, false);
    }

}

It is the SharedPreferencesRepository interface that you then inject with Dagger into your Presenter. This way a very simple mock can be provided at runtime during tests. During normal operation the concrete implementation is provided.

它是SharedPreferencesRepository接口,然后您可以将Dagger插入到演示者中。这样,在测试期间可以在运行时提供一个非常简单的mock。在正常运行期间,提供了具体的实现。

#2


3  

This question was answered some time ago, and, assuming that the definition of MVP is what OP used in his code, the answer by @Jahnold is really good.

这个问题在一段时间前就得到了答案,假设OP在他的代码中使用的是MVP的定义,@Jahnold的答案是非常好的。

However, it should be pointed out that MVP is a high level concept, and there can be many implementations following MVP principles - there is more than one way to skin the cat.

然而,应该指出的是,MVP是一个高水平的概念,并且可以有许多实现遵循MVP原则——有不止一种方法可以使cat脱皮。

There is another implementation of MVP, which is based on the idea that Activities in Android are not UI Elements, which designates Activity and Fragment as MVP presenters. In this configuration, MVP presenters have a direct access to Context.

还有另一个MVP的实现,它基于在Android中的活动不是UI元素的想法,它将活动和片段指定为MVP演讲者。在这个配置中,MVP主持人可以直接访问上下文。

By the way, even in the aforementioned implementation of MVP, I wouldn't use Context in order to get access to SharedPreferences in presenter - I would still define a wrapper class for SharedPreferences and inject it into presenter.

顺便说一下,即使在前面提到的MVP实现中,我也不会使用上下文来访问presenter中的SharedPreferences——我仍然会为SharedPreferences定义一个包装器类并将其注入到presenter中。

#3


1  

Most of the domain elements, like DB or network, needs Context to be built. Thay cannot be created in View because View cannot have any knowledge about Model. They must be then created in Presenter. They can be injected by Dagger, but is it also using Context. So Context is used in Presenter xP

大多数域元素,如DB或网络,都需要构建上下文。视图不能在视图中创建,因为视图对模型没有任何了解。它们必须在演示者中创建。他们可以被匕首注射,但它也使用上下文。所以Context在演示xP中使用

The hack is that if we want to avoid Context in Presenter then we can just make the constructor that is creating all these Model objects from Context and not saving it. But in my opinion, it is stupid. New JUnit in Android has access to Context.

如果我们想避免在演示程序中使用上下文,那么我们就可以创建构造函数,从上下文创建所有这些模型对象,而不是保存它。但在我看来,这是愚蠢的。Android中的新JUnit可以访问上下文。

Another hack is to make Context nullable, and in domain objects there should be mechanism to provide testing instance in case of null in context. I also don't like this hack.

另一种方法是使上下文无效,在域对象中应该有机制来提供测试实例,以在上下文环境中为空。我也不喜欢这个黑客。