android - 在 MVP 模式中,了解 Activity/上下文的演示者是不是一个坏主意?

标签 android mvp android-sharedpreferences

我已经用 MVP 模式玩了几个星期了,现在我已经到了需要上下文来启动 service 和访问 Shared Preferences.

我读过 MVP 的目的是将 View 与逻辑分离,并且在 Presenter 中包含 context 可能会破坏该目的(如果我'我错了)。

目前,我有一个如下所示的 LoginActivity:

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

演示者界面 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();
}

最后,我的演示者:

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

如您所见,我将 Activity 的上下文传递到我的 Presenter 中,这样我就可以访问 Shared Preferences。我很担心将上下文传递给我的演示者。这是一件好事吗?还是我应该以其他方式来做?

EDIT 实现了 Jahnold 的第三个偏好

所以让我们忽略接口(interface)和实现,因为它几乎就是全部。所以现在我将 Sharedpreference 的接口(interface)注入(inject)到我的演示者中。这是我的 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()); }

    }
}

我获取上下文的方式是从 MyApplication.java

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

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

这样好吗?我的意思是我现在不必将 Activity 的上下文传递给我的演示者,但我仍然拥有应用程序的上下文。

最佳答案

您问这个问题已经有一段时间了,但我认为无论如何提供答案都会很有用。我强烈建议演示者不应该有 Android 上下文(或任何其他 Android 类)的概念。通过将 Presenter 代码与 Android 系统代码完全分离,您可以在 JVM 上对其进行测试,而无需复杂的模拟系统组件。

要实现这一点,我认为您有三个选择。

从 View 访问 SharedPreferences

这是三者中我最不喜欢的,因为访问 SharedPreferences 不是 View 操作。但是,它确实使 Activity 中的 Android 系统代码远离 Presenter。在你的 View 界面中有一个方法:

boolean isLoggedIn();

可以从演示者那里调用。

使用 Dagger 注入(inject) SharedPreferences

由于您已经在使用 Dagger 注入(inject)事件总线,您可以将 SharedPreferences 添加到您的 ObjectGraph 中,因此将获得一个使用 ApplicationContext 构造的 SharedPreferences 实例。这是您无需将 Context 传递给您的演示者即可获得它们。

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

创建 SharePreferencesRepository 接口(interface)

这是我从 Presenter 中访问 SharedPreferences 数据的首选方法。基本上,您将 SharedPreferences 视为模型并为其提供存储库接口(interface)。

您的界面类似于:

public interface SharedPreferencesRepository {

    boolean isLoggedIn();
}

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

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

}

它是 SharedPreferencesRepository 接口(interface),然后你用 Dagger 将它注入(inject)到你的 Presenter 中。这样,可以在测试期间在运行时提供一个非常简单的模拟。正常运行时提供具体实现。

关于android - 在 MVP 模式中,了解 Activity/上下文的演示者是不是一个坏主意?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34303510/

相关文章:

android - java.lang.Boolean 无法转换为 java.lang.Integer SharedPreference

android - SharedPreferences 值在新实例后返回 null

android - 如何访问android中其他应用程序的共享偏好数据?

android - 错误 XA0000 : Could not determine API level for $(TargetFrameworkVersion) of 'v7.1'

android - 如何在执行另一个 Completable 后链接 Completable 列表

使用 MVP 的 VBA 设置对话框 - 我需要模型吗?

android - 为什么 MVC 中的单元测试比 MVP 和 MVVM 更难

android - 将单选按钮数据发送到下一个 Activity

安卓 WebView : On click of webview new activity should open

两个 fragment 共享相同数据的 Android MVP