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

Post on 17-Jul-2015

827 views 0 download

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

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