Patrón MVP

+

Arquitectura Clean

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

¿Y el modelo?

Arquitectura Clean

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.

  • Independiente de frameworks.
  • Testeable.
  • Independiente de la UI.
  • Independiente de la base de datos.
  • Independiente de factores externos.

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)

MVP+CLEAN

By Gustavo Giménez

MVP+CLEAN

MVP Pattern + Clean Arquitecture presentation

  • 504