Подтвердить что ты не робот

Предоставляет ли ведущий знания о деятельности/Контексте плохую идею в шаблоне 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();
    }
}

Интерфейс презентатора 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 в my Presenter, чтобы я мог получить доступ к Shared Preferences. Я очень беспокоюсь о том, чтобы передать контекст в мой ведущий. Это нормально? Или я должен делать это по-другому?

РЕДАКТИРОВАННОЕ РЕГУЛИРОВАННОЕ ПРЕДЛОЖЕНИЕ 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));

Это нормально? Я имею в виду, что теперь мне не нужно передавать контекст из активности в мой ведущий, но у меня все еще есть контекст приложения.

4b9b3361

Ответ 1

Прошло некоторое время с тех пор, как вы задали этот вопрос, но я подумал, что было бы полезно дать ответ в любом случае. Я бы настоятельно сказал, что у ведущего не должно быть понятия Android Context (или любых других классов Android). Полностью отделяя ваш код презентатора от системного кода Android, вы можете протестировать его на JVM без усложнения системных компонентов.

Для этого я думаю, что у вас есть три варианта.

Доступ к SharedPreferences из представления

Это мой наименее любимый из трех, поскольку доступ к SharedPreferences - это не действие вида. Тем не менее, он сохраняет системный код Android в действии вдали от Presenter. В вашем интерфейсе просмотра есть метод:

boolean isLoggedIn();

который можно вызвать из ведущего.

Ввод общих разделов с использованием кинжала

Поскольку вы уже используете Dagger для ввода шины событий, вы можете добавить SharedPreferences в свой ObjectGraph и, таким образом, получите экземпляр SharedPreferences, который был создан с использованием ApplicationContext. Это вы получили их, не передавая Контекст в ваш ведущий.

Недостатком этого подхода является то, что вы все еще проходите в системном классе Android (SharedPreferences) и должны были бы издеваться над ним, когда хотите протестировать Presenter.

Создать интерфейс SharePreferencesRepository

Это мой предпочтительный метод для доступа к данным SharedPreferences из Presenter. В основном вы рассматриваете 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);
    }

}

Это интерфейс SharedPreferencesRepository, который вы затем вводите кинжалом в свой презентатор. Таким образом, во время тестов во время выполнения может быть предоставлен очень простой макет. При нормальной работе обеспечивается конкретная реализация.

Ответ 2

Этот вопрос был дан ответ некоторое время назад, и, полагая, что определение MVP - это то, что OP используется в его коде, ответ by @Jahnold действительно хорош.

Однако следует отметить, что MVP представляет собой концепцию высокого уровня, и в соответствии с принципами MVP может быть много реализаций - существует более одного способа скин-кота.

Существует еще одна реализация MVP, которая основана на идее, что Действия в Android не являются элементами пользовательского интерфейса, которые обозначают Activity и Fragment в качестве презентаторов MVP. В этой конфигурации ведущие MVP имеют прямой доступ к Context.

Кстати, даже в вышеупомянутой реализации MVP я бы не использовал Context, чтобы получить доступ к SharedPreferences в презентаторе - я бы все же определил класс-оболочку для SharedPreferences и ввел его в ведущий.

Ответ 3

Для большинства элементов домена, таких как БД или сеть, нужен Контекст для построения. Thay нельзя создать в представлении, потому что View не может иметь никаких знаний о модели. Затем они должны быть созданы в Presenter. Они могут быть введены кинжалом, но также он использует Context. Таким образом, контекст используется в Presenter xP

Взлом заключается в том, что если мы хотим избежать Context в Presenter, тогда мы можем просто создать конструктор, который создает все эти объекты Model из контекста и не сохраняет его. Но, на мой взгляд, это глупо. Новый JUnit в Android имеет доступ к контексту.

Другой взлом - это сделать Context nullable, а в объектах домена должен быть механизм для предоставления экземпляра тестирования в случае null в контексте. Мне тоже не нравится этот хак.