con Android (y Java)
¿Qué es MVP?
Derivación del patrón arquitectónico Modelo-Vista-Controlador (MVC), y es utilizado mayoritariamente para construir interfaces de usuario.
Es un patrón arquitectónico de interfaz de usuario diseñada para facilitar pruebas de unidad automatizada y mejorar la separación de intereses en lógica de presentación.
Patrón MVP
El modelo es una interfaz que define los datos que se mostrará o no actuado en la interfaz de usuario.
El presentador actúa sobre el modelo y la vista. Recupera datos de los repositorios (el modelo), y los formatea para mostrarlos en la vista.
La vista es una interfaz pasiva que exhibe datos (el modelo) y órdenes de usuario de las rutas (eventos) al presentador para actuar sobre los datos.
Flujo Model-View-Presenter
Vista pasiva en Android (Activity)
public interface LoginView {
void enableInputs();
void disableInputs();
void showProgress();
void hideProgress();
void handleSignIn();
void handleSignUp();
void newUserSuccess();
void newUserError(String error);
void navigateToMainScreen();
void loginError(String error);
}
Se crea una interfaz donde se definirán los métodos que implementará la vista (Activity)
Vista pasiva en Android (Activity)
public class LoginActivity extends AppCompatActivity implements LoginView {
@Override
public void enableInputs() { setInputs(true); }
@Override
public void disableInputs() { setInputs(false); }
@Override
public void showProgress() { progressBar.setVisibility(View.VISIBLE); }
@Override
public void hideProgress() { progressBar.setVisibility(View.GONE); }
@OnClick(R.id.buttonSignin)
public void handleSignIn(){
String email = inputText.getText().toString();
String password = inputPassword.getText().toString();
presenter.validateLogin(email, password);
}
@OnClick(R.id.buttonSignup)
public void handleSignUp(){
String email = inputText.getText().toString();
String password = inputPassword.getText().toString();
presenter.registerNewUser(email, password);
}
}
Implementación en la actividad
Vista pasiva en Android (Activity)
public class LoginActivity extends AppCompatActivity implements LoginView {
@Override
public void newUserSuccess() {
Snackbar.make(container, R.string.login_error_notice_signup,
Snackbar.LENGTH_SHORT).show();
}
@Override
public void newUserError(String error) {
inputPassword.setText("");
String msgError = String.format(getString(R.string.login_error_message_signup),
error);
inputPassword.setError(msgError);
}
@Override
public void navigateToMainScreen() {
Intent intent = new Intent(this, ContactListActivity.class);
startActivity(intent);
}
@Override
public void loginError(String error) {
inputPassword.setText("");
String msgError = String.format(getString(R.string.login_error_message_signin),
error);
inputPassword.setError(msgError);
}
}
Implementación en la actividad
Vista pasiva en Android (Activity)
public class LoginActivity extends AppCompatActivity implements LoginView {
private LoginPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
ButterKnife.bind(this);
presenter = new LoginPresenterImp(this);
presenter.onCreate();
presenter.checkForAuthenticatedUser();
}
}
Presentador en la vista
Presentador
public interface LoginPresenter {
void onCreate();
void onDestroy();
void checkForAuthenticatedUser();
void validateLogin(String email, String password);
void registerNewUser(String email, String password);
void onEventMainThread(LoginEvent event);
}
Interfaz del presentador
Presentador
public class LoginPresenterImp implements LoginPresenter {
private EventBus eventBus;
private LoginView loginView;
private LoginInteractor loginInteractor;
public LoginPresenterImp(LoginView view) {
loginView = view;
loginInteractor = new LoginInteractorImp();
eventBus = GreenRobotEventBus.getInstance();
}
@Override
public void onCreate() {
eventBus.register(this);
}
@Override
public void onDestroy() {
loginView = null;
eventBus.unregister(this);
}
@Override
public void checkForAuthenticatedUser() {
if(loginView != null) {
loginView.disableInputs();
loginView.showProgress();
}
loginInteractor.checkSession();
}
}
Implementación de los métodos del presentador
Presentador
public class LoginPresenterImp implements LoginPresenter {
@Override
public void validateLogin(String email, String password) {
if(loginView != null) {
loginView.disableInputs();
loginView.showProgress();
}
loginInteractor.doSignIn(email,password);
}
@Override
public void registerNewUser(String email, String password) {
if(loginView != null) {
loginView.disableInputs();
loginView.showProgress();
}
loginInteractor.doSignUp(email, password);
}
@Override
@Subscribe
public void onEventMainThread(LoginEvent event) {
/* Manejo del Evento de Login */
}
}
Implementación de los métodos del presentador
Presentador
public class LoginPresenterImp implements LoginPresenter {
@Override
@Subscribe
public void onEventMainThread(LoginEvent event) {
/* Manejo del Evento de Login */
}
private void onSignInSuccess(){
if(loginView != null){
loginView.navigateToMainScreen();
}
}
private void onSignUpSuccess(){
if(loginView != null){
loginView.newUserSuccess();
}
}
private void onSignInError(String error){
if(loginView != null) {
loginView.hideProgress();
loginView.enableInputs();
loginView.loginError(error);
}
}
private void onSignUpError(String error){
if(loginView != null) {
loginView.hideProgress();
loginView.enableInputs();
loginView.newUserError(error);
}
}
private void onFailedToRecoverSession(){
if(loginView != null) {
loginView.hideProgress();
loginView.enableInputs();
}
}
}
Manejo de los eventos del Login
Clean Architecture es una arquitectura de software que Robert Martin (Uncle Bob) describió en su blog, que no es más que búsqueda de la separación de intereses.
https://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html
Características en común de los sistmas que producen las arquitecturas de software que resume Uncle Bob.
Descripción de la Arquitectura Clean
Regla de Dependencia
Esta regla dice que las dependencias a nivel de código fuente sólo pueden apuntar hacia dentro. Nada que se encuentre en un circulo interior puede saber algo sobre lo que hay en un círculo exterior.
Algo declarado en un círculo externo no puede ser mencionado desde el código situado en un círculo interno. Eso incluye funciones, clases, variables o cualquier otra entidad software.
Entidades
Las entidades encapsulan la lógica de negocio.
Casos de Uso
El software de esta capa contiene reglas de negocio específicas de la aplicación
Adaptadores de Interfaces
El software de esta capa es un conjunto de adaptadores que convierten datos desde el formato mas conveniente para el caso de uso y entidades al formato mas conveniente o aceptado por elementos externos como la base de datos o la web
Frameworks / UI
El software de esta capa es un conjunto de adaptadores que convierten datos desde el formato mas conveniente para el caso de uso y entidades al formato mas conveniente o aceptado por elementos externos como la base de datos o la web
¿Y con Android?
MVP + Clean
Interactuador
public interface LoginInteractor {
void checkSession();
void doSignUp(String email, String password);
void doSignIn(String email, String password);
}
public class LoginInteractorImp implements LoginInteractor{
private LoginRepository loginRepository;
public LoginInteractorImp() {
loginRepository = new LoginRepositoryImp();
}
@Override
public void checkSession() {
loginRepository.checkSession();
}
@Override
public void doSignUp(String email, String password) {
loginRepository.signUp(email, password);
}
@Override
public void doSignIn(String email, String password) {
loginRepository.signIn(email, password);
}
}
Repositorio
public interface LoginRepository {
void signUp(String email, String password);
void signIn(String email, String password);
void checkSession();
}
public class LoginRepositoryImp implements LoginRepository {
public LoginRepositoryImp() {
helper = FirebaseHelper.getInstance();
dataReference = helper.getDatareference();
myUserReference = helper.getMyUserReference();
}
@Override
public void signUp(final String email, final String password) {
dataReference.createUser(email, password, new Firebase.ValueResultHandler<Map<String, Object>>() {
@Override
public void onSuccess(Map<String, Object> stringObjectMap) {
postEvent(LoginEvent.onSignUpSuccess);
signIn(email, password);
}
@Override
public void onError(FirebaseError firebaseError) {
postEvent(LoginEvent.onSignUnError);
}
});
}
@Override
public void signIn(String email, String password) {
dataReference.authWithPassword(email,password, new Firebase.AuthResultHandler() {
@Override
public void onAuthenticated(AuthData authData) {
initSignIn();
postEvent(LoginEvent.onSignInSuccess);
}
@Override
public void onAuthenticationError(FirebaseError firebaseError) {
postEvent(LoginEvent.onSignInError, firebaseError.getMessage());
}
});
}
@Override
public void checkSession() {
if(dataReference.getAuth() != null){
initSignIn();
} else {
postEvent(LoginEvent.onFailedToRecoverSession);
}
}
Estructura del directorio
Vamos al código (ya cocinado)