Programing

발표자가 활동 / 컨텍스트에 대한 지식을 가지고있는 것이 MVP 패턴에서 나쁜 생각입니까?

lottogame 2020. 12. 31. 07:53
반응형

발표자가 활동 / 컨텍스트에 대한 지식을 가지고있는 것이 MVP 패턴에서 나쁜 생각입니까?


저는 지금까지 몇 주 동안 MVP 패턴을 가지고 놀았으며 a를 시작 service하고 액세스 하려면 컨텍스트가 필요한 지점에 도달했습니다 Shared Preferences.

나는 MVP의 목적은 로직에서보기를 분리하는 것입니다 읽었습니다와 가진 contextA가 내 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();
    }
}

발표자 인터페이스 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. 발표자에게 컨텍스트를 전달하는 것이 매우 걱정됩니다. 이게 괜찮습니까? 아니면 다른 방법으로해야합니까?

편집 Jahnold의 세 번째 선호도 구현

따라서 인터페이스와 구현은 거의 모든 것이기 때문에 무시합시다. 이제 저는 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));

괜찮아? 이제 활동에서 발표자에게 컨텍스트를 전달할 필요가 없지만 여전히 응용 프로그램의 컨텍스트가 있습니다.


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.

To achieve this I think you have three options.

Access SharedPreferences from the View

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:

boolean isLoggedIn();

which can be called from the presenter.

Inject SharedPreferences Using Dagger

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.

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.

Create a SharePreferencesRepository Interface

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.

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.


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.

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.

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.

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.


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

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.

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.

ReferenceURL : https://stackoverflow.com/questions/34303510/does-the-presenter-having-knowledge-of-the-activity-context-a-bad-idea-in-the

반응형