35
Christian Panadero http://panavtec.me @PaNaVTEC Github - PaNaVTEC My way to clean Android

My way to clean android - Android day salamanca edition

Embed Size (px)

Citation preview

Christian Panaderohttp://panavtec.me

@PaNaVTEC Github - PaNaVTEC

My way to clean Android

Fernando Cejas Jorge Barroso

Pedro Gomez Sergio Rodrigo

@fernando_cejas @flipper83

@pedro_g_s @srodrigoDev

Android developer en Sound Cloud

Android developer en Tuenti

Cofounder & Android expert en Karumi

Android developer en Develapps

Alberto Moraga Carlos Morera@albertomoraga @CarlosMChicaiOS Developer en Selltag Android Developer en Viagogo

Agradecimientos

“My way to clean Android”

¿Por qué Clean?• Desacoplado de los frameworks

• Testable

• Desacoplado de la UI

• Desacoplado de BDD

• Desacoplado de cualquier detalle de implementación

Conceptos• Patrón command (Invoker, command, receiver)

• Interactors / Casos de uso

• Abstracciones

• Data Source

• Repository

Niveles de abstracción

Presenters

Interactors

Entidades

Repository

Data sources

UI

Abstractions

Regla de dependencias

Presenters

Interactors

Entidades

Repository

Data sources

UI

Abstractions

Modelando el proyecto• App (UI, DI y detalles de implementación)

• Presentation

• Domain y Entities

• Repository

• Data Sources

Dependencias del proyectoApp

Presenters Domain Data

Entities

Repository

Flow

View

Presenter

Presenter

Interactor

Interactor

Interactor

Interactor

Repository

Repository

DataSource

DataSource

DataSource

UI: MVP

ViewPresenter(s)

Model

Eventos

Rellena la vista

Acciones Resultados de esas acciones

UI: MVP - View

public class MainActivity extends BaseActivity implements MainView { @Inject MainPresenter presenter; @Override protected void onResume() { super.onResume(); presenter.onResume(); } @Override public void onRefresh() { presenter.onRefresh(); }

UI: MVP - Presenterpublic class MainPresenter extends Presenter { private MainView mainView; private Bus bus;

public void onResume() { bus.register(this); }

public void onPause() { bus.unregister(this); }

public void onRefresh() { mainView.clearData();

… }

public interface MainView { void showGetContactsError(); void clearData(); }

UI: MVP - Presenter

Presentation - Domain

Presenter InteractorInvoker

BusBus IMP

Invoker IMP

Presentation - Domainpublic class MainPresenter extends Presenter { public void onCreate() { interactorInvoker.execute(getContactsInteractor); }

public void onEvent(GetContactsEvent event) { if (event.getError() == null) { List<PresentationContact> contacts = event.getContacts();

mainView.refreshContactsList(contacts); } else { mainView.showGetContactsError(); } }

public class GetContactsInteractor implements Interactor { private Bus bus; private ContactsRepository repository;

@Override public void execute() { GetContactsEvent event = new GetContactsEvent(); try { List<Contact> contacts = repository.obtainContacts(); event.setContacts(contacts); } catch (RetrieveContactsException e) { event.setError(e); } bus.post(event); } }

Domain - Interactor

Repository

NetworkData Source

BDDData Source

RepositoryModel

Data

Repository Interfacepublic interface ContactsRepository {

List<Contact> obtainContacts() throws CantRetrieveContactsException; Contact obtain(String md5) throws CannotObtainContactException;

}

Repository imp@Override public List<Contact> obtainContacts() throws RetrieveContactsException { List<Contact> contacts = null; try { contacts = bddDataSource.obtainContacts(); } catch (ObtainContactsBddException | InvalidCacheException e) { try { contacts = networkDataSource.obtainContacts(); bddDataSource.persist(contacts); } catch (UnknownObtainContactsException | ContactsNetworkException) { throw new RetrieveContactsException(); } catch (PersistContactsBddException) { e.printStackTrace(); } } return contacts; }

Data source

Model

Data source Imp

Data source

Mapper

Data source Interface

public interface ContactsNetworkDataSource { public List<Contact> obtainContacts() throws ContactsNetworkException, UnknownObtainContactsException; }

private ContactsApiService apiService; private static final ApiContactMapper mapper = new ApiContactMapper();

@Override public List<Contact> obtainContacts() throws ContactsNetworkException { try { ApiContactsResponse apiContactsResponse = apiService.obtainUsers(100); List<ApiContactResult> results = apiContactsResponse.getResults();

<MAP APICONTACTS TO CONTACTS>

return contacts; } catch (Throwable e) { throw new ContactsNetworkException(); } }

Data source imp

Caching Strategypublic interface CachingStrategy<T> { boolean isValid(T data);}

public TtlCachingStrategy(int ttl, TimeUnit timeUnit) { ttlMillis = timeUnit.toMillis(ttl); }

@Override public boolean isValid(T data) { return (data.getPersistedTime() + ttlMillis) > System.currentTimeMillis(); }

Caching Strategy@Override public List<Contact> obtainContacts() throws ObtainContactsBddException, UnknownObtainContactsException, InvalidCacheException { try { List<BddContact> bddContacts = daoContacts.queryForAll(); if (!cachingStrategy.isValid(bddContacts)) { deleteBddContacts(cachingStrategy.candidatesToPurgue(bddContacts)); throw new InvalidCacheException(); } ArrayList<Contact> contacts = new ArrayList<>(); for (BddContact bddContact : bddContacts) { contacts.add(transformer.transform(bddContact, Contact.class)); } return contacts; } catch (java.sql.SQLException e) { throw new ObtainContactsBddException(); } catch (Throwable e) { throw new UnknownObtainContactsException(); } }

Ventajas de Repository

• La lógica de negocio no sabe de donde vienen los datos.

• Fácil de cambiar la implementación de los orígenes de datos.

• En caso de cambio de orígenes de datos la lógica de negocio no se ve alterada.

– Uncle Bob

“Make implementation details swappable”

Picassopublic interface ImageLoader { public void load(String url, ImageView imageView); public void loadCircular(String url, ImageView imageView); }

public class PicassoImageLoader implements ImageLoader { private Picasso picasso; public PicassoImageLoader(Picasso picasso) { this.picasso = picasso; }

public void load(String url, ImageView imageView) { picasso.load(url).into(imageView); }

@Override public void loadCircular(String url, ImageView imageView) { picasso.load(url).transform(new CircleTransform()).into(imageView); }

ErrorManagerpublic interface ErrorManager { public void showError(String error);}

public class SnackbarErrorManagerImp implements ErrorManager { @Override public void showError(String error) {SnackbarManager.show(Snackbar.with(activity).text(error)); }}

public class ToastErrorManagerImp implements ErrorManager { @Override public void showError(String error) { Toast.makeText(activity, error, Toast.LENGTH_LONG).show(); }}

Consejos

• Trabaja siempre con abstracciones nunca con concreciones.

• Usa un buen naming, si ves que hay alguna figura que has creado que no sabes que nombre poner, seguramente esté mal modelada.

• Si creas nuevas figuras usa la diana inicial para asegurarte que las creas en la capa correspondiente

– Uncle Bob

“Clean code. The last programming language”

In Uncle Bob we trust

Show me the code!https://github.com/PaNaVTEC/Clean-Contacts

Referencias

• Fernando Cejas - Clean way

• Jorge Barroso - Arquitectura Tuenti

• Pedro Gomez - Dependency Injection

• Pedro Gomez - Desing patterns

• Uncle Bob - The clean architecture

¿Preguntas?

Christian Panaderohttp://panavtec.me

@PaNaVTEC Github - PaNaVTEC