Upload
christian-panadero
View
829
Download
0
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 @ Sound Cloud
Android developer @ Tuenti
Cofounder & Android expert @ Karumi
Android developer @ Develapps
Alberto Moraga Carlos Morera@albertomoraga @CarlosMChicaiOS 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 dependenciesApp
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 - 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(); } }
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”
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(); }}
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 Panaderohttp://panavtec.me
@PaNaVTEC Github - PaNaVTEC