私はここ数週間、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));
これでいいですか?つまり、アクティビティのコンテキストをプレゼンターに渡す必要はありませんが、アプリケーションのコンテキストはまだあります。
あなたがこの質問をしてからしばらく経ちましたが、とにかく答えを提供することは役に立つと思いました。プレゼンターは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インターフェイスです。このようにして、テスト中に実行時に非常に単純なモックを提供できます。通常の操作中に、具体的な実装が提供されます。
この質問はしばらく前に回答されました。MVPの定義がOPがコードで使用したものであると仮定すると、@ Jahnoldの回答は本当に良いです。
ただし、MVPは高レベルの概念であり、MVPの原則に従って多くの実装が可能であることを指摘しておく必要があります。猫の皮をむく方法は複数あります。
MVPの別の実装があります は、 AndroidはUI要素ではありません)のアクティビティ という考えに基づいており、Activity
およびFragment
はMVPプレゼンターとしてこの構成では、MVPプレゼンターはContext
に直接アクセスできます。
ところで、前述のMVPの実装でも、プレゼンターでContext
へのアクセスを取得するためにSharedPreferences
を使用しません-SharedPreferences
のラッパークラスを定義し、プレゼンターに注入します。
DBやネットワークなどのドメイン要素のほとんどは、コンテキストを構築する必要があります。 Viewにはモデルに関する知識がないため、ViewでThayを作成できません。その後、Presenterで作成する必要があります。 Daggerによって注入できますが、Contextを使用することもできます。コンテキストはPresenter xPで使用されます
ハックは、PresenterでContextを避けたい場合、これらのすべてのModelオブジェクトをContextから作成し、保存しないコンストラクターを作成するだけです。しかし、私の意見では、それは愚かです。 Android=の新しいJUnitはコンテキストにアクセスできます。
もう1つのハックは、Contextをヌル可能にすることです。ドメインオブジェクトには、コンテキストでヌルの場合にテストインスタンスを提供するメカニズムが必要です。私もこのハックが好きではありません。