Christian Panadero
http://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 @CarlosMChica
iOS 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 proyecto
App
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 - Presenter
public 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
Bus
Bus IMP
Invoker IMP
Presentation - Domain
public 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
Network
Data Source
BDD
Data Source
Repository
Model
Data
Repository Interface
public 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 Strategy
public 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”
Picasso
public 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);
}
ErrorManager
public 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 Panadero
http://panavtec.me
@PaNaVTEC
Github - PaNaVTEC

My way to clean android - Android day salamanca edition

  • 1.
  • 2.
    Fernando Cejas JorgeBarroso 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 @CarlosMChica iOS Developer en Selltag Android Developer en Viagogo Agradecimientos
  • 3.
    “My way toclean Android”
  • 4.
    ¿Por qué Clean? •Desacoplado de los frameworks • Testable • Desacoplado de la UI • Desacoplado de BDD • Desacoplado de cualquier detalle de implementación
  • 5.
    Conceptos • Patrón command(Invoker, command, receiver) • Interactors / Casos de uso • Abstracciones • Data Source • Repository
  • 6.
  • 7.
  • 8.
    Modelando el proyecto •App (UI, DI y detalles de implementación) • Presentation • Domain y Entities • Repository • Data Sources
  • 9.
    Dependencias del proyecto App PresentersDomain Data Entities Repository
  • 10.
  • 11.
    UI: MVP ViewPresenter(s) Model Eventos Rellena lavista Acciones Resultados de esas acciones
  • 12.
    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(); }
  • 13.
    UI: MVP -Presenter public 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(); … }
  • 14.
    public interface MainView{ void showGetContactsError(); void clearData(); } UI: MVP - Presenter
  • 15.
    Presentation - Domain PresenterInteractorInvoker Bus Bus IMP Invoker IMP
  • 16.
    Presentation - Domain publicclass 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(); } }
  • 17.
    public class GetContactsInteractorimplements 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
  • 18.
  • 19.
    Repository Interface public interfaceContactsRepository { List<Contact> obtainContacts() throws CantRetrieveContactsException; Contact obtain(String md5) throws CannotObtainContactException; }
  • 20.
    Repository imp @Override publicList<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; }
  • 21.
    Data source Model Data sourceImp Data source Mapper
  • 22.
    Data source Interface publicinterface ContactsNetworkDataSource { public List<Contact> obtainContacts() throws ContactsNetworkException, UnknownObtainContactsException; }
  • 23.
    private ContactsApiService apiService; privatestatic 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
  • 24.
    Caching Strategy public interfaceCachingStrategy<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(); }
  • 25.
    Caching Strategy @Override publicList<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(); } }
  • 26.
    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.
  • 27.
    – Uncle Bob “Makeimplementation details swappable”
  • 28.
    Picasso public 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); }
  • 29.
    ErrorManager public 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(); } }
  • 30.
    Consejos • Trabaja siemprecon 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
  • 31.
    – Uncle Bob “Cleancode. The last programming language”
  • 32.
    In Uncle Bobwe trust
  • 33.
    Show me thecode! https://github.com/PaNaVTEC/Clean-Contacts
  • 34.
    Referencias • Fernando Cejas- Clean way • Jorge Barroso - Arquitectura Tuenti • Pedro Gomez - Dependency Injection • Pedro Gomez - Desing patterns • Uncle Bob - The clean architecture
  • 35.