简体   繁体   English

主持人是否知道MVP模式中的活动/上下文是个坏主意?

[英]Does the presenter having knowledge of the Activity / Context a bad idea in the MVP pattern?

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模式,我已经到了需要上下文来启动service和访问Shared Preferences的地步。

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的目的是将视图与逻辑分离,并且Presenter中的context可能会破坏这个目的(如果我错了,请纠正我)。

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 . 如您所见,我将上下文从Activity传递到我的Presenter ,这样我就可以访问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 EDIT实施了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. 所以现在我injecting共享偏好的界面注入我的演示者。 Here's my code for the AppModule 这是我的AppModule代码

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: 当应用程序开始时,我确保用这行代码创建这个Object图:

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. 我的意思是我现在不必将活动的上下文传递给我的演示者,但我仍然有应用程序的上下文。

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). 我强烈建议演示者不应该有Android Context(或任何其他Android类)的概念。 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. 通过将Presenter代码与Android系统代码完全分离,您可以在JVM上对其进行测试,而无需模拟系统组件的复杂性。

To achieve this I think you have three options. 为实现这一点,我认为你有三个选择。

Access SharedPreferences from the View 从View中访问SharedPreferences

This is my least favourite of the three as accessing SharedPreferences is not a view action. 这是我最不喜欢的三个,因为访问SharedPreferences 不是一个视图操作。 However it does keep the Android system code in the Activity away from the Presenter. 但是,它确实使Activity中的Android系统代码远离Presenter。 In your view interface have a method: 在您的视图界面中有一个方法:

boolean isLoggedIn();

which can be called from the presenter. 可以从演示者调用。

Inject SharedPreferences Using Dagger 使用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. 由于您已经使用Dagger注入事件总线,因此可以将SharedPreferences添加到ObjectGraph中,这样就可以获得使用ApplicationContext构造的SharedPreferences实例。 This was you get the them without having to pass a Context into your presenter. 这是你得到它们而不必将Context传递给你的演示者。

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),并且当你想测试Presenter时必须模拟它。

Create a SharePreferencesRepository Interface 创建SharePreferencesRepository接口

This is my preferred method for accessing SharedPreferences data from within a Presenter. 这是我从Presenter中访问SharedPreferences数据的首选方法。 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. 然后将SharedPreferencesRepository接口与Dagger一起注入Presenter。 This way a very simple mock can be provided at runtime during tests. 这样,在测试期间可以在运行时提供非常简单的模拟。 During normal operation the concrete implementation is provided. 在正常操作期间,提供具体实施。

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. 这个问题在前一段时间得到了回答,假设MVP的定义是他在代码中使用的OP,那么@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原则 - 有不止一种方法可以使猫皮肤变形。

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. 还有另一种MVP实现 ,它基于以下思想: Android中的活动不是UI元素 ,它将ActivityFragment指定为MVP演示者。 In this configuration, MVP presenters have a direct access to Context . 在此配置中,MVP演示者可以直接访问Context

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实现中,我也不会使用Context来访问演示者中的SharedPreferences - 我仍然会为SharedPreferences定义一个包装类并将其注入到演示者中。

Most of the domain elements, like DB or network, needs Context to be built. 大多数域元素(如DB或网络)都需要构建Context。 Thay cannot be created in View because View cannot have any knowledge about Model. 无法在View中创建Thay,因为View无法了解Model。 They must be then created in Presenter. 然后必须在Presenter中创建它们。 They can be injected by Dagger, but is it also using Context. 它们可以由Dagger注入,但它也使用Context。 So Context is used in Presenter xP 因此,在Presenter xP中使用了Context

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. 黑客就是如果我们想避免在Presenter中使用Context,那么我们就可以创建从Context创建所有这些Model对象而不保存它的构造函数。 But in my opinion, it is stupid. 但在我看来,这是愚蠢的。 New JUnit in Android has access to Context. Android中的新JUnit可以访问Context。

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. 另一个hack是使Context可以为空,并且在域对象中应该有机制在上下文中为null时提供测试实例。 I also don't like this hack. 我也不喜欢这个黑客。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 演示者在 Android MVP 模式中具有处理程序 - Presenter having Handler in Android MVP pattern 将视图的引用传递给MVP模式中的Presenter是一种不好的做法吗? - Is a bad practice passing references of a View to a Presenter in MVP Pattern? Android MVP 从 Presenter 打开 Activity,反模式? - Android MVP open Activity from Presenter, anti-pattern? 我们应该检查Presenter或MVP模式的Activity中的视图可见性吗? - Should we check for view visibility in Presenter or Activity in MVP pattern? 每个视图都必须有 MVP 模式的演示者吗? - Does every view have to have presenter in MVP pattern? 使用保留的Fragments是否会打破MVP模式的思想,其中Fragment / Activity表示像View? - Does using retained Fragment breaks the idea of MVP pattern ,where Fragment/Activity represents like View? 我们是否需要用于Android MVP模式的登录以及Signup Activity的其他Presenter? - Do we need different Presenter for Login as well as Signup Activity in android MVP pattern? 在MVP模式下,Presenter应该是Android应用程序的活动性还是功能性 - In MVP pattern, should Presenter be activity-wise or functionality-wise of Android app Presenter中的资源/应用上下文(MVP架构) - Resources/App Context in Presenter (MVP architecture) Android MVP:在Presenter中安全使用Context - Android MVP: safe use Context in Presenter
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM