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 @ Sound Cloud
Android developer @ Tuenti
Cofounder & Android expert @ Karumi
Android developer @ Develapps
Alberto Moraga Carlos Morera
@albertomoraga @CarlosMChica
iOS Developer @ Selltag Android Developer @ Viagogo
Acknowledgements
“My way to clean Android”
Why clean architecture?
• Independent of Frameworks
• Testable
• Independent of UI
• Independent of Database
• Independent of any external agency
Concepts
• Command pattern (Invoker, command, receiver)
• Interactors / Use cases
• Abstractions
• Data Source
• Repository
Abstraction levels
Presenters
Interactors
Entities
Repository
Data sources
UI
Abstractions
The dependency rule
Presenters
Interactors
Entities
Repository
Data sources
UI
Abstractions
Thinking in projects
• App (UI, DI and implementation details)
• Presentation
• Domain y Entities
• Repository
• Data Sources
Project dependencies
App
Presenters Domain Data
Entities
Repository
Flow
View
Presenter
Presenter
Interactor
Interactor
Interactor
Interactor
Repository
Repository
DataSource
DataSource
DataSource
UI: MVP
ViewPresenter(s)
Model
Events
Fill the view
Actions Actions output
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();
}
}
Repository adavantages
• Bussines logic doesn’t know where the data came
from
• It’s easy to change data source implementation
• If you change the data sources implementation the
business logic is not altered
– 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();
}
}
Tips
• ALWAYS Depend upon abstractions, NEVER
depend upon concretions
• Use a good naming, if there's a class you've
created and the naming does not feel right, most
probably it is wrong modeled.
• Create new shapes using the initial dartboard to
ensure that it's placed on the corresponding layer
– Uncle Bob
“Clean code. The last programming
language”
In Uncle Bob we trust
Show me the code!
https://github.com/PaNaVTEC/Clean-Contacts
References
• Fernando Cejas - Clean way
• Jorge Barroso - Arquitectura Tuenti
• Pedro Gomez - Dependency Injection
• Pedro Gomez - Desing patterns
• Uncle Bob - The clean architecture
¿Questions?
Christian Panadero
http://panavtec.me
@PaNaVTEC
Github - PaNaVTEC

My way to clean android (EN) - Android day salamanca edition

  • 1.
  • 2.
    Fernando Cejas JorgeBarroso Pedro Gomez Sergio Rodrigo @fernando_cejas @flipper83 @pedro_g_s @srodrigoDev Android developer @ Sound Cloud Android developer @ Tuenti Cofounder & Android expert @ Karumi Android developer @ Develapps Alberto Moraga Carlos Morera @albertomoraga @CarlosMChica iOS Developer @ Selltag Android Developer @ Viagogo Acknowledgements
  • 3.
    “My way toclean Android”
  • 4.
    Why clean architecture? •Independent of Frameworks • Testable • Independent of UI • Independent of Database • Independent of any external agency
  • 5.
    Concepts • Command pattern(Invoker, command, receiver) • Interactors / Use cases • Abstractions • Data Source • Repository
  • 6.
  • 7.
  • 8.
    Thinking in projects •App (UI, DI and implementation details) • Presentation • Domain y Entities • Repository • Data Sources
  • 9.
  • 10.
  • 11.
  • 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.
    Repository adavantages • Bussineslogic doesn’t know where the data came from • It’s easy to change data source implementation • If you change the data sources implementation the business logic is not altered
  • 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.
    Tips • ALWAYS Dependupon abstractions, NEVER depend upon concretions • Use a good naming, if there's a class you've created and the naming does not feel right, most probably it is wrong modeled. • Create new shapes using the initial dartboard to ensure that it's placed on the corresponding layer
  • 31.
    – Uncle Bob “Cleancode. The last programming language”
  • 32.
    In Uncle Bobwe trust
  • 33.
    Show me thecode! https://github.com/PaNaVTEC/Clean-Contacts
  • 34.
    References • Fernando Cejas- Clean way • Jorge Barroso - Arquitectura Tuenti • Pedro Gomez - Dependency Injection • Pedro Gomez - Desing patterns • Uncle Bob - The clean architecture
  • 35.