web-dev-qa-db-ja.com

プレゼンターはアクティビティ/コンテキストの知識を持っているので、MVPパターンでは悪い考えですか?

私はここ数週間、MVPパターンをいじっていますが、serviceを開始してShared Preferencesにアクセスするためにコンテキストが必要になるところまで来ました。

MVPの目的は、ビューをロジックから切り離すことであり、context内にPresenterを含めると、その目的が損なわれる可能性があることを読みました(これについて間違っている場合は修正してください)。

現在、次のような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();
    }
}

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

ご覧のとおり、Shared Preferencesにアクセスできるように、コンテキストをActivityからPresenterに渡しました。プレゼンターにコンテキストを渡すことを非常に心配しています。これは大丈夫ですか?それとも他の方法でやるべきですか?

EDITが実装されたJahnoldの3番目の設定

インターフェースと実装はほとんどすべてなので、無視しましょう。これで、私はinjectingで、プレゼンターへのSharedpreferenceのインターフェイスになりました。ここに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));

これでいいですか?つまり、アクティビティのコンテキストをプレゼンターに渡す必要はありませんが、アプリケーションのコンテキストはまだあります。

55
remedy.

あなたがこの質問をしてからしばらく経ちましたが、とにかく答えを提供することは役に立つと思いました。プレゼンターはAndroid Context(または他のAndroidクラス)の概念を持たないことを強くお勧めします。Presenterコードを= Androidシステムコードを使用すると、システムコンポーネントのモックを作成することなくJVMでテストできます。

これを達成するには、3つの選択肢があると思います。

ビューからSharedPreferencesにアクセス

SharedPreferencesへのアクセスはnotビューアクションであるため、これは3つの中で最も好きではありません。ただし、アクティビティ内のAndroidシステムコードをプレゼンターから遠ざけます。ビューインターフェイスには次のメソッドがあります。

boolean isLoggedIn();

プレゼンターから呼び出すことができます。

Daggerを使用してSharedPreferencesを挿入

既にDaggerを使用してイベントバスを注入しているため、ObjectGraphにSharedPreferencesを追加し、ApplicationContextを使用して構築されたSharedPreferencesインスタンスを取得できます。これは、プレゼンターにコンテキストを渡すことなく、それらを取得しました。

このアプローチの欠点は、まだAndroidシステムクラス(SharedPreferences))を渡しているため、Presenterをテストするときにそれをモックする必要があることです。

SharePreferencesRepositoryインターフェイスの作成

これは、Presenter内からSharedPreferencesデータにアクセスするための私の推奨方法です。基本的に、SharedPreferencesをモデルとして扱い、そのためのリポジトリインターフェイスを持っています。

インターフェースは次のようになります。

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

}

次に、Daggerをプレゼンターに注入するのはSharedPreferencesRepositoryインターフェイスです。このようにして、テスト中に実行時に非常に単純なモックを提供できます。通常の操作中に、具体的な実装が提供されます。

71
Jahnold

この質問はしばらく前に回答されました。MVPの定義がOPがコードで使用したものであると仮定すると、@ Jahnoldの回答は本当に良いです。

ただし、MVPは高レベルの概念であり、MVPの原則に従って多くの実装が可能であることを指摘しておく必要があります。猫の皮をむく方法は複数あります。

MVPの別の実装があります は、 AndroidはUI要素ではありません)のアクティビティ という考えに基づいており、ActivityおよびFragmentはMVPプレゼンターとしてこの構成では、MVPプレゼンターはContextに直接アクセスできます。

ところで、前述のMVPの実装でも、プレゼンターでContextへのアクセスを取得するためにSharedPreferencesを使用しません-SharedPreferencesのラッパークラスを定義し、プレゼンターに注入します。

4
Vasiliy

DBやネットワークなどのドメイン要素のほとんどは、コンテキストを構築する必要があります。 Viewにはモデルに関する知識がないため、ViewでThayを作成できません。その後、Presenterで作成する必要があります。 Daggerによって注入できますが、Contextを使用することもできます。コンテキストはPresenter xPで使用されます

ハックは、PresenterでContextを避けたい場合、これらのすべてのModelオブジェクトをContextから作成し、保存しないコンストラクターを作成するだけです。しかし、私の意見では、それは愚かです。 Android=の新しいJUnitはコンテキストにアクセスできます。

もう1つのハックは、Contextをヌル可能にすることです。ドメインオブジェクトには、コンテキストでヌルの場合にテストインスタンスを提供するメカニズムが必要です。私もこのハックが好きではありません。

2
MarcinM