SlideShare ist ein Scribd-Unternehmen logo
1 von 112
Downloaden Sie, um offline zu lesen
EVOLUINDO
ARQUITETURAS
REATIVAS
Ubiratan Soares
Julho / 2017
O QUE É UMA
ARQUITETURA EM
MOBILE ?
MVP
MVVM
VIPER
FLUX REDUX
CLEAN
MVC…
MVI
PRINCÍPIOS DE ARQUITETURA
Organização
Facilidade em se encontrar o que se precisa
Menor impedância para se resolver bugs
Menos dor ao escalar em tamanho (codebase e devs)
Estilo de projeto unificado, definido e defendido pelo time
UMA QUEIXA COMUM
NA COMUNIDADE
MOBILE ?
TEM PELO MENOS UM UNIT TEST NO APP?
EM MOBILE,
ARQUITETURA É CRÍTICA
PARA TESTABILIDADE
QUAL ARQUITETURA
ESCOLHER ENTÃO ???
NÃO HÁ
SILVER
BULLETS!
ESTUDO DE CASO
MVP / CLEAN
PRESENTATION
LAYER
DATA
LAYER
DB
REST
ETC
UI
. . .
public interface ViewDelegate {
void displayResults(DataModel model);
void networkingError();
void displayEmptyState();
void displayErrorState();
// More delegation
}
public class MainActivity extends AppCompatActivity implements ViewDelegate {
Presenter presenter; // How to resolve this instance ???
@Override protected void onStart() {
super.onStart();
presenter.bindView(this);
presenter.fetchData();
}
@Override public void displayResults(DataModel model) {
// Put data into view
}
@Override public void networkingError() {
// Up to you
}
@Override public void displayEmptyState() {
// And this too!
}
@Override public void displayErrorState() {
// Please, do not mess with your user
}
}
public class Presenter {
public void bindView(ViewDelegate delegate) {
this.delegate = delegate;
}
public void fetchData() {
source.fetchData(new DataSource.Callback() {
@Override public void onDataLoaded(DataModel model) {
delegate.displayResults(model);
}
@Override public void onError(Throwable t) {
if (t instanceof NetworkingError) {
delegate.networkingError();
} else if (t instanceof NoDataAvailable) {
…
}
}
});
}
}
DATASOURCE
REST GATEWAY
PRESENTER
VIEW DELEGATION CALLBACKS
PLATAFORM CONTROLLER
CALLBACK
UNIT TESTS
(Mocked Contract)
FUNCTIONAL UI TESTS
INTEGRATION TESTS
INTEGRATION TESTS
(DOUBLES)
UNIT TESTS
(Mocked Source
+
Mocked View)
DATAMODEL
String description = “Blah”
String date = “2010-02-26T19:35:24Z”
int step = 2
String description = “Blah”
LocalDateTime dateTime = (JSR310)
TrackingStep currentStep = (enum)
String description = “Blah”
String formattedDate = “26/02/2010”
String currentStep = “Concluído”
Response
Model
Domain
Model
View
Model
DATA
MODEL
PROBLEMAS EM POTENCIAL
Qual representação de dados utilizar? Unificada ou separada?
Onde aplicar parsing? E formatação para a UI?
Callbacks aninhados
Memory leaks no nível do mecanismo de entrega
Etc
BRACE
YOURSELVES
RX
IS COMING
COMO
ADICIONAR RX
NESSA
ARQUITETURA ??
PRESENTATION
LAYER
DATA
LAYER
DB
REST
ETC
UI
. . .
Callback(T)Callback(T) Callback(T)
SUBSTITUIR
CALLBACKS POR
SEQUÊNCIAS
OBSERVÁVEIS
PRIMEIRA INTERAÇÃO
CAMADA DE DADOS
REATIVA
REST GATEWAY
VIEW DELEGATION
VIEW
DATA SOURCE
Flowable<T>
PRESENTER
Callback(T)
FlowableSubscriber<T> Disposable
public interface EventsSource {
Flowable<Message> fetchWith(MessageToFetchParameters params);
Flowable<Message> sendMessage(MessageToSendParameters params);
}
ADEUS
CALLBACKS !!! 👌
public class MessagesInfrastructure implements EventsSource {
@Override public Flowable<Message> fetchWith(MessageToFetch params) {
return restAPI.getMessages(params)
.subscribeOn(Schedulers.io())
.map(PayloadMapper::map)
.flatMap(Flowable::fromIterable);
}
@Override public Flowable<Message> sendMessage(MessageToSend params) {
SendMessageToBody body = SendMessageToBody.convert(params);
return restAPI.sendMessage(body)
.subscribeOn(Schedulers.io())
.flatMap(emptyBody -> fetchWith(sameFrom(params)));
}
}
Chained request, easy !!!!
VANTAGENS OBSERVADAS
Facilidades via frameworks utilitários para REST / DB
Validação de dados de entrada e tradução de modelos
como etapas do pipeline
Tratamento de erros, auto retry, exponential backoff no
“baixo nível”
PROBLEMAS OBSERVADOS
Consumir os dados no nível da apresentação nos força a
rodar comportamentos na thread principal do app
(orquestração dos callbacks)
Indireção forçada para prover Scheduler via DI, para
propósitos de testes
Muitas responsabilidades no Presenter
SEGUNDA INTERAÇÃO
CAMADA DE
APRESENTAÇÃO REATIVA
REST GATEWAY
VIEW DELEGATION
VIEW
DATA SOURCE
Flowable<T>
PRESENTER
FlowableSubscriber<U>
Flowable<U>
Disposable
public interface SomeView<T> {
Function<Flowable<T>, Disposable> results();
Function<Flowable<Unit>, Disposable> showEmptyState();
Function<Flowable<Unit>, Disposable> hideEmptyState();
Function<Flowable<Unit>, Disposable> showLoading();
Function<Flowable<Unit>, Disposable> hideLoading();
// More delegation
}
public static <T> Disposable bind(Flowable<T> flow,
Function<Flowable<T>, Disposable> uiFunc) {
return uiFunc.call(flow);
}
public static <T> Function<Flowable<T>, Disposable> uiFunction(Consumer<T> uiAction) {
return uiFunction(uiAction, () -> {});
}
public static <T> Function<Flowable<T>, Disposable> uiFunction(Consumer<T> uiAction,
Action done) {
try {
return flowable ->
flowable
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
uiAction,
throwable -> Logger.e(throwable.getMessage()),
done
);
} catch (Exception e) { throw new RuntimeException();}
}
public static <T> Disposable bind(Flowable<T> flow,
Function<Flowable<T>, Disposable> uiFunc) {
return uiFunc.call(flow);
}
public static <T> Function<Flowable<T>, Disposable> uiFunction(Consumer<T> uiAction) {
return uiFunction(uiAction, () -> {});
}
public static <T> Function<Flowable<T>, Disposable> uiFunction(Consumer<T> uiAction,
Action done) {
try {
return flowable ->
flowable
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
uiAction,
throwable -> Logger.e(throwable.getMessage()),
done
);
} catch (Exception e) { throw new RuntimeException();}
}
public static <T> Disposable bind(Flowable<T> flow,
Function<Flowable<T>, Disposable> uiFunc) {
return uiFunc.call(flow);
}
public static <T> Function<Flowable<T>, Disposable> uiFunction(Consumer<T> uiAction) {
return uiFunction(uiAction, () -> {});
}
public static <T> Function<Flowable<T>, Disposable> uiFunction(Consumer<T>uiAction,
Action done) {
try {
return flowable ->
flowable
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
uiAction,
throwable -> Logger.e(throwable.getMessage()),
done
);
} catch (Exception e) { throw new RuntimeException();}
}
public class MessagingActivity extends BaseActivity
implements MessagesStreamView {
@Override public Function<Flowable<String>, Disposable> restoreNotSentMessage() {
return uiFunction(message -> {
Toast.makeText(this, "Erro ao enviar mensagem", LENGTH_SHORT).show();
messageInput.setText(message);
});
}
@Override public Function<Flowable<String>, Disposable> enableSomeOption() {
return uiFunction(action -> optionView.setVisibility(VISIBLE));
}
@Override public Function<Flowable<String>, Disposable> disableSomeOption() {
return uiFunction(action -> optionView.setVisibility(GONE));
}
@Override public Function<Flowable<String>, Disposable> showEmptyState() {
return uiFunction(action -> emptyStateContainer.setVisibility(VISIBLE));
}
// More delegate methods
public class MessagingActivity extends BaseActivity
implements MessagesStreamView {
@Override public Function<Flowable<String>, Disposable> restoreNotSentMessage() {
return uiFunction(message -> {
Toast.makeText(this, "Erro ao enviar mensagem", LENGTH_SHORT).show();
messageInput.setText(message);
});
}
@Override public Function<Flowable<String>, Disposable> enableComplaintOption() {
return uiFunction(action -> optionView.setVisibility(VISIBLE));
}
@Override public Function<Flowable<String>, Disposable> disableComplaintOption() {
return uiFunction(action -> optionView.setVisibility(GONE));
}
@Override public Function<Flowable<String>, Disposable> showEmptyState() {
return uiFunction(action -> emptyStateContainer.setVisibility(VISIBLE));
}
// More delegate methods
public class ReactivePresenter<V> {
private final CompositeDisposable disposable = new CompositeDisposable();
private V view;
public void bind(V view) { this.view = view; }
public void unbind() {
disposable.clear();
this.view = null;
}
public CompositeDisposable subscriptions() {
return disposable;
}
protected V view() { return view; }
protected boolean isBinded() { return view != null; }
}
public class ReactivePresenter<V> {
private final CompositeDisposable disposable = new CompositeDisposable();
private V view;
public void bind(V view) { this.view = view; }
public void unbind() {
disposable.dispose();
this.view = null;
}
public CompositeDisposable subscriptions() {
return disposable;
}
protected V view() { return view; }
protected boolean isBinded() { return view != null; }
}
public void userRequiredMediation(String userId, String messageText) {
MessageToSendParameters parameters = new MessageToSendParameters.Builder()
.userId(userId)
// …
.messageText(messageText)
.build();
executionPipeline(parameters);
}
private void executionPipeline(MessageToSend parameters) {
Flowable<HelpDeskEventViewModel> execution =
source.sendMessage(parameters)
.doOnSubscribe(this::prepareToLoad)
.map(ViewModelMappers::map)
.flatMap(Flowable::fromIterable)
.doOnCompleted(this::finishLoadingMessages);
subscriptions().add(bind(execution, view().onMessagesLoaded()));
}
public void userRequiredMediation(String userId, String messageText) {
MessageToSendParameters parameters = new MessageToSendParameters.Builder()
.userId(userId)
// …
.messageText(messageText)
.build();
executionPipeline(parameters);
}
private void executionPipeline(MessageToSend parameters) {
Flowable<HelpDeskEventViewModel> execution =
source.sendMessage(parameters)
.doOnSubscribe(this::prepareToSend)
.map(ViewModelMappers::map)
.flatMap(Flowable::fromIterable)
.doOnCompleted(this::finishSendMessage);
subscriptions().add(bind(execution, view().onMessagesLoaded()));
}
VANTAGENS OBSERVADAS
Presenter não precisa mais da noção de threading
Presenter passar a orquestrar a UI através de um pipeline de
execução bem definido
Condições relacionadas aos dados no ciclo de vida do fluxo
podem ser disparada a partir do pipeline
Tradução de ViewModels é uma etapa do pipeline
PROBLEMAS OBSERVADOS
1) Protocolo View ainda gordo
2) “Repetição” de código entre Presenters, normalmente
relacionada a comportamentos de UI similares que
acompanhando o ciclo de vida da sequências
- Mostrar empty state se não houver dados
- Mostrar loading ao iniciar operação; esconder ao terminar
- Etc
3) Testes ruins de serem lidos
@Test public void shouldNotDisplayResults_WhenEmptyData() {
presenter.bind(view);
// When source has no data to return
when(source.getResults()).thenReturn(Flowable.empty());
// and presenter requires data
presenter.fetchResults();
// we should not display any data into View
verify(view.resultsDeliveredAction, never()).call(Flowable.just(any());
}
public class MockView implements SomeView {
@Mock public Action resultsDeliveredAction;
public MockFAQView() {
MockitoAnnotations.initMocks(this);
}
@Override public Function<Flowable<ViewModel>, Disposable> onResults() {
return flowable -> flowable.subscribe(resultsDeliveredAction);
}
...
public class MockView implements SomeView {
@Mock public Action resultsDeliveredAction;
public MockFAQView() {
MockitoAnnotations.initMocks(this);
}
@Override public Function<Flowable<ViewModel>, Disposable> onResults() {
return flowable -> flowable.subscribe(resultsDeliveredAction);
}
...
TERCEIRA INTERAÇÃO
REACTIVE VIEW
SEGREGATION
public interface SomeView<T> {
Func1<Flowable<T>, Disposable> results();
Function<Flowable<Unit>, Disposable> showEmptyState();
Function<Flowable<Unit>, Disposable> hideEmptyState();
Function<Flowable<Unit>, Disposable> showLoading();
Function<Flowable<Unit>, Disposable> hideLoading();
Function<Flowable<Unit>, Disposable> networkError();
Function<Flowable<Unit>, Disposable> networkUnavailable();
Function<Flowable<Unit>, Disposable> networkSlow();
}
UI BEHAVIOR
VIEW PROTOCOL
UI BEHAVIOR UI BEHAVIOR
UI BEHAVIOR . . .
public interface EmptyStateView<T> {
Function<Flowable<Unit>, Disposable> showEmptyState();
Function<Flowable<Unit>, Disposable> hideEmptyState();
}
public interface LoadingView<T> {
Function<Flowable<Unit>, Disposable> showLoading();
Function<Flowable<Unit>, Disposable> hideLoading();
}
public interface SomeView<T>
extends LoadingView, EmptyStateView, NetworkingReporterView {
Function<Flowable<T>, Disposable> displayResults();
}
public interface NetworkingReporterView<T> {
Function<Flowable<Unit>, Disposable> networkError();
Function<Flowable<Unit>, Disposable> networkUnavailable();
Function<Flowable<Unit>, Disposable> networkSlow();
}
- Cada comportamento poderia ter
o seu “mini-presenter” associado, e
o Presenter “grande” faria a
orquestração dos colaboradores
- Melhor estratégia : fazer a
composição ser uma etapa do
pipeline !!!
f(g(x))
public class LoadingWhenProcessing<T> implements FlowableTransformer<T, T> {
private PublishSubject<Unit> show, hide = PublishSubject.create();
public Dispsoable bind(LoadingView view) {
CompositeDisposable composite = new CompositeDisposable();
composite.add(bind(show, view.showLoading()));
composite.add(bind(hide, view.hideLoading()));
return composite;
}
@Override public Flowable<T> call(Flowable<T> upstream) {
return upstream
.doOnSubscribe(this::showLoading)
.doOnTerminate(this::hideLoading);
}
private void hideLoading() {
hide.onNext(Unit.instance());
}
private void showLoading() {
show.onNext(Unit.instance());
}
}
public class SomePresenter
extends ReactivePresenter<SomeView> {
// Hook all behaviors for view
[ ... ]
public void executeOperation() {
bind(executionPipeline(), view().results());
}
private Flowable<Data> executionPipeline() {
return source.search()
.compose(networkErrorFeedback)
.compose(loadingWhenProcessing)
.compose(coordinateRefresh)
.compose(emptyStateWhenMissingData)
.compose(errorWhenProblems)
.map(DataViewModelMapper::map);
}
}
VANTAGENS
Cada evento delegado para a UI agora é unit-testable de uma
mais fácil !!!
Presenters apenas orquestram a UI (como prega MVP)
Transformers são facilmente reutilizáveis
PROBLEMAS ENCONTRADOS (I)
1) Boilerplate para o binding de comportamentos
@Override public void bind(SomeView view) {
super.bind(view);
subscription().add(loadingWhileProcessing.bind(view));
subscription().add(networkErrorFeedback.bind(view));
subscription().add(coordinateRefresh.bind(view));
subscription().add(emptyStateWhenMissingData.bind(view));
subscription().add(errorStateWhenProblem.bind(view));
}
PROBLEMAS ENCONTRADOS (II)
2) Comportamentos injetados via DI no Presenter; possível confusão ao
fazer pull das dependências
3) Cooperação entre comportamentos, como fazer?
4) Comando para ação na View sinalizado via emissão de item
5) Testes de transformers são mais isolados, mas não necessariamente
mais legíveis!
@Test public void shouldTransformView_WhenErrorAtStream() {
loadingWhileFetching.bindLoadingContent(view);
// When stream will propagate an error
Flowable<String> stream = Flowable.error(new RuntimeCryptoException());
// and we add this transformation to pipeline
stream.compose(loadingWhileFetching)
.subscribe(
s -> {},
throwable -> {},
() -> {}
);
// we still should interact with loading actions
verify(view.showLoadingAction).call(Flowable.just(any());
verify(view.hideLoadingAction).call(Flowable.just(any());
}
QUARTA INTERAÇÃO
SIMPLIFICAR PARA
ESCALAR
PRINCÍPIO EM SOFTWARE
“Camadas mais internas
escondem complexidade das
camadas mais externas”
COROLÁRIO
“Camadas mais externas de uma
API devem ser mais SIMPLES para
seus usuários do que as internas”
REMODELANDO AS APIs
Queremos manter comportamentos segregados como etapas do
pipeline via transformadores
Queremos adicionar mais comportamentos que estão associados à
condições dos dados de forma transparente
Queremos diminuir a fricção para implementação dessa abordagem em
novos fluxos
Queremos facilitar escrita e entendimento de testes
Queremos fornecer 100% dos objetos via DI (incluindo a própria View)
REPENSANDO VIEW PASSIVA
public interface EmptyStateView<T> {
Function<Flowable<Unit>, Disposable> showEmptyState();
Function<Flowable<Unit>, Disposable> hideEmptyState();
}
REPENSANDO VIEW PASSIVA
public interface EmptyStateView<T> {
Function<Flowable<Unit>, Disposable> showEmptyState();
Function<Flowable<Unit>, Disposable> hideEmptyState();
}
public interface EmptyStateView {
Action showEmptyState();
Action hideEmptyState();
}
IMPL. VIEW PASSIVA (ANTES)
@Override public Func1<Observable<Unit>, Subscription> showEmptyState() {
return RxUi.uiFunction(unit -> emptyState.setVisibility(View.VISIBLE));
}
@Override public Func1<Observable<Unit>, Subscription> hideEmptyState() {
return RxUi.uiFunction(unit -> emptyState.setVisibility(View.GONE));
}
@Override public Func1<Observable<Unit>, Subscription> showLoading() {
return RxUi.uiFunction(unit -> progress.setVisibility(View.VISIBLE));
}
@Override public Func1<Observable<Unit>, Subscription> hideLoading() {
return RxUi.uiFunction(unit -> progress.setVisibility(View.GONE));
}
// More delegation
IMPL. VIEW PASSIVA (DEPOIS)
@Override public Action showLoading() {
return () -> loading.setVisibility(View.VISIBLE);
}
@Override public Action hideLoading() {
return () -> loading.setVisibility(View.GONE);
}
@Override public Action showEmptyState() {
return () -> emptyState.setVisibility(View.VISIBLE);
}
@Override public Action hideEmptyState() {
return () -> emptyState.setVisibility(View.GONE);
}
// More Delegation
ENTREGA DE DADOS (ANTES)
public interface SomeView
extends EmptyStateView, LoadingView, NetworkErrorReporterView {
Function<Flowable<ViewModel>, Disposable> onResults();
}
// At view implementation
@Override public Function<Flowable<ViewModel>, Disposable> onResults() {
return RxUi.uiFunction(
model -> adapter.add(model),
this::displayResults
);
}
ENTREGA DE DADOS (DEPOIS)
public interface DisplayFactsView
extends LoadingView, ErrorStateView, EmptyStateView {
Disposable subscribeInto(Flowable<FactViewModel> flow);
}
@Override public Disposable subscribeInto(Flowable<FactViewModel> flow) {
return flow
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
model -> addToAdapter(model),
throwable -> Logger.e(throwable.getMessage()),
() -> displayResults()
);
}
ENTREGA DE DADOS (DEPOIS)
public interface DisplayFactsView
extends LoadingView, ErrorStateView, EmptyStateView {
Disposable subscribeInto(Flowable<FactViewModel> flow);
}
@Override public Disposable subscribeInto(Flowable<FactViewModel> flow) {
return flow
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
model -> addToAdapter(model),
throwable -> Logger.e(throwable.getMessage()),
() -> displayResults()
);
}
public ComplexPresenter(
DataSource source,
NetworkErrorFeedback networkErrorFeedback,
LoadingWhileFetching loadingWhileFetching,
CoordinateRefreshWhenLoadingContent coordinateRefresh,
ShowEmptyStateWhenMissingData emptyStateWhenMissingData,
ShowErrorState errorState) {
this.source = source;
this.networkErrorFeedback = networkErrorFeedback;
this.loadingWhileFetching = loadingWhileFetching;
this.coordinateRefresh = coordinateRefresh;
this.emptyStateWhenMissingData = emptyStateWhenMissingData;
this.errorState = errorState;
}
@Override public void bind(ComplexView view) {
super.bind(view);
subscriptions().add(loadingWhileFetching.bindLoadingContent(view));
subscriptions().add(networkErrorFeedback.bindNetworkingReporter(view));
subscriptions().add(coordinateRefresh.bindRefreshableView(view));
subscriptions().add(emptyStateWhenMissingData.bindEmptyStateView(view));
subscriptions().add(errorState.bindErrorStateView(view));
}
ANTES …
public class FactsPresenter {
private GetRandomFacts usecase;
private DisplayFactsView view;
private BehavioursCoordinator<FactAboutNumber> coordinator;
private ViewModelMapper mapper;
public FactsPresenter(GetRandomFacts usecase,
DisplayFactsView view,
BehavioursCoordinator coordinator,
ViewModelMapper mapper) {
this.usecase = usecase;
this.view = view;
this.coordinator = coordinator;
this.mapper = mapper;
}
DEPOIS!!!
public class FactsPresenter {
private GetRandomFacts usecase;
private DisplayFactsView view;
private BehavioursCoordinator<FactAboutNumber> coordinator;
private ViewModelMapper mapper;
public FactsPresenter(GetRandomFacts usecase,
DisplayFactsView view,
BehavioursCoordinator coordinator,
ViewModelMapper mapper) {
this.usecase = usecase;
this.view = view;
this.coordinator = coordinator;
this.mapper = mapper;
}
public class BehavioursCoordinator<T> implements FlowableTransformer<T, T> {
private AssignEmptyState<T> dealWithEmptyState;
private AssignErrorState<T> assignErrorState;
private LoadingCoordination<T> loadingCoordinator;
// More transfomers
public BehavioursCoordinator(AssignEmptyState<T> dealWithEmptyState,
AssignErrorState<T> assignErrorState,
// More transfomers
LoadingCoordination<T> loadingCoordinator) {
this.dealWithEmptyState = dealWithEmptyState;
this.assignErrorState = assignErrorState;
this.loadingCoordinator = loadingCoordinator;
// More transfomers
}
@Override public Flowable<T> apply(Flowable<T> upstream) {
return upstream
.compose(dealWithEmptyState)
.compose(assignErrorState)
// compose all transformers
.compose(loadingCoordinator);
}
}
public class BehavioursCoordinator<T> implements FlowableTransformer<T, T> {
private AssignEmptyState<T> dealWithEmptyState;
private AssignErrorState<T> assignErrorState;
private LoadingCoordination<T> loadingCoordinator;
// More transfomers
public BehavioursCoordinator(AssignEmptyState<T> dealWithEmptyState,
AssignErrorState<T> assignErrorState,
// More transfomers
LoadingCoordination<T> loadingCoordinator) {
this.dealWithEmptyState = dealWithEmptyState;
this.assignErrorState = assignErrorState;
this.loadingCoordinator = loadingCoordinator;
// More transfomers
}
@Override public Flowable<T> apply(Flowable<T> upstream) {
return upstream
.compose(dealWithEmptyState)
.compose(assignErrorState)
// compose all transformers
.compose(loadingCoordinator);
}
}
public class HideAtStartShowAtError<T> implements FlowableTransformer<T, T> {
private Action whenStart;
private Action atError;
private ErrorPredicate errorPredicate;
private Scheduler targetScheduler;
// Constructor
@Override public Publisher<T> apply(Flowable<T> upstream) {
return upstream
.doOnSubscribe(subscription -> hide())
.doOnError(this::evaluateAndShowIfApplicable);
}
private void evaluateAndShowIfApplicable(Throwable throwable) {
if (errorPredicate.evaluate(throwable)) subscribeAndFireAction(atError);
}
private void hide() { subscribeAndFireAction(whenStart);}
private void subscribeAndFireAction(Action toPerform) {
Completable.fromAction(toPerform).subscribeOn(targetScheduler).subscribe();
}
}
public class HideAtStartShowAtError<T> implements FlowableTransformer<T, T> {
private Action whenStart;
private Action atError;
private ErrorPredicate errorPredicate;
private Scheduler targetScheduler;
// Constructor
@Override public Publisher<T> apply(Flowable<T> upstream) {
return upstream
.doOnSubscribe(subscription -> hide())
.doOnError(this::evaluateAndShowIfApplicable);
}
private void evaluateAndShowIfApplicable(Throwable throwable) {
if (errorPredicate.evaluate(throwable)) subscribeAndFireAction(atError);
}
private void hide() { subscribeAndFireAction(whenStart);}
private void subscribeAndFireAction(Action toPerform) {
Completable.fromAction(toPerform).subscribeOn(targetScheduler).subscribe();
}
}
public class AssignEmptyState<T> implements FlowableTransformer<T, T> {
EmptyStateView view;
Scheduler uiScheduler;
public AssignEmptyState(EmptyStateView view, Scheduler uiScheduler) {
this.view = view;
this.uiScheduler = uiScheduler;
}
@Override public Publisher<T> apply(Flowable<T> upstream) {
HideAtStartShowAtError<T> delegate = new HideAtStartShowAtError<>(
view.hideEmptyState(),
view.showEmptyState(),
error -> error instanceof ContentNotFoundError,
uiScheduler
);
return upstream.compose(delegate);
}
}
public class AssignEmptyState<T> implements FlowableTransformer<T, T> {
EmptyStateView view;
Scheduler uiScheduler;
public AssignEmptyState(EmptyStateView view, Scheduler uiScheduler) {
this.view = view;
this.uiScheduler = uiScheduler;
}
@Override public Publisher<T> apply(Flowable<T> upstream) {
HideAtStartShowAtError<T> delegate = new HideAtStartShowAtError<>(
view.hideEmptyState(),
view.showEmptyState(),
error -> error instanceof ContentNotFoundError,
uiScheduler
);
return upstream.compose(delegate);
}
}
public void fetchData() {
if (isBinded()) {
RxUi.bind(executionPipeline(), view().results());
}
}
private Flowable<SomeModel> executionPipeline() {
return source.fetch()
.compose(networkErrorFeedback)
.compose(loadingWhileFetching)
.compose(coordinateRefresh)
.compose(emptyStateWhenMissingData)
.compose(showErroState)
.map(ViewModelMapper::map)
.flatMap(Flowable::fromIterable);
}
ANTES …
NOVO PIPELINE
public void fetchRandomFacts() {
Flowable<FactViewModel> dataFlow =
usecase.fetchTrivia()
.compose(coordinator)
.map(fact -> mapper.translate(fact));
Disposable toDispose = view.subscribeInto(dataFlow);
// TODO : find a better way to handle this
Disposable disposable = view.subscribeInto(dataFlow);
}
TESTES LIMPOS (I)
@Test public void shouldNotAssignError_WhenFlowEmmits() throws Exception {
Flowable.just("A", "B")
.compose(assignErrorState)
.subscribe();
verify(hide, oneTimeOnly()).run();
verify(show, never()).run();
}
@Test public void shouldNotAssignError_WithEmptyFlow() throws Exception {
Flowable<String> empty = Flowable.empty();
empty.compose(assignErrorState).subscribe();
verify(hide, oneTimeOnly()).run();
verify(show, never()).run();
}
TESTES LIMPOS (II)
@Test public void shouldPresent_NoContentError_IntoView()
throws Exception {
Flowable<FactAboutNumber> noContent =
Flowable.error(new ContentNotFoundError());
when(usecase.fetchTrivia()).thenReturn(noContent);
presenter.fetchRandomFacts();
BehavioursVerifier.with(view)
.showLoadingFirstHideLoadingAfter()
.shouldShowEmptyState()
.shouldNotShowErrorState();
}
TESTES LIMPOS (III)
@Test public void shouldPresent_NoContentError_IntoView()
throws Exception {
Flowable<FactAboutNumber> noContent =
Flowable.error(new ContentNotFoundError());
when(usecase.fetchTrivia()).thenReturn(noContent);
presenter.fetchRandomFacts();
BehavioursVerifier.with(view)
.showLoadingFirstHideLoadingAfter()
.shouldShowEmptyState()
.shouldNotShowErrorState();
}
public class BehavioursVerifier {
private Object target; // Via factory method
public BehavioursVerifier shouldShowErrorState() throws Exception {
checkErrorStateView();
ErrorStateView view = (ErrorStateView) target;
verify(view.showErrorState(), oneTimeOnly()).run();
return this;
}
// For each View and each behavior, check if bind / apply is possible
private void checkEmptyStateView() {
if (!(target instanceof EmptyStateView))
throw new IllegalArgumentException("Not instance of EmptyStateView");
}
public class BehavioursVerifier {
private Object target;
public BehavioursVerifier shouldShowErrorState() throws Exception {
checkErrorStateView();
ErrorStateView view = (ErrorStateView) target;
verify(view.showErrorState(), oneTimeOnly()).run();
return this;
}
// For each View and each behavior, check if bind / apply is possible
private void checkEmptyStateView() {
if (!(target instanceof EmptyStateView))
throw new IllegalArgumentException("Not instance of EmptyStateView");
}
public class BehavioursVerifier {
private Object target;
public BehavioursVerifier shouldShowErrorState() throws Exception {
checkErrorStateView();
ErrorStateView view = (ErrorStateView) target;
verify(view.showErrorState(), oneTimeOnly()).run();
return this;
}
// For each View and each behavior, check if bind / apply is possible
private void checkEmptyStateView() {
if (!(target instanceof EmptyStateView))
throw new IllegalArgumentException("Not instance of EmptyStateView");
}
VANTAGENS
APIs mais próximas à View agora são muito mais simples
Testes mais simples e legíveis, muito próximos ao MVP sem Rx
Menos boilerplate via Coordinator para ações na View que são
relacionadas à condições de fluxo
Coordinator pode ter quantos comportamentos se deseja
independente à qual View for associado
FINAL LAP
Como eliminar as APIs pública
e privada do Presenter para
hooks de lifecycle de Activity
ou Fragment ???
public class ReactivePresenter<V> {
private final CompositeDisposable disposable = new CompositeDisposable();
private V view;
public void bind(V view) { this.view = view; }
public void unbind() {
disposable.dispose();
this.view = null;
}
public CompositeDisposable subscriptions() {
return disposable;
}
protected V view() { return view; }
protected boolean isBinded() { return view != null; }
}
????????
Fazer com que a
liberação de Disposable
seja responsabilidade
de algum colaborador
que conheça o ciclo de
vida do mecanismo de
entrega
VIEW PROTOCOL
VIEW IMPL.
PRESENTER
FlowableSubscriber<U>
Flowable<U>
LifecycleStrategist
Disposable DisposeStrategy
LifecycleObserver
LifecycleOwner
Flowable<T>
(eg. Activity)
public class DisposeStrategy implements LifecycleObserver {
private CompositeDisposable composite = new CompositeDisposable();
void addDisposable(Disposable toDispose) {
composite.add(toDispose);
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) public void release() {
composite.dispose();
}
}
public class LifecycleStrategist {
private DisposeStrategy strategy;
public LifecycleStrategist(LifecycleOwner owner,
DisposeStrategy strategy) {
this.strategy = strategy;
owner.getLifecycle().addObserver(strategy);
}
public void applyStrategy(Disposable toDispose) {
strategy.addDisposable(toDispose);
}
}
public class LifecycleStrategist {
private DisposeStrategy strategy;
public LifecycleStrategist(LifecycleOwner owner,
DisposeStrategy strategy) {
this.strategy = strategy;
owner.getLifecycle().addObserver(strategy);
}
public void applyStrategy(Disposable toDispose) {
strategy.addDisposable(toDispose);
}
}
public class LifecycleStrategist {
private DisposeStrategy strategy;
public LifecycleStrategist(LifecycleOwner owner,
DisposeStrategy strategy) {
this.strategy = strategy;
owner.getLifecycle().addObserver(strategy);
}
public void applyStrategy(Disposable toDispose) {
strategy.addDisposable(toDispose);
}
}
public class FactsPresenter {
private GetRandomFacts usecase;
private DisplayFactsView view;
private BehavioursCoordinator<FactAboutNumber> coordinator;
private ViewModelMapper mapper;
public FactsPresenter(GetRandomFacts usecase,
DisplayFactsView view,
BehavioursCoordinator coordinator,
LifecycleStrategist strategist,
ViewModelMapper mapper) {
this.usecase = usecase;
this.view = view;
this.coordinator = coordinator;
this.strategist = strategist;
this.mapper = mapper;
}
public void fetchRandomFacts() {
Flowable<FactViewModel> dataFlow = coordinator
.coordinateFlow(usecase.fetchTrivia())
.map(fact -> mapper.translateFrom(fact));
Disposable toDispose = view.subscribeInto(dataFlow);
strategist.applyStrategy(toDispose)
}
VANTAGENS OBSERVADAS
Presenter não precisa mais de API pública por motivos de ciclo
de vida do mecanismo de entrega
Fluxo pode ser construído 100% via DI de forma
componetizada
CONCLUSÕES
LIÇÕES APRENDIDAS
Escolher um modelo de arquitetura não é uma tarefa trivial
Evoluir um modelo para obter vantagens de um paradigma
(FRP) é ainda menos trivial
Nem tudo são flores e não tenha medo de errar; adote
iterações na evolução da arquitetura junto com seu time
Novos problemas sempre aparecerão com as novas soluções
TALK IS CHEAP
https://github.com/ubiratansoares/reactive-architectures-playground
Sample usando numbersapi.com
100% RxJava2 + MVP, ilustrando conceitos vistos aqui
Dagger 2.11, Full Android Support APIs
Testes de unidade que importam
Mais outras coisinhas (WIP)
https://speakerdeck.com/ubiratansoares/evoluindo-arquiteturas-reativas
UBIRATAN
SOARES
Computer Scientist by ICMC/USP
Software Engineer @ luizalabs
Google Developer Expert for Android
Teacher, speaker, etc, etc
OBRIGADO
@ubiratanfsoares
ubiratansoares.github.io
https://br.linkedin.com/in/ubiratanfsoares

Weitere ähnliche Inhalte

Ähnlich wie TDC2017 | São Paulo - Trilha Android How we figured out we had a SRE team at - Evoluindo arquiteturas reativas

L2 Web App Development Guest Lecture At University of Surrey 20/11/09
L2 Web App Development Guest Lecture At University of Surrey 20/11/09L2 Web App Development Guest Lecture At University of Surrey 20/11/09
L2 Web App Development Guest Lecture At University of Surrey 20/11/09Daniel Bryant
 
比XML更好用的Java Annotation
比XML更好用的Java Annotation比XML更好用的Java Annotation
比XML更好用的Java Annotationjavatwo2011
 
The Next Step in AS3 Framework Evolution
The Next Step in AS3 Framework EvolutionThe Next Step in AS3 Framework Evolution
The Next Step in AS3 Framework EvolutionFITC
 
Net conf BG xamarin lecture
Net conf BG xamarin lectureNet conf BG xamarin lecture
Net conf BG xamarin lectureTsvyatko Konov
 
Skroutz Android MVP and Adapter Delegates presentation
Skroutz Android MVP and Adapter Delegates  presentationSkroutz Android MVP and Adapter Delegates  presentation
Skroutz Android MVP and Adapter Delegates presentationgmetal
 
Going fullstack React(ive) - Paulo Lopes - Codemotion Amsterdam 2017
Going fullstack React(ive) - Paulo Lopes - Codemotion Amsterdam 2017Going fullstack React(ive) - Paulo Lopes - Codemotion Amsterdam 2017
Going fullstack React(ive) - Paulo Lopes - Codemotion Amsterdam 2017Codemotion
 
softshake 2014 - Java EE
softshake 2014 - Java EEsoftshake 2014 - Java EE
softshake 2014 - Java EEAlexis Hassler
 
Solid Software Design Principles
Solid Software Design PrinciplesSolid Software Design Principles
Solid Software Design PrinciplesJon Kruger
 
Architecture components - IT Talk
Architecture components - IT TalkArchitecture components - IT Talk
Architecture components - IT TalkConstantine Mars
 
Architecture Components
Architecture Components Architecture Components
Architecture Components DataArt
 
Quick and Easy Development with Node.js and Couchbase Server
Quick and Easy Development with Node.js and Couchbase ServerQuick and Easy Development with Node.js and Couchbase Server
Quick and Easy Development with Node.js and Couchbase ServerNic Raboy
 
Android Architecture Components
Android Architecture ComponentsAndroid Architecture Components
Android Architecture ComponentsBurhanuddinRashid
 
Microservice Come in Systems
Microservice Come in SystemsMicroservice Come in Systems
Microservice Come in SystemsMarkus Eisele
 
2008 - TechDays PT: WCF, JSON and AJAX for performance and manageability
2008 - TechDays PT: WCF, JSON and AJAX for performance and manageability2008 - TechDays PT: WCF, JSON and AJAX for performance and manageability
2008 - TechDays PT: WCF, JSON and AJAX for performance and manageabilityDaniel Fisher
 
Automated UI testing for iOS apps using KIF framework and Swift
Automated UI testing for iOS apps using KIF framework and SwiftAutomated UI testing for iOS apps using KIF framework and Swift
Automated UI testing for iOS apps using KIF framework and SwiftJurgis Kirsakmens
 
A full introductory guide to React
A full introductory guide to ReactA full introductory guide to React
A full introductory guide to ReactJean Carlo Emer
 
Creating a polyglottestframework
Creating a polyglottestframeworkCreating a polyglottestframework
Creating a polyglottestframeworkerwindeg
 
"Migrate large gwt applications - Lessons Learned" By Harald Pehl
"Migrate large gwt applications - Lessons Learned" By Harald Pehl"Migrate large gwt applications - Lessons Learned" By Harald Pehl
"Migrate large gwt applications - Lessons Learned" By Harald PehlGWTcon
 

Ähnlich wie TDC2017 | São Paulo - Trilha Android How we figured out we had a SRE team at - Evoluindo arquiteturas reativas (20)

L2 Web App Development Guest Lecture At University of Surrey 20/11/09
L2 Web App Development Guest Lecture At University of Surrey 20/11/09L2 Web App Development Guest Lecture At University of Surrey 20/11/09
L2 Web App Development Guest Lecture At University of Surrey 20/11/09
 
比XML更好用的Java Annotation
比XML更好用的Java Annotation比XML更好用的Java Annotation
比XML更好用的Java Annotation
 
The Next Step in AS3 Framework Evolution
The Next Step in AS3 Framework EvolutionThe Next Step in AS3 Framework Evolution
The Next Step in AS3 Framework Evolution
 
Android best practices
Android best practicesAndroid best practices
Android best practices
 
Net conf BG xamarin lecture
Net conf BG xamarin lectureNet conf BG xamarin lecture
Net conf BG xamarin lecture
 
Backendless apps
Backendless appsBackendless apps
Backendless apps
 
Skroutz Android MVP and Adapter Delegates presentation
Skroutz Android MVP and Adapter Delegates  presentationSkroutz Android MVP and Adapter Delegates  presentation
Skroutz Android MVP and Adapter Delegates presentation
 
Going fullstack React(ive) - Paulo Lopes - Codemotion Amsterdam 2017
Going fullstack React(ive) - Paulo Lopes - Codemotion Amsterdam 2017Going fullstack React(ive) - Paulo Lopes - Codemotion Amsterdam 2017
Going fullstack React(ive) - Paulo Lopes - Codemotion Amsterdam 2017
 
softshake 2014 - Java EE
softshake 2014 - Java EEsoftshake 2014 - Java EE
softshake 2014 - Java EE
 
Solid Software Design Principles
Solid Software Design PrinciplesSolid Software Design Principles
Solid Software Design Principles
 
Architecture components - IT Talk
Architecture components - IT TalkArchitecture components - IT Talk
Architecture components - IT Talk
 
Architecture Components
Architecture Components Architecture Components
Architecture Components
 
Quick and Easy Development with Node.js and Couchbase Server
Quick and Easy Development with Node.js and Couchbase ServerQuick and Easy Development with Node.js and Couchbase Server
Quick and Easy Development with Node.js and Couchbase Server
 
Android Architecture Components
Android Architecture ComponentsAndroid Architecture Components
Android Architecture Components
 
Microservice Come in Systems
Microservice Come in SystemsMicroservice Come in Systems
Microservice Come in Systems
 
2008 - TechDays PT: WCF, JSON and AJAX for performance and manageability
2008 - TechDays PT: WCF, JSON and AJAX for performance and manageability2008 - TechDays PT: WCF, JSON and AJAX for performance and manageability
2008 - TechDays PT: WCF, JSON and AJAX for performance and manageability
 
Automated UI testing for iOS apps using KIF framework and Swift
Automated UI testing for iOS apps using KIF framework and SwiftAutomated UI testing for iOS apps using KIF framework and Swift
Automated UI testing for iOS apps using KIF framework and Swift
 
A full introductory guide to React
A full introductory guide to ReactA full introductory guide to React
A full introductory guide to React
 
Creating a polyglottestframework
Creating a polyglottestframeworkCreating a polyglottestframework
Creating a polyglottestframework
 
"Migrate large gwt applications - Lessons Learned" By Harald Pehl
"Migrate large gwt applications - Lessons Learned" By Harald Pehl"Migrate large gwt applications - Lessons Learned" By Harald Pehl
"Migrate large gwt applications - Lessons Learned" By Harald Pehl
 

Mehr von tdc-globalcode

TDC2019 Intel Software Day - Visao Computacional e IA a servico da humanidade
TDC2019 Intel Software Day - Visao Computacional e IA a servico da humanidadeTDC2019 Intel Software Day - Visao Computacional e IA a servico da humanidade
TDC2019 Intel Software Day - Visao Computacional e IA a servico da humanidadetdc-globalcode
 
TDC2019 Intel Software Day - Tecnicas de Programacao Paralela em Machine Lear...
TDC2019 Intel Software Day - Tecnicas de Programacao Paralela em Machine Lear...TDC2019 Intel Software Day - Tecnicas de Programacao Paralela em Machine Lear...
TDC2019 Intel Software Day - Tecnicas de Programacao Paralela em Machine Lear...tdc-globalcode
 
TDC2019 Intel Software Day - ACATE - Cases de Sucesso
TDC2019 Intel Software Day - ACATE - Cases de SucessoTDC2019 Intel Software Day - ACATE - Cases de Sucesso
TDC2019 Intel Software Day - ACATE - Cases de Sucessotdc-globalcode
 
TDC2019 Intel Software Day - Otimizacao grafica com o Intel GPA
TDC2019 Intel Software Day - Otimizacao grafica com o Intel GPATDC2019 Intel Software Day - Otimizacao grafica com o Intel GPA
TDC2019 Intel Software Day - Otimizacao grafica com o Intel GPAtdc-globalcode
 
TDC2019 Intel Software Day - Deteccao de objetos em tempo real com OpenVino
TDC2019 Intel Software Day - Deteccao de objetos em tempo real com OpenVinoTDC2019 Intel Software Day - Deteccao de objetos em tempo real com OpenVino
TDC2019 Intel Software Day - Deteccao de objetos em tempo real com OpenVinotdc-globalcode
 
TDC2019 Intel Software Day - OpenCV: Inteligencia artificial e Visao Computac...
TDC2019 Intel Software Day - OpenCV: Inteligencia artificial e Visao Computac...TDC2019 Intel Software Day - OpenCV: Inteligencia artificial e Visao Computac...
TDC2019 Intel Software Day - OpenCV: Inteligencia artificial e Visao Computac...tdc-globalcode
 
TDC2019 Intel Software Day - Inferencia de IA em edge devices
TDC2019 Intel Software Day - Inferencia de IA em edge devicesTDC2019 Intel Software Day - Inferencia de IA em edge devices
TDC2019 Intel Software Day - Inferencia de IA em edge devicestdc-globalcode
 
Trilha BigData - Banco de Dados Orientado a Grafos na Seguranca Publica
Trilha BigData - Banco de Dados Orientado a Grafos na Seguranca PublicaTrilha BigData - Banco de Dados Orientado a Grafos na Seguranca Publica
Trilha BigData - Banco de Dados Orientado a Grafos na Seguranca Publicatdc-globalcode
 
Trilha .Net - Programacao funcional usando f#
Trilha .Net - Programacao funcional usando f#Trilha .Net - Programacao funcional usando f#
Trilha .Net - Programacao funcional usando f#tdc-globalcode
 
TDC2018SP | Trilha Go - Case Easylocus
TDC2018SP | Trilha Go - Case EasylocusTDC2018SP | Trilha Go - Case Easylocus
TDC2018SP | Trilha Go - Case Easylocustdc-globalcode
 
TDC2018SP | Trilha Modern Web - Para onde caminha a Web?
TDC2018SP | Trilha Modern Web - Para onde caminha a Web?TDC2018SP | Trilha Modern Web - Para onde caminha a Web?
TDC2018SP | Trilha Modern Web - Para onde caminha a Web?tdc-globalcode
 
TDC2018SP | Trilha Go - Clean architecture em Golang
TDC2018SP | Trilha Go - Clean architecture em GolangTDC2018SP | Trilha Go - Clean architecture em Golang
TDC2018SP | Trilha Go - Clean architecture em Golangtdc-globalcode
 
TDC2018SP | Trilha Go - "Go" tambem e linguagem de QA
TDC2018SP | Trilha Go - "Go" tambem e linguagem de QATDC2018SP | Trilha Go - "Go" tambem e linguagem de QA
TDC2018SP | Trilha Go - "Go" tambem e linguagem de QAtdc-globalcode
 
TDC2018SP | Trilha Mobile - Digital Wallets - Seguranca, inovacao e tendencia
TDC2018SP | Trilha Mobile - Digital Wallets - Seguranca, inovacao e tendenciaTDC2018SP | Trilha Mobile - Digital Wallets - Seguranca, inovacao e tendencia
TDC2018SP | Trilha Mobile - Digital Wallets - Seguranca, inovacao e tendenciatdc-globalcode
 
TDC2018SP | Trilha .Net - Real Time apps com Azure SignalR Service
TDC2018SP | Trilha .Net - Real Time apps com Azure SignalR ServiceTDC2018SP | Trilha .Net - Real Time apps com Azure SignalR Service
TDC2018SP | Trilha .Net - Real Time apps com Azure SignalR Servicetdc-globalcode
 
TDC2018SP | Trilha .Net - Passado, Presente e Futuro do .NET
TDC2018SP | Trilha .Net - Passado, Presente e Futuro do .NETTDC2018SP | Trilha .Net - Passado, Presente e Futuro do .NET
TDC2018SP | Trilha .Net - Passado, Presente e Futuro do .NETtdc-globalcode
 
TDC2018SP | Trilha .Net - Novidades do C# 7 e 8
TDC2018SP | Trilha .Net - Novidades do C# 7 e 8TDC2018SP | Trilha .Net - Novidades do C# 7 e 8
TDC2018SP | Trilha .Net - Novidades do C# 7 e 8tdc-globalcode
 
TDC2018SP | Trilha .Net - Obtendo metricas com TDD utilizando build automatiz...
TDC2018SP | Trilha .Net - Obtendo metricas com TDD utilizando build automatiz...TDC2018SP | Trilha .Net - Obtendo metricas com TDD utilizando build automatiz...
TDC2018SP | Trilha .Net - Obtendo metricas com TDD utilizando build automatiz...tdc-globalcode
 
TDC2018SP | Trilha .Net - .NET funcional com F#
TDC2018SP | Trilha .Net - .NET funcional com F#TDC2018SP | Trilha .Net - .NET funcional com F#
TDC2018SP | Trilha .Net - .NET funcional com F#tdc-globalcode
 
TDC2018SP | Trilha .Net - Crie SPAs com Razor e C# usando Blazor em .Net Core
TDC2018SP | Trilha .Net - Crie SPAs com Razor e C# usando Blazor  em .Net CoreTDC2018SP | Trilha .Net - Crie SPAs com Razor e C# usando Blazor  em .Net Core
TDC2018SP | Trilha .Net - Crie SPAs com Razor e C# usando Blazor em .Net Coretdc-globalcode
 

Mehr von tdc-globalcode (20)

TDC2019 Intel Software Day - Visao Computacional e IA a servico da humanidade
TDC2019 Intel Software Day - Visao Computacional e IA a servico da humanidadeTDC2019 Intel Software Day - Visao Computacional e IA a servico da humanidade
TDC2019 Intel Software Day - Visao Computacional e IA a servico da humanidade
 
TDC2019 Intel Software Day - Tecnicas de Programacao Paralela em Machine Lear...
TDC2019 Intel Software Day - Tecnicas de Programacao Paralela em Machine Lear...TDC2019 Intel Software Day - Tecnicas de Programacao Paralela em Machine Lear...
TDC2019 Intel Software Day - Tecnicas de Programacao Paralela em Machine Lear...
 
TDC2019 Intel Software Day - ACATE - Cases de Sucesso
TDC2019 Intel Software Day - ACATE - Cases de SucessoTDC2019 Intel Software Day - ACATE - Cases de Sucesso
TDC2019 Intel Software Day - ACATE - Cases de Sucesso
 
TDC2019 Intel Software Day - Otimizacao grafica com o Intel GPA
TDC2019 Intel Software Day - Otimizacao grafica com o Intel GPATDC2019 Intel Software Day - Otimizacao grafica com o Intel GPA
TDC2019 Intel Software Day - Otimizacao grafica com o Intel GPA
 
TDC2019 Intel Software Day - Deteccao de objetos em tempo real com OpenVino
TDC2019 Intel Software Day - Deteccao de objetos em tempo real com OpenVinoTDC2019 Intel Software Day - Deteccao de objetos em tempo real com OpenVino
TDC2019 Intel Software Day - Deteccao de objetos em tempo real com OpenVino
 
TDC2019 Intel Software Day - OpenCV: Inteligencia artificial e Visao Computac...
TDC2019 Intel Software Day - OpenCV: Inteligencia artificial e Visao Computac...TDC2019 Intel Software Day - OpenCV: Inteligencia artificial e Visao Computac...
TDC2019 Intel Software Day - OpenCV: Inteligencia artificial e Visao Computac...
 
TDC2019 Intel Software Day - Inferencia de IA em edge devices
TDC2019 Intel Software Day - Inferencia de IA em edge devicesTDC2019 Intel Software Day - Inferencia de IA em edge devices
TDC2019 Intel Software Day - Inferencia de IA em edge devices
 
Trilha BigData - Banco de Dados Orientado a Grafos na Seguranca Publica
Trilha BigData - Banco de Dados Orientado a Grafos na Seguranca PublicaTrilha BigData - Banco de Dados Orientado a Grafos na Seguranca Publica
Trilha BigData - Banco de Dados Orientado a Grafos na Seguranca Publica
 
Trilha .Net - Programacao funcional usando f#
Trilha .Net - Programacao funcional usando f#Trilha .Net - Programacao funcional usando f#
Trilha .Net - Programacao funcional usando f#
 
TDC2018SP | Trilha Go - Case Easylocus
TDC2018SP | Trilha Go - Case EasylocusTDC2018SP | Trilha Go - Case Easylocus
TDC2018SP | Trilha Go - Case Easylocus
 
TDC2018SP | Trilha Modern Web - Para onde caminha a Web?
TDC2018SP | Trilha Modern Web - Para onde caminha a Web?TDC2018SP | Trilha Modern Web - Para onde caminha a Web?
TDC2018SP | Trilha Modern Web - Para onde caminha a Web?
 
TDC2018SP | Trilha Go - Clean architecture em Golang
TDC2018SP | Trilha Go - Clean architecture em GolangTDC2018SP | Trilha Go - Clean architecture em Golang
TDC2018SP | Trilha Go - Clean architecture em Golang
 
TDC2018SP | Trilha Go - "Go" tambem e linguagem de QA
TDC2018SP | Trilha Go - "Go" tambem e linguagem de QATDC2018SP | Trilha Go - "Go" tambem e linguagem de QA
TDC2018SP | Trilha Go - "Go" tambem e linguagem de QA
 
TDC2018SP | Trilha Mobile - Digital Wallets - Seguranca, inovacao e tendencia
TDC2018SP | Trilha Mobile - Digital Wallets - Seguranca, inovacao e tendenciaTDC2018SP | Trilha Mobile - Digital Wallets - Seguranca, inovacao e tendencia
TDC2018SP | Trilha Mobile - Digital Wallets - Seguranca, inovacao e tendencia
 
TDC2018SP | Trilha .Net - Real Time apps com Azure SignalR Service
TDC2018SP | Trilha .Net - Real Time apps com Azure SignalR ServiceTDC2018SP | Trilha .Net - Real Time apps com Azure SignalR Service
TDC2018SP | Trilha .Net - Real Time apps com Azure SignalR Service
 
TDC2018SP | Trilha .Net - Passado, Presente e Futuro do .NET
TDC2018SP | Trilha .Net - Passado, Presente e Futuro do .NETTDC2018SP | Trilha .Net - Passado, Presente e Futuro do .NET
TDC2018SP | Trilha .Net - Passado, Presente e Futuro do .NET
 
TDC2018SP | Trilha .Net - Novidades do C# 7 e 8
TDC2018SP | Trilha .Net - Novidades do C# 7 e 8TDC2018SP | Trilha .Net - Novidades do C# 7 e 8
TDC2018SP | Trilha .Net - Novidades do C# 7 e 8
 
TDC2018SP | Trilha .Net - Obtendo metricas com TDD utilizando build automatiz...
TDC2018SP | Trilha .Net - Obtendo metricas com TDD utilizando build automatiz...TDC2018SP | Trilha .Net - Obtendo metricas com TDD utilizando build automatiz...
TDC2018SP | Trilha .Net - Obtendo metricas com TDD utilizando build automatiz...
 
TDC2018SP | Trilha .Net - .NET funcional com F#
TDC2018SP | Trilha .Net - .NET funcional com F#TDC2018SP | Trilha .Net - .NET funcional com F#
TDC2018SP | Trilha .Net - .NET funcional com F#
 
TDC2018SP | Trilha .Net - Crie SPAs com Razor e C# usando Blazor em .Net Core
TDC2018SP | Trilha .Net - Crie SPAs com Razor e C# usando Blazor  em .Net CoreTDC2018SP | Trilha .Net - Crie SPAs com Razor e C# usando Blazor  em .Net Core
TDC2018SP | Trilha .Net - Crie SPAs com Razor e C# usando Blazor em .Net Core
 

Kürzlich hochgeladen

Micro-Scholarship, What it is, How can it help me.pdf
Micro-Scholarship, What it is, How can it help me.pdfMicro-Scholarship, What it is, How can it help me.pdf
Micro-Scholarship, What it is, How can it help me.pdfPoh-Sun Goh
 
Python Notes for mca i year students osmania university.docx
Python Notes for mca i year students osmania university.docxPython Notes for mca i year students osmania university.docx
Python Notes for mca i year students osmania university.docxRamakrishna Reddy Bijjam
 
Spellings Wk 3 English CAPS CARES Please Practise
Spellings Wk 3 English CAPS CARES Please PractiseSpellings Wk 3 English CAPS CARES Please Practise
Spellings Wk 3 English CAPS CARES Please PractiseAnaAcapella
 
On National Teacher Day, meet the 2024-25 Kenan Fellows
On National Teacher Day, meet the 2024-25 Kenan FellowsOn National Teacher Day, meet the 2024-25 Kenan Fellows
On National Teacher Day, meet the 2024-25 Kenan FellowsMebane Rash
 
Making communications land - Are they received and understood as intended? we...
Making communications land - Are they received and understood as intended? we...Making communications land - Are they received and understood as intended? we...
Making communications land - Are they received and understood as intended? we...Association for Project Management
 
UGC NET Paper 1 Mathematical Reasoning & Aptitude.pdf
UGC NET Paper 1 Mathematical Reasoning & Aptitude.pdfUGC NET Paper 1 Mathematical Reasoning & Aptitude.pdf
UGC NET Paper 1 Mathematical Reasoning & Aptitude.pdfNirmal Dwivedi
 
Towards a code of practice for AI in AT.pptx
Towards a code of practice for AI in AT.pptxTowards a code of practice for AI in AT.pptx
Towards a code of practice for AI in AT.pptxJisc
 
SKILL OF INTRODUCING THE LESSON MICRO SKILLS.pptx
SKILL OF INTRODUCING THE LESSON MICRO SKILLS.pptxSKILL OF INTRODUCING THE LESSON MICRO SKILLS.pptx
SKILL OF INTRODUCING THE LESSON MICRO SKILLS.pptxAmanpreet Kaur
 
Salient Features of India constitution especially power and functions
Salient Features of India constitution especially power and functionsSalient Features of India constitution especially power and functions
Salient Features of India constitution especially power and functionsKarakKing
 
Introduction to Nonprofit Accounting: The Basics
Introduction to Nonprofit Accounting: The BasicsIntroduction to Nonprofit Accounting: The Basics
Introduction to Nonprofit Accounting: The BasicsTechSoup
 
TỔNG ÔN TẬP THI VÀO LỚP 10 MÔN TIẾNG ANH NĂM HỌC 2023 - 2024 CÓ ĐÁP ÁN (NGỮ Â...
TỔNG ÔN TẬP THI VÀO LỚP 10 MÔN TIẾNG ANH NĂM HỌC 2023 - 2024 CÓ ĐÁP ÁN (NGỮ Â...TỔNG ÔN TẬP THI VÀO LỚP 10 MÔN TIẾNG ANH NĂM HỌC 2023 - 2024 CÓ ĐÁP ÁN (NGỮ Â...
TỔNG ÔN TẬP THI VÀO LỚP 10 MÔN TIẾNG ANH NĂM HỌC 2023 - 2024 CÓ ĐÁP ÁN (NGỮ Â...Nguyen Thanh Tu Collection
 
SOC 101 Demonstration of Learning Presentation
SOC 101 Demonstration of Learning PresentationSOC 101 Demonstration of Learning Presentation
SOC 101 Demonstration of Learning Presentationcamerronhm
 
FSB Advising Checklist - Orientation 2024
FSB Advising Checklist - Orientation 2024FSB Advising Checklist - Orientation 2024
FSB Advising Checklist - Orientation 2024Elizabeth Walsh
 
Accessible Digital Futures project (20/03/2024)
Accessible Digital Futures project (20/03/2024)Accessible Digital Futures project (20/03/2024)
Accessible Digital Futures project (20/03/2024)Jisc
 
Jamworks pilot and AI at Jisc (20/03/2024)
Jamworks pilot and AI at Jisc (20/03/2024)Jamworks pilot and AI at Jisc (20/03/2024)
Jamworks pilot and AI at Jisc (20/03/2024)Jisc
 
ICT Role in 21st Century Education & its Challenges.pptx
ICT Role in 21st Century Education & its Challenges.pptxICT Role in 21st Century Education & its Challenges.pptx
ICT Role in 21st Century Education & its Challenges.pptxAreebaZafar22
 
Sociology 101 Demonstration of Learning Exhibit
Sociology 101 Demonstration of Learning ExhibitSociology 101 Demonstration of Learning Exhibit
Sociology 101 Demonstration of Learning Exhibitjbellavia9
 
Jual Obat Aborsi Hongkong ( Asli No.1 ) 085657271886 Obat Penggugur Kandungan...
Jual Obat Aborsi Hongkong ( Asli No.1 ) 085657271886 Obat Penggugur Kandungan...Jual Obat Aborsi Hongkong ( Asli No.1 ) 085657271886 Obat Penggugur Kandungan...
Jual Obat Aborsi Hongkong ( Asli No.1 ) 085657271886 Obat Penggugur Kandungan...ZurliaSoop
 
Beyond_Borders_Understanding_Anime_and_Manga_Fandom_A_Comprehensive_Audience_...
Beyond_Borders_Understanding_Anime_and_Manga_Fandom_A_Comprehensive_Audience_...Beyond_Borders_Understanding_Anime_and_Manga_Fandom_A_Comprehensive_Audience_...
Beyond_Borders_Understanding_Anime_and_Manga_Fandom_A_Comprehensive_Audience_...Pooja Bhuva
 

Kürzlich hochgeladen (20)

Micro-Scholarship, What it is, How can it help me.pdf
Micro-Scholarship, What it is, How can it help me.pdfMicro-Scholarship, What it is, How can it help me.pdf
Micro-Scholarship, What it is, How can it help me.pdf
 
Python Notes for mca i year students osmania university.docx
Python Notes for mca i year students osmania university.docxPython Notes for mca i year students osmania university.docx
Python Notes for mca i year students osmania university.docx
 
Spellings Wk 3 English CAPS CARES Please Practise
Spellings Wk 3 English CAPS CARES Please PractiseSpellings Wk 3 English CAPS CARES Please Practise
Spellings Wk 3 English CAPS CARES Please Practise
 
On National Teacher Day, meet the 2024-25 Kenan Fellows
On National Teacher Day, meet the 2024-25 Kenan FellowsOn National Teacher Day, meet the 2024-25 Kenan Fellows
On National Teacher Day, meet the 2024-25 Kenan Fellows
 
Making communications land - Are they received and understood as intended? we...
Making communications land - Are they received and understood as intended? we...Making communications land - Are they received and understood as intended? we...
Making communications land - Are they received and understood as intended? we...
 
UGC NET Paper 1 Mathematical Reasoning & Aptitude.pdf
UGC NET Paper 1 Mathematical Reasoning & Aptitude.pdfUGC NET Paper 1 Mathematical Reasoning & Aptitude.pdf
UGC NET Paper 1 Mathematical Reasoning & Aptitude.pdf
 
Towards a code of practice for AI in AT.pptx
Towards a code of practice for AI in AT.pptxTowards a code of practice for AI in AT.pptx
Towards a code of practice for AI in AT.pptx
 
SKILL OF INTRODUCING THE LESSON MICRO SKILLS.pptx
SKILL OF INTRODUCING THE LESSON MICRO SKILLS.pptxSKILL OF INTRODUCING THE LESSON MICRO SKILLS.pptx
SKILL OF INTRODUCING THE LESSON MICRO SKILLS.pptx
 
Salient Features of India constitution especially power and functions
Salient Features of India constitution especially power and functionsSalient Features of India constitution especially power and functions
Salient Features of India constitution especially power and functions
 
Mehran University Newsletter Vol-X, Issue-I, 2024
Mehran University Newsletter Vol-X, Issue-I, 2024Mehran University Newsletter Vol-X, Issue-I, 2024
Mehran University Newsletter Vol-X, Issue-I, 2024
 
Introduction to Nonprofit Accounting: The Basics
Introduction to Nonprofit Accounting: The BasicsIntroduction to Nonprofit Accounting: The Basics
Introduction to Nonprofit Accounting: The Basics
 
TỔNG ÔN TẬP THI VÀO LỚP 10 MÔN TIẾNG ANH NĂM HỌC 2023 - 2024 CÓ ĐÁP ÁN (NGỮ Â...
TỔNG ÔN TẬP THI VÀO LỚP 10 MÔN TIẾNG ANH NĂM HỌC 2023 - 2024 CÓ ĐÁP ÁN (NGỮ Â...TỔNG ÔN TẬP THI VÀO LỚP 10 MÔN TIẾNG ANH NĂM HỌC 2023 - 2024 CÓ ĐÁP ÁN (NGỮ Â...
TỔNG ÔN TẬP THI VÀO LỚP 10 MÔN TIẾNG ANH NĂM HỌC 2023 - 2024 CÓ ĐÁP ÁN (NGỮ Â...
 
SOC 101 Demonstration of Learning Presentation
SOC 101 Demonstration of Learning PresentationSOC 101 Demonstration of Learning Presentation
SOC 101 Demonstration of Learning Presentation
 
FSB Advising Checklist - Orientation 2024
FSB Advising Checklist - Orientation 2024FSB Advising Checklist - Orientation 2024
FSB Advising Checklist - Orientation 2024
 
Accessible Digital Futures project (20/03/2024)
Accessible Digital Futures project (20/03/2024)Accessible Digital Futures project (20/03/2024)
Accessible Digital Futures project (20/03/2024)
 
Jamworks pilot and AI at Jisc (20/03/2024)
Jamworks pilot and AI at Jisc (20/03/2024)Jamworks pilot and AI at Jisc (20/03/2024)
Jamworks pilot and AI at Jisc (20/03/2024)
 
ICT Role in 21st Century Education & its Challenges.pptx
ICT Role in 21st Century Education & its Challenges.pptxICT Role in 21st Century Education & its Challenges.pptx
ICT Role in 21st Century Education & its Challenges.pptx
 
Sociology 101 Demonstration of Learning Exhibit
Sociology 101 Demonstration of Learning ExhibitSociology 101 Demonstration of Learning Exhibit
Sociology 101 Demonstration of Learning Exhibit
 
Jual Obat Aborsi Hongkong ( Asli No.1 ) 085657271886 Obat Penggugur Kandungan...
Jual Obat Aborsi Hongkong ( Asli No.1 ) 085657271886 Obat Penggugur Kandungan...Jual Obat Aborsi Hongkong ( Asli No.1 ) 085657271886 Obat Penggugur Kandungan...
Jual Obat Aborsi Hongkong ( Asli No.1 ) 085657271886 Obat Penggugur Kandungan...
 
Beyond_Borders_Understanding_Anime_and_Manga_Fandom_A_Comprehensive_Audience_...
Beyond_Borders_Understanding_Anime_and_Manga_Fandom_A_Comprehensive_Audience_...Beyond_Borders_Understanding_Anime_and_Manga_Fandom_A_Comprehensive_Audience_...
Beyond_Borders_Understanding_Anime_and_Manga_Fandom_A_Comprehensive_Audience_...
 

TDC2017 | São Paulo - Trilha Android How we figured out we had a SRE team at - Evoluindo arquiteturas reativas

  • 2. O QUE É UMA ARQUITETURA EM MOBILE ?
  • 4.
  • 5. PRINCÍPIOS DE ARQUITETURA Organização Facilidade em se encontrar o que se precisa Menor impedância para se resolver bugs Menos dor ao escalar em tamanho (codebase e devs) Estilo de projeto unificado, definido e defendido pelo time
  • 6. UMA QUEIXA COMUM NA COMUNIDADE MOBILE ?
  • 7. TEM PELO MENOS UM UNIT TEST NO APP?
  • 8. EM MOBILE, ARQUITETURA É CRÍTICA PARA TESTABILIDADE
  • 10.
  • 11.
  • 12.
  • 16. public interface ViewDelegate { void displayResults(DataModel model); void networkingError(); void displayEmptyState(); void displayErrorState(); // More delegation }
  • 17. public class MainActivity extends AppCompatActivity implements ViewDelegate { Presenter presenter; // How to resolve this instance ??? @Override protected void onStart() { super.onStart(); presenter.bindView(this); presenter.fetchData(); } @Override public void displayResults(DataModel model) { // Put data into view } @Override public void networkingError() { // Up to you } @Override public void displayEmptyState() { // And this too! } @Override public void displayErrorState() { // Please, do not mess with your user } }
  • 18. public class Presenter { public void bindView(ViewDelegate delegate) { this.delegate = delegate; } public void fetchData() { source.fetchData(new DataSource.Callback() { @Override public void onDataLoaded(DataModel model) { delegate.displayResults(model); } @Override public void onError(Throwable t) { if (t instanceof NetworkingError) { delegate.networkingError(); } else if (t instanceof NoDataAvailable) { … } } }); } }
  • 19. DATASOURCE REST GATEWAY PRESENTER VIEW DELEGATION CALLBACKS PLATAFORM CONTROLLER CALLBACK UNIT TESTS (Mocked Contract) FUNCTIONAL UI TESTS INTEGRATION TESTS INTEGRATION TESTS (DOUBLES) UNIT TESTS (Mocked Source + Mocked View) DATAMODEL
  • 20. String description = “Blah” String date = “2010-02-26T19:35:24Z” int step = 2 String description = “Blah” LocalDateTime dateTime = (JSR310) TrackingStep currentStep = (enum) String description = “Blah” String formattedDate = “26/02/2010” String currentStep = “Concluído” Response Model Domain Model View Model DATA MODEL
  • 21. PROBLEMAS EM POTENCIAL Qual representação de dados utilizar? Unificada ou separada? Onde aplicar parsing? E formatação para a UI? Callbacks aninhados Memory leaks no nível do mecanismo de entrega Etc
  • 27. REST GATEWAY VIEW DELEGATION VIEW DATA SOURCE Flowable<T> PRESENTER Callback(T) FlowableSubscriber<T> Disposable
  • 28. public interface EventsSource { Flowable<Message> fetchWith(MessageToFetchParameters params); Flowable<Message> sendMessage(MessageToSendParameters params); } ADEUS CALLBACKS !!! 👌
  • 29. public class MessagesInfrastructure implements EventsSource { @Override public Flowable<Message> fetchWith(MessageToFetch params) { return restAPI.getMessages(params) .subscribeOn(Schedulers.io()) .map(PayloadMapper::map) .flatMap(Flowable::fromIterable); } @Override public Flowable<Message> sendMessage(MessageToSend params) { SendMessageToBody body = SendMessageToBody.convert(params); return restAPI.sendMessage(body) .subscribeOn(Schedulers.io()) .flatMap(emptyBody -> fetchWith(sameFrom(params))); } } Chained request, easy !!!!
  • 30. VANTAGENS OBSERVADAS Facilidades via frameworks utilitários para REST / DB Validação de dados de entrada e tradução de modelos como etapas do pipeline Tratamento de erros, auto retry, exponential backoff no “baixo nível”
  • 31. PROBLEMAS OBSERVADOS Consumir os dados no nível da apresentação nos força a rodar comportamentos na thread principal do app (orquestração dos callbacks) Indireção forçada para prover Scheduler via DI, para propósitos de testes Muitas responsabilidades no Presenter
  • 33.
  • 34. REST GATEWAY VIEW DELEGATION VIEW DATA SOURCE Flowable<T> PRESENTER FlowableSubscriber<U> Flowable<U> Disposable
  • 35. public interface SomeView<T> { Function<Flowable<T>, Disposable> results(); Function<Flowable<Unit>, Disposable> showEmptyState(); Function<Flowable<Unit>, Disposable> hideEmptyState(); Function<Flowable<Unit>, Disposable> showLoading(); Function<Flowable<Unit>, Disposable> hideLoading(); // More delegation }
  • 36. public static <T> Disposable bind(Flowable<T> flow, Function<Flowable<T>, Disposable> uiFunc) { return uiFunc.call(flow); } public static <T> Function<Flowable<T>, Disposable> uiFunction(Consumer<T> uiAction) { return uiFunction(uiAction, () -> {}); } public static <T> Function<Flowable<T>, Disposable> uiFunction(Consumer<T> uiAction, Action done) { try { return flowable -> flowable .observeOn(AndroidSchedulers.mainThread()) .subscribe( uiAction, throwable -> Logger.e(throwable.getMessage()), done ); } catch (Exception e) { throw new RuntimeException();} }
  • 37. public static <T> Disposable bind(Flowable<T> flow, Function<Flowable<T>, Disposable> uiFunc) { return uiFunc.call(flow); } public static <T> Function<Flowable<T>, Disposable> uiFunction(Consumer<T> uiAction) { return uiFunction(uiAction, () -> {}); } public static <T> Function<Flowable<T>, Disposable> uiFunction(Consumer<T> uiAction, Action done) { try { return flowable -> flowable .observeOn(AndroidSchedulers.mainThread()) .subscribe( uiAction, throwable -> Logger.e(throwable.getMessage()), done ); } catch (Exception e) { throw new RuntimeException();} }
  • 38. public static <T> Disposable bind(Flowable<T> flow, Function<Flowable<T>, Disposable> uiFunc) { return uiFunc.call(flow); } public static <T> Function<Flowable<T>, Disposable> uiFunction(Consumer<T> uiAction) { return uiFunction(uiAction, () -> {}); } public static <T> Function<Flowable<T>, Disposable> uiFunction(Consumer<T>uiAction, Action done) { try { return flowable -> flowable .observeOn(AndroidSchedulers.mainThread()) .subscribe( uiAction, throwable -> Logger.e(throwable.getMessage()), done ); } catch (Exception e) { throw new RuntimeException();} }
  • 39. public class MessagingActivity extends BaseActivity implements MessagesStreamView { @Override public Function<Flowable<String>, Disposable> restoreNotSentMessage() { return uiFunction(message -> { Toast.makeText(this, "Erro ao enviar mensagem", LENGTH_SHORT).show(); messageInput.setText(message); }); } @Override public Function<Flowable<String>, Disposable> enableSomeOption() { return uiFunction(action -> optionView.setVisibility(VISIBLE)); } @Override public Function<Flowable<String>, Disposable> disableSomeOption() { return uiFunction(action -> optionView.setVisibility(GONE)); } @Override public Function<Flowable<String>, Disposable> showEmptyState() { return uiFunction(action -> emptyStateContainer.setVisibility(VISIBLE)); } // More delegate methods
  • 40. public class MessagingActivity extends BaseActivity implements MessagesStreamView { @Override public Function<Flowable<String>, Disposable> restoreNotSentMessage() { return uiFunction(message -> { Toast.makeText(this, "Erro ao enviar mensagem", LENGTH_SHORT).show(); messageInput.setText(message); }); } @Override public Function<Flowable<String>, Disposable> enableComplaintOption() { return uiFunction(action -> optionView.setVisibility(VISIBLE)); } @Override public Function<Flowable<String>, Disposable> disableComplaintOption() { return uiFunction(action -> optionView.setVisibility(GONE)); } @Override public Function<Flowable<String>, Disposable> showEmptyState() { return uiFunction(action -> emptyStateContainer.setVisibility(VISIBLE)); } // More delegate methods
  • 41. public class ReactivePresenter<V> { private final CompositeDisposable disposable = new CompositeDisposable(); private V view; public void bind(V view) { this.view = view; } public void unbind() { disposable.clear(); this.view = null; } public CompositeDisposable subscriptions() { return disposable; } protected V view() { return view; } protected boolean isBinded() { return view != null; } }
  • 42. public class ReactivePresenter<V> { private final CompositeDisposable disposable = new CompositeDisposable(); private V view; public void bind(V view) { this.view = view; } public void unbind() { disposable.dispose(); this.view = null; } public CompositeDisposable subscriptions() { return disposable; } protected V view() { return view; } protected boolean isBinded() { return view != null; } }
  • 43. public void userRequiredMediation(String userId, String messageText) { MessageToSendParameters parameters = new MessageToSendParameters.Builder() .userId(userId) // … .messageText(messageText) .build(); executionPipeline(parameters); } private void executionPipeline(MessageToSend parameters) { Flowable<HelpDeskEventViewModel> execution = source.sendMessage(parameters) .doOnSubscribe(this::prepareToLoad) .map(ViewModelMappers::map) .flatMap(Flowable::fromIterable) .doOnCompleted(this::finishLoadingMessages); subscriptions().add(bind(execution, view().onMessagesLoaded())); }
  • 44. public void userRequiredMediation(String userId, String messageText) { MessageToSendParameters parameters = new MessageToSendParameters.Builder() .userId(userId) // … .messageText(messageText) .build(); executionPipeline(parameters); } private void executionPipeline(MessageToSend parameters) { Flowable<HelpDeskEventViewModel> execution = source.sendMessage(parameters) .doOnSubscribe(this::prepareToSend) .map(ViewModelMappers::map) .flatMap(Flowable::fromIterable) .doOnCompleted(this::finishSendMessage); subscriptions().add(bind(execution, view().onMessagesLoaded())); }
  • 45. VANTAGENS OBSERVADAS Presenter não precisa mais da noção de threading Presenter passar a orquestrar a UI através de um pipeline de execução bem definido Condições relacionadas aos dados no ciclo de vida do fluxo podem ser disparada a partir do pipeline Tradução de ViewModels é uma etapa do pipeline
  • 46. PROBLEMAS OBSERVADOS 1) Protocolo View ainda gordo 2) “Repetição” de código entre Presenters, normalmente relacionada a comportamentos de UI similares que acompanhando o ciclo de vida da sequências - Mostrar empty state se não houver dados - Mostrar loading ao iniciar operação; esconder ao terminar - Etc 3) Testes ruins de serem lidos
  • 47. @Test public void shouldNotDisplayResults_WhenEmptyData() { presenter.bind(view); // When source has no data to return when(source.getResults()).thenReturn(Flowable.empty()); // and presenter requires data presenter.fetchResults(); // we should not display any data into View verify(view.resultsDeliveredAction, never()).call(Flowable.just(any()); }
  • 48. public class MockView implements SomeView { @Mock public Action resultsDeliveredAction; public MockFAQView() { MockitoAnnotations.initMocks(this); } @Override public Function<Flowable<ViewModel>, Disposable> onResults() { return flowable -> flowable.subscribe(resultsDeliveredAction); } ...
  • 49. public class MockView implements SomeView { @Mock public Action resultsDeliveredAction; public MockFAQView() { MockitoAnnotations.initMocks(this); } @Override public Function<Flowable<ViewModel>, Disposable> onResults() { return flowable -> flowable.subscribe(resultsDeliveredAction); } ...
  • 51. public interface SomeView<T> { Func1<Flowable<T>, Disposable> results(); Function<Flowable<Unit>, Disposable> showEmptyState(); Function<Flowable<Unit>, Disposable> hideEmptyState(); Function<Flowable<Unit>, Disposable> showLoading(); Function<Flowable<Unit>, Disposable> hideLoading(); Function<Flowable<Unit>, Disposable> networkError(); Function<Flowable<Unit>, Disposable> networkUnavailable(); Function<Flowable<Unit>, Disposable> networkSlow(); }
  • 52. UI BEHAVIOR VIEW PROTOCOL UI BEHAVIOR UI BEHAVIOR UI BEHAVIOR . . .
  • 53. public interface EmptyStateView<T> { Function<Flowable<Unit>, Disposable> showEmptyState(); Function<Flowable<Unit>, Disposable> hideEmptyState(); } public interface LoadingView<T> { Function<Flowable<Unit>, Disposable> showLoading(); Function<Flowable<Unit>, Disposable> hideLoading(); }
  • 54. public interface SomeView<T> extends LoadingView, EmptyStateView, NetworkingReporterView { Function<Flowable<T>, Disposable> displayResults(); } public interface NetworkingReporterView<T> { Function<Flowable<Unit>, Disposable> networkError(); Function<Flowable<Unit>, Disposable> networkUnavailable(); Function<Flowable<Unit>, Disposable> networkSlow(); }
  • 55. - Cada comportamento poderia ter o seu “mini-presenter” associado, e o Presenter “grande” faria a orquestração dos colaboradores - Melhor estratégia : fazer a composição ser uma etapa do pipeline !!!
  • 57. public class LoadingWhenProcessing<T> implements FlowableTransformer<T, T> { private PublishSubject<Unit> show, hide = PublishSubject.create(); public Dispsoable bind(LoadingView view) { CompositeDisposable composite = new CompositeDisposable(); composite.add(bind(show, view.showLoading())); composite.add(bind(hide, view.hideLoading())); return composite; } @Override public Flowable<T> call(Flowable<T> upstream) { return upstream .doOnSubscribe(this::showLoading) .doOnTerminate(this::hideLoading); } private void hideLoading() { hide.onNext(Unit.instance()); } private void showLoading() { show.onNext(Unit.instance()); } }
  • 58. public class SomePresenter extends ReactivePresenter<SomeView> { // Hook all behaviors for view [ ... ] public void executeOperation() { bind(executionPipeline(), view().results()); } private Flowable<Data> executionPipeline() { return source.search() .compose(networkErrorFeedback) .compose(loadingWhenProcessing) .compose(coordinateRefresh) .compose(emptyStateWhenMissingData) .compose(errorWhenProblems) .map(DataViewModelMapper::map); } }
  • 59. VANTAGENS Cada evento delegado para a UI agora é unit-testable de uma mais fácil !!! Presenters apenas orquestram a UI (como prega MVP) Transformers são facilmente reutilizáveis
  • 60. PROBLEMAS ENCONTRADOS (I) 1) Boilerplate para o binding de comportamentos @Override public void bind(SomeView view) { super.bind(view); subscription().add(loadingWhileProcessing.bind(view)); subscription().add(networkErrorFeedback.bind(view)); subscription().add(coordinateRefresh.bind(view)); subscription().add(emptyStateWhenMissingData.bind(view)); subscription().add(errorStateWhenProblem.bind(view)); }
  • 61. PROBLEMAS ENCONTRADOS (II) 2) Comportamentos injetados via DI no Presenter; possível confusão ao fazer pull das dependências 3) Cooperação entre comportamentos, como fazer? 4) Comando para ação na View sinalizado via emissão de item 5) Testes de transformers são mais isolados, mas não necessariamente mais legíveis!
  • 62. @Test public void shouldTransformView_WhenErrorAtStream() { loadingWhileFetching.bindLoadingContent(view); // When stream will propagate an error Flowable<String> stream = Flowable.error(new RuntimeCryptoException()); // and we add this transformation to pipeline stream.compose(loadingWhileFetching) .subscribe( s -> {}, throwable -> {}, () -> {} ); // we still should interact with loading actions verify(view.showLoadingAction).call(Flowable.just(any()); verify(view.hideLoadingAction).call(Flowable.just(any()); }
  • 63.
  • 65. PRINCÍPIO EM SOFTWARE “Camadas mais internas escondem complexidade das camadas mais externas”
  • 66. COROLÁRIO “Camadas mais externas de uma API devem ser mais SIMPLES para seus usuários do que as internas”
  • 67. REMODELANDO AS APIs Queremos manter comportamentos segregados como etapas do pipeline via transformadores Queremos adicionar mais comportamentos que estão associados à condições dos dados de forma transparente Queremos diminuir a fricção para implementação dessa abordagem em novos fluxos Queremos facilitar escrita e entendimento de testes Queremos fornecer 100% dos objetos via DI (incluindo a própria View)
  • 68. REPENSANDO VIEW PASSIVA public interface EmptyStateView<T> { Function<Flowable<Unit>, Disposable> showEmptyState(); Function<Flowable<Unit>, Disposable> hideEmptyState(); }
  • 69. REPENSANDO VIEW PASSIVA public interface EmptyStateView<T> { Function<Flowable<Unit>, Disposable> showEmptyState(); Function<Flowable<Unit>, Disposable> hideEmptyState(); } public interface EmptyStateView { Action showEmptyState(); Action hideEmptyState(); }
  • 70. IMPL. VIEW PASSIVA (ANTES) @Override public Func1<Observable<Unit>, Subscription> showEmptyState() { return RxUi.uiFunction(unit -> emptyState.setVisibility(View.VISIBLE)); } @Override public Func1<Observable<Unit>, Subscription> hideEmptyState() { return RxUi.uiFunction(unit -> emptyState.setVisibility(View.GONE)); } @Override public Func1<Observable<Unit>, Subscription> showLoading() { return RxUi.uiFunction(unit -> progress.setVisibility(View.VISIBLE)); } @Override public Func1<Observable<Unit>, Subscription> hideLoading() { return RxUi.uiFunction(unit -> progress.setVisibility(View.GONE)); } // More delegation
  • 71. IMPL. VIEW PASSIVA (DEPOIS) @Override public Action showLoading() { return () -> loading.setVisibility(View.VISIBLE); } @Override public Action hideLoading() { return () -> loading.setVisibility(View.GONE); } @Override public Action showEmptyState() { return () -> emptyState.setVisibility(View.VISIBLE); } @Override public Action hideEmptyState() { return () -> emptyState.setVisibility(View.GONE); } // More Delegation
  • 72. ENTREGA DE DADOS (ANTES) public interface SomeView extends EmptyStateView, LoadingView, NetworkErrorReporterView { Function<Flowable<ViewModel>, Disposable> onResults(); } // At view implementation @Override public Function<Flowable<ViewModel>, Disposable> onResults() { return RxUi.uiFunction( model -> adapter.add(model), this::displayResults ); }
  • 73. ENTREGA DE DADOS (DEPOIS) public interface DisplayFactsView extends LoadingView, ErrorStateView, EmptyStateView { Disposable subscribeInto(Flowable<FactViewModel> flow); } @Override public Disposable subscribeInto(Flowable<FactViewModel> flow) { return flow .observeOn(AndroidSchedulers.mainThread()) .subscribe( model -> addToAdapter(model), throwable -> Logger.e(throwable.getMessage()), () -> displayResults() ); }
  • 74. ENTREGA DE DADOS (DEPOIS) public interface DisplayFactsView extends LoadingView, ErrorStateView, EmptyStateView { Disposable subscribeInto(Flowable<FactViewModel> flow); } @Override public Disposable subscribeInto(Flowable<FactViewModel> flow) { return flow .observeOn(AndroidSchedulers.mainThread()) .subscribe( model -> addToAdapter(model), throwable -> Logger.e(throwable.getMessage()), () -> displayResults() ); }
  • 75. public ComplexPresenter( DataSource source, NetworkErrorFeedback networkErrorFeedback, LoadingWhileFetching loadingWhileFetching, CoordinateRefreshWhenLoadingContent coordinateRefresh, ShowEmptyStateWhenMissingData emptyStateWhenMissingData, ShowErrorState errorState) { this.source = source; this.networkErrorFeedback = networkErrorFeedback; this.loadingWhileFetching = loadingWhileFetching; this.coordinateRefresh = coordinateRefresh; this.emptyStateWhenMissingData = emptyStateWhenMissingData; this.errorState = errorState; } @Override public void bind(ComplexView view) { super.bind(view); subscriptions().add(loadingWhileFetching.bindLoadingContent(view)); subscriptions().add(networkErrorFeedback.bindNetworkingReporter(view)); subscriptions().add(coordinateRefresh.bindRefreshableView(view)); subscriptions().add(emptyStateWhenMissingData.bindEmptyStateView(view)); subscriptions().add(errorState.bindErrorStateView(view)); } ANTES …
  • 76. public class FactsPresenter { private GetRandomFacts usecase; private DisplayFactsView view; private BehavioursCoordinator<FactAboutNumber> coordinator; private ViewModelMapper mapper; public FactsPresenter(GetRandomFacts usecase, DisplayFactsView view, BehavioursCoordinator coordinator, ViewModelMapper mapper) { this.usecase = usecase; this.view = view; this.coordinator = coordinator; this.mapper = mapper; } DEPOIS!!!
  • 77. public class FactsPresenter { private GetRandomFacts usecase; private DisplayFactsView view; private BehavioursCoordinator<FactAboutNumber> coordinator; private ViewModelMapper mapper; public FactsPresenter(GetRandomFacts usecase, DisplayFactsView view, BehavioursCoordinator coordinator, ViewModelMapper mapper) { this.usecase = usecase; this.view = view; this.coordinator = coordinator; this.mapper = mapper; }
  • 78. public class BehavioursCoordinator<T> implements FlowableTransformer<T, T> { private AssignEmptyState<T> dealWithEmptyState; private AssignErrorState<T> assignErrorState; private LoadingCoordination<T> loadingCoordinator; // More transfomers public BehavioursCoordinator(AssignEmptyState<T> dealWithEmptyState, AssignErrorState<T> assignErrorState, // More transfomers LoadingCoordination<T> loadingCoordinator) { this.dealWithEmptyState = dealWithEmptyState; this.assignErrorState = assignErrorState; this.loadingCoordinator = loadingCoordinator; // More transfomers } @Override public Flowable<T> apply(Flowable<T> upstream) { return upstream .compose(dealWithEmptyState) .compose(assignErrorState) // compose all transformers .compose(loadingCoordinator); } }
  • 79. public class BehavioursCoordinator<T> implements FlowableTransformer<T, T> { private AssignEmptyState<T> dealWithEmptyState; private AssignErrorState<T> assignErrorState; private LoadingCoordination<T> loadingCoordinator; // More transfomers public BehavioursCoordinator(AssignEmptyState<T> dealWithEmptyState, AssignErrorState<T> assignErrorState, // More transfomers LoadingCoordination<T> loadingCoordinator) { this.dealWithEmptyState = dealWithEmptyState; this.assignErrorState = assignErrorState; this.loadingCoordinator = loadingCoordinator; // More transfomers } @Override public Flowable<T> apply(Flowable<T> upstream) { return upstream .compose(dealWithEmptyState) .compose(assignErrorState) // compose all transformers .compose(loadingCoordinator); } }
  • 80. public class HideAtStartShowAtError<T> implements FlowableTransformer<T, T> { private Action whenStart; private Action atError; private ErrorPredicate errorPredicate; private Scheduler targetScheduler; // Constructor @Override public Publisher<T> apply(Flowable<T> upstream) { return upstream .doOnSubscribe(subscription -> hide()) .doOnError(this::evaluateAndShowIfApplicable); } private void evaluateAndShowIfApplicable(Throwable throwable) { if (errorPredicate.evaluate(throwable)) subscribeAndFireAction(atError); } private void hide() { subscribeAndFireAction(whenStart);} private void subscribeAndFireAction(Action toPerform) { Completable.fromAction(toPerform).subscribeOn(targetScheduler).subscribe(); } }
  • 81. public class HideAtStartShowAtError<T> implements FlowableTransformer<T, T> { private Action whenStart; private Action atError; private ErrorPredicate errorPredicate; private Scheduler targetScheduler; // Constructor @Override public Publisher<T> apply(Flowable<T> upstream) { return upstream .doOnSubscribe(subscription -> hide()) .doOnError(this::evaluateAndShowIfApplicable); } private void evaluateAndShowIfApplicable(Throwable throwable) { if (errorPredicate.evaluate(throwable)) subscribeAndFireAction(atError); } private void hide() { subscribeAndFireAction(whenStart);} private void subscribeAndFireAction(Action toPerform) { Completable.fromAction(toPerform).subscribeOn(targetScheduler).subscribe(); } }
  • 82. public class AssignEmptyState<T> implements FlowableTransformer<T, T> { EmptyStateView view; Scheduler uiScheduler; public AssignEmptyState(EmptyStateView view, Scheduler uiScheduler) { this.view = view; this.uiScheduler = uiScheduler; } @Override public Publisher<T> apply(Flowable<T> upstream) { HideAtStartShowAtError<T> delegate = new HideAtStartShowAtError<>( view.hideEmptyState(), view.showEmptyState(), error -> error instanceof ContentNotFoundError, uiScheduler ); return upstream.compose(delegate); } }
  • 83. public class AssignEmptyState<T> implements FlowableTransformer<T, T> { EmptyStateView view; Scheduler uiScheduler; public AssignEmptyState(EmptyStateView view, Scheduler uiScheduler) { this.view = view; this.uiScheduler = uiScheduler; } @Override public Publisher<T> apply(Flowable<T> upstream) { HideAtStartShowAtError<T> delegate = new HideAtStartShowAtError<>( view.hideEmptyState(), view.showEmptyState(), error -> error instanceof ContentNotFoundError, uiScheduler ); return upstream.compose(delegate); } }
  • 84. public void fetchData() { if (isBinded()) { RxUi.bind(executionPipeline(), view().results()); } } private Flowable<SomeModel> executionPipeline() { return source.fetch() .compose(networkErrorFeedback) .compose(loadingWhileFetching) .compose(coordinateRefresh) .compose(emptyStateWhenMissingData) .compose(showErroState) .map(ViewModelMapper::map) .flatMap(Flowable::fromIterable); } ANTES …
  • 85. NOVO PIPELINE public void fetchRandomFacts() { Flowable<FactViewModel> dataFlow = usecase.fetchTrivia() .compose(coordinator) .map(fact -> mapper.translate(fact)); Disposable toDispose = view.subscribeInto(dataFlow); // TODO : find a better way to handle this Disposable disposable = view.subscribeInto(dataFlow); }
  • 86. TESTES LIMPOS (I) @Test public void shouldNotAssignError_WhenFlowEmmits() throws Exception { Flowable.just("A", "B") .compose(assignErrorState) .subscribe(); verify(hide, oneTimeOnly()).run(); verify(show, never()).run(); } @Test public void shouldNotAssignError_WithEmptyFlow() throws Exception { Flowable<String> empty = Flowable.empty(); empty.compose(assignErrorState).subscribe(); verify(hide, oneTimeOnly()).run(); verify(show, never()).run(); }
  • 87. TESTES LIMPOS (II) @Test public void shouldPresent_NoContentError_IntoView() throws Exception { Flowable<FactAboutNumber> noContent = Flowable.error(new ContentNotFoundError()); when(usecase.fetchTrivia()).thenReturn(noContent); presenter.fetchRandomFacts(); BehavioursVerifier.with(view) .showLoadingFirstHideLoadingAfter() .shouldShowEmptyState() .shouldNotShowErrorState(); }
  • 88. TESTES LIMPOS (III) @Test public void shouldPresent_NoContentError_IntoView() throws Exception { Flowable<FactAboutNumber> noContent = Flowable.error(new ContentNotFoundError()); when(usecase.fetchTrivia()).thenReturn(noContent); presenter.fetchRandomFacts(); BehavioursVerifier.with(view) .showLoadingFirstHideLoadingAfter() .shouldShowEmptyState() .shouldNotShowErrorState(); }
  • 89. public class BehavioursVerifier { private Object target; // Via factory method public BehavioursVerifier shouldShowErrorState() throws Exception { checkErrorStateView(); ErrorStateView view = (ErrorStateView) target; verify(view.showErrorState(), oneTimeOnly()).run(); return this; } // For each View and each behavior, check if bind / apply is possible private void checkEmptyStateView() { if (!(target instanceof EmptyStateView)) throw new IllegalArgumentException("Not instance of EmptyStateView"); }
  • 90. public class BehavioursVerifier { private Object target; public BehavioursVerifier shouldShowErrorState() throws Exception { checkErrorStateView(); ErrorStateView view = (ErrorStateView) target; verify(view.showErrorState(), oneTimeOnly()).run(); return this; } // For each View and each behavior, check if bind / apply is possible private void checkEmptyStateView() { if (!(target instanceof EmptyStateView)) throw new IllegalArgumentException("Not instance of EmptyStateView"); }
  • 91. public class BehavioursVerifier { private Object target; public BehavioursVerifier shouldShowErrorState() throws Exception { checkErrorStateView(); ErrorStateView view = (ErrorStateView) target; verify(view.showErrorState(), oneTimeOnly()).run(); return this; } // For each View and each behavior, check if bind / apply is possible private void checkEmptyStateView() { if (!(target instanceof EmptyStateView)) throw new IllegalArgumentException("Not instance of EmptyStateView"); }
  • 92. VANTAGENS APIs mais próximas à View agora são muito mais simples Testes mais simples e legíveis, muito próximos ao MVP sem Rx Menos boilerplate via Coordinator para ações na View que são relacionadas à condições de fluxo Coordinator pode ter quantos comportamentos se deseja independente à qual View for associado
  • 94. Como eliminar as APIs pública e privada do Presenter para hooks de lifecycle de Activity ou Fragment ???
  • 95. public class ReactivePresenter<V> { private final CompositeDisposable disposable = new CompositeDisposable(); private V view; public void bind(V view) { this.view = view; } public void unbind() { disposable.dispose(); this.view = null; } public CompositeDisposable subscriptions() { return disposable; } protected V view() { return view; } protected boolean isBinded() { return view != null; } } ????????
  • 96. Fazer com que a liberação de Disposable seja responsabilidade de algum colaborador que conheça o ciclo de vida do mecanismo de entrega
  • 97.
  • 98. VIEW PROTOCOL VIEW IMPL. PRESENTER FlowableSubscriber<U> Flowable<U> LifecycleStrategist Disposable DisposeStrategy LifecycleObserver LifecycleOwner Flowable<T> (eg. Activity)
  • 99. public class DisposeStrategy implements LifecycleObserver { private CompositeDisposable composite = new CompositeDisposable(); void addDisposable(Disposable toDispose) { composite.add(toDispose); } @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) public void release() { composite.dispose(); } }
  • 100. public class LifecycleStrategist { private DisposeStrategy strategy; public LifecycleStrategist(LifecycleOwner owner, DisposeStrategy strategy) { this.strategy = strategy; owner.getLifecycle().addObserver(strategy); } public void applyStrategy(Disposable toDispose) { strategy.addDisposable(toDispose); } }
  • 101. public class LifecycleStrategist { private DisposeStrategy strategy; public LifecycleStrategist(LifecycleOwner owner, DisposeStrategy strategy) { this.strategy = strategy; owner.getLifecycle().addObserver(strategy); } public void applyStrategy(Disposable toDispose) { strategy.addDisposable(toDispose); } }
  • 102. public class LifecycleStrategist { private DisposeStrategy strategy; public LifecycleStrategist(LifecycleOwner owner, DisposeStrategy strategy) { this.strategy = strategy; owner.getLifecycle().addObserver(strategy); } public void applyStrategy(Disposable toDispose) { strategy.addDisposable(toDispose); } }
  • 103. public class FactsPresenter { private GetRandomFacts usecase; private DisplayFactsView view; private BehavioursCoordinator<FactAboutNumber> coordinator; private ViewModelMapper mapper; public FactsPresenter(GetRandomFacts usecase, DisplayFactsView view, BehavioursCoordinator coordinator, LifecycleStrategist strategist, ViewModelMapper mapper) { this.usecase = usecase; this.view = view; this.coordinator = coordinator; this.strategist = strategist; this.mapper = mapper; }
  • 104. public void fetchRandomFacts() { Flowable<FactViewModel> dataFlow = coordinator .coordinateFlow(usecase.fetchTrivia()) .map(fact -> mapper.translateFrom(fact)); Disposable toDispose = view.subscribeInto(dataFlow); strategist.applyStrategy(toDispose) }
  • 105. VANTAGENS OBSERVADAS Presenter não precisa mais de API pública por motivos de ciclo de vida do mecanismo de entrega Fluxo pode ser construído 100% via DI de forma componetizada
  • 107. LIÇÕES APRENDIDAS Escolher um modelo de arquitetura não é uma tarefa trivial Evoluir um modelo para obter vantagens de um paradigma (FRP) é ainda menos trivial Nem tudo são flores e não tenha medo de errar; adote iterações na evolução da arquitetura junto com seu time Novos problemas sempre aparecerão com as novas soluções
  • 109. https://github.com/ubiratansoares/reactive-architectures-playground Sample usando numbersapi.com 100% RxJava2 + MVP, ilustrando conceitos vistos aqui Dagger 2.11, Full Android Support APIs Testes de unidade que importam Mais outras coisinhas (WIP)
  • 111. UBIRATAN SOARES Computer Scientist by ICMC/USP Software Engineer @ luizalabs Google Developer Expert for Android Teacher, speaker, etc, etc