studio medium guidelines explicacion example course android mvp android-sharedpreferences

medium - mvp android studio example



¿El presentador que tiene conocimiento de la Actividad/Contexto es una mala idea en el patrón MVP? (3)

He estado jugando con el patrón MVP durante algunas semanas y he llegado al punto en que necesito contexto para iniciar un service y acceder a Shared Preferences .

He leído que el propósito de MVP es desacoplar la vista de la lógica y tener context dentro de un Presenter puede vencer ese propósito (corrígeme si me equivoco en esto).

Actualmente, tengo una LoginActivity que se parece a esto:

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

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

Por último, mi presentador:

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

Como puede ver, pasé el contexto de la Activity a mi Presenter solo para poder acceder a las Shared Preferences . Estoy bastante preocupado por pasar el contexto a mi presentador. ¿Es esto algo correcto? ¿O debería hacerlo de otra manera?

EDITAR Implementa la tercera preferencia de Jahnold

Ignoremos la interfaz y la implementación porque es prácticamente todo. Así que ahora estoy injecting la interfaz para la preferencia compartida en mi presentador. Aquí está mi código para el 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()); } } }

La forma en que obtengo el contexto es de MyApplication.java

Cuando comienza la aplicación, me aseguro de crear este gráfico de objetos con esta línea de código:

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

¿Esta bien? Quiero decir que ahora no tengo que pasar el contexto de la actividad a mi presentador, pero todavía tengo el contexto de la aplicación.


Esta pregunta fue respondida hace algún tiempo, y, suponiendo que la definición de MVP es lo que OP usó en su código, la respuesta de @Jahnold es realmente buena.

Sin embargo, debe señalarse que MVP es un concepto de alto nivel, y puede haber muchas implementaciones siguiendo los principios de MVP: hay más de una forma de desollar al gato.

Hay otra implementación de MVP , que se basa en la idea de que las actividades en Android no son elementos de la interfaz de usuario , que designa a Activity and Fragment como presentadores de MVP. En esta configuración, los presentadores de MVP tienen acceso directo al Context .

Por cierto, incluso en la implementación antes mencionada de MVP, no usaría Context para obtener acceso a SharedPreferences en el presentador; todavía definiría una clase de contenedor para SharedPreferences y la inyectaría en el presentador.


Ha pasado algún tiempo desde que hizo esta pregunta, pero pensé que sería útil proporcionar una respuesta de todos modos. Sugeriría encarecidamente que el presentador no tenga ningún concepto del contexto de Android (o cualquier otra clase de Android). Al separar completamente su código de Presenter del código del sistema Android, puede probarlo en la JVM sin la complicación de burlarse de los componentes del sistema.

Para lograr esto, creo que tienes tres opciones.

Acceda a las preferencias compartidas desde la vista

Este es mi menos favorito de los tres, ya que acceder a SharedPreferences no es una acción de vista. Sin embargo, mantiene el código del sistema Android en la Actividad lejos del Presentador. En su interfaz de vista tiene un método:

boolean isLoggedIn();

que se puede llamar desde el presentador.

Inyectar SharedPreferences usando Dagger

Como ya está utilizando Dagger para inyectar el bus de eventos, puede agregar SharedPreferences a su ObjectGraph y, como tal, obtendrá una instancia SharedPreferences que se ha construido utilizando ApplicationContext. Esto es lo que obtienes sin tener que pasar un contexto a tu presentador.

La desventaja de este enfoque es que todavía estás pasando una clase de sistema Android (SharedPreferences) y tendrías que burlarte de él cuando quisieras probar el presentador.

Crear una interfaz SharePreferencesRepository

Este es mi método preferido para acceder a datos de Preferencias Compartidas desde un Presentador. Básicamente, usted trata SharedPreferences como un modelo y tiene una interfaz de repositorio para ello.

Su interfaz sería similar a:

public interface SharedPreferencesRepository { boolean isLoggedIn(); }

Entonces puede tener una implementación concreta de esto:

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

Es la interfaz SharedPreferencesRepository que luego se inyecta con Dagger en su presentador. De esta forma, se puede proporcionar un simulacro muy simple en tiempo de ejecución durante las pruebas. Durante el funcionamiento normal se proporciona la implementación concreta.


La mayoría de los elementos del dominio, como la base de datos o la red, necesitan que se cree el contexto. Thay no se puede crear en View porque View no puede tener ningún conocimiento sobre Model. Luego deben crearse en Presentador. Pueden ser inyectados por Dagger, pero también está usando Context. Entonces, el contexto se usa en Presenter xP

El truco es que si queremos evitar el Contexto en Presenter, entonces podemos hacer el constructor que está creando todos estos objetos Modelo a partir del Contexto y no guardarlo. Pero en mi opinión, es estúpido. El nuevo JUnit en Android tiene acceso a Context.

Otro truco es hacer que el contexto sea anulable, y en los objetos de dominio debe haber un mecanismo para proporcionar una instancia de prueba en caso de nulo en contexto. Tampoco me gusta este truco.