SlideShare a Scribd company logo
1 of 112
Download to read offline
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

More Related Content

Similar to 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
 

Similar to 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
 

More from 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
 

More from 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
 

Recently uploaded

Employee wellbeing at the workplace.pptx
Employee wellbeing at the workplace.pptxEmployee wellbeing at the workplace.pptx
Employee wellbeing at the workplace.pptxNirmalaLoungPoorunde1
 
Arihant handbook biology for class 11 .pdf
Arihant handbook biology for class 11 .pdfArihant handbook biology for class 11 .pdf
Arihant handbook biology for class 11 .pdfchloefrazer622
 
APM Welcome, APM North West Network Conference, Synergies Across Sectors
APM Welcome, APM North West Network Conference, Synergies Across SectorsAPM Welcome, APM North West Network Conference, Synergies Across Sectors
APM Welcome, APM North West Network Conference, Synergies Across SectorsAssociation for Project Management
 
Alper Gobel In Media Res Media Component
Alper Gobel In Media Res Media ComponentAlper Gobel In Media Res Media Component
Alper Gobel In Media Res Media ComponentInMediaRes1
 
microwave assisted reaction. General introduction
microwave assisted reaction. General introductionmicrowave assisted reaction. General introduction
microwave assisted reaction. General introductionMaksud Ahmed
 
Q4-W6-Restating Informational Text Grade 3
Q4-W6-Restating Informational Text Grade 3Q4-W6-Restating Informational Text Grade 3
Q4-W6-Restating Informational Text Grade 3JemimahLaneBuaron
 
Solving Puzzles Benefits Everyone (English).pptx
Solving Puzzles Benefits Everyone (English).pptxSolving Puzzles Benefits Everyone (English).pptx
Solving Puzzles Benefits Everyone (English).pptxOH TEIK BIN
 
Software Engineering Methodologies (overview)
Software Engineering Methodologies (overview)Software Engineering Methodologies (overview)
Software Engineering Methodologies (overview)eniolaolutunde
 
PSYCHIATRIC History collection FORMAT.pptx
PSYCHIATRIC   History collection FORMAT.pptxPSYCHIATRIC   History collection FORMAT.pptx
PSYCHIATRIC History collection FORMAT.pptxPoojaSen20
 
The Most Excellent Way | 1 Corinthians 13
The Most Excellent Way | 1 Corinthians 13The Most Excellent Way | 1 Corinthians 13
The Most Excellent Way | 1 Corinthians 13Steve Thomason
 
Call Girls in Dwarka Mor Delhi Contact Us 9654467111
Call Girls in Dwarka Mor Delhi Contact Us 9654467111Call Girls in Dwarka Mor Delhi Contact Us 9654467111
Call Girls in Dwarka Mor Delhi Contact Us 9654467111Sapana Sha
 
Incoming and Outgoing Shipments in 1 STEP Using Odoo 17
Incoming and Outgoing Shipments in 1 STEP Using Odoo 17Incoming and Outgoing Shipments in 1 STEP Using Odoo 17
Incoming and Outgoing Shipments in 1 STEP Using Odoo 17Celine George
 
Sanyam Choudhary Chemistry practical.pdf
Sanyam Choudhary Chemistry practical.pdfSanyam Choudhary Chemistry practical.pdf
Sanyam Choudhary Chemistry practical.pdfsanyamsingh5019
 
Concept of Vouching. B.Com(Hons) /B.Compdf
Concept of Vouching. B.Com(Hons) /B.CompdfConcept of Vouching. B.Com(Hons) /B.Compdf
Concept of Vouching. B.Com(Hons) /B.CompdfUmakantAnnand
 
18-04-UA_REPORT_MEDIALITERAСY_INDEX-DM_23-1-final-eng.pdf
18-04-UA_REPORT_MEDIALITERAСY_INDEX-DM_23-1-final-eng.pdf18-04-UA_REPORT_MEDIALITERAСY_INDEX-DM_23-1-final-eng.pdf
18-04-UA_REPORT_MEDIALITERAСY_INDEX-DM_23-1-final-eng.pdfssuser54595a
 
MENTAL STATUS EXAMINATION format.docx
MENTAL     STATUS EXAMINATION format.docxMENTAL     STATUS EXAMINATION format.docx
MENTAL STATUS EXAMINATION format.docxPoojaSen20
 
URLs and Routing in the Odoo 17 Website App
URLs and Routing in the Odoo 17 Website AppURLs and Routing in the Odoo 17 Website App
URLs and Routing in the Odoo 17 Website AppCeline George
 
How to Make a Pirate ship Primary Education.pptx
How to Make a Pirate ship Primary Education.pptxHow to Make a Pirate ship Primary Education.pptx
How to Make a Pirate ship Primary Education.pptxmanuelaromero2013
 

Recently uploaded (20)

Employee wellbeing at the workplace.pptx
Employee wellbeing at the workplace.pptxEmployee wellbeing at the workplace.pptx
Employee wellbeing at the workplace.pptx
 
Arihant handbook biology for class 11 .pdf
Arihant handbook biology for class 11 .pdfArihant handbook biology for class 11 .pdf
Arihant handbook biology for class 11 .pdf
 
APM Welcome, APM North West Network Conference, Synergies Across Sectors
APM Welcome, APM North West Network Conference, Synergies Across SectorsAPM Welcome, APM North West Network Conference, Synergies Across Sectors
APM Welcome, APM North West Network Conference, Synergies Across Sectors
 
Alper Gobel In Media Res Media Component
Alper Gobel In Media Res Media ComponentAlper Gobel In Media Res Media Component
Alper Gobel In Media Res Media Component
 
microwave assisted reaction. General introduction
microwave assisted reaction. General introductionmicrowave assisted reaction. General introduction
microwave assisted reaction. General introduction
 
Q4-W6-Restating Informational Text Grade 3
Q4-W6-Restating Informational Text Grade 3Q4-W6-Restating Informational Text Grade 3
Q4-W6-Restating Informational Text Grade 3
 
Solving Puzzles Benefits Everyone (English).pptx
Solving Puzzles Benefits Everyone (English).pptxSolving Puzzles Benefits Everyone (English).pptx
Solving Puzzles Benefits Everyone (English).pptx
 
Software Engineering Methodologies (overview)
Software Engineering Methodologies (overview)Software Engineering Methodologies (overview)
Software Engineering Methodologies (overview)
 
PSYCHIATRIC History collection FORMAT.pptx
PSYCHIATRIC   History collection FORMAT.pptxPSYCHIATRIC   History collection FORMAT.pptx
PSYCHIATRIC History collection FORMAT.pptx
 
The Most Excellent Way | 1 Corinthians 13
The Most Excellent Way | 1 Corinthians 13The Most Excellent Way | 1 Corinthians 13
The Most Excellent Way | 1 Corinthians 13
 
Model Call Girl in Bikash Puri Delhi reach out to us at 🔝9953056974🔝
Model Call Girl in Bikash Puri  Delhi reach out to us at 🔝9953056974🔝Model Call Girl in Bikash Puri  Delhi reach out to us at 🔝9953056974🔝
Model Call Girl in Bikash Puri Delhi reach out to us at 🔝9953056974🔝
 
Call Girls in Dwarka Mor Delhi Contact Us 9654467111
Call Girls in Dwarka Mor Delhi Contact Us 9654467111Call Girls in Dwarka Mor Delhi Contact Us 9654467111
Call Girls in Dwarka Mor Delhi Contact Us 9654467111
 
Incoming and Outgoing Shipments in 1 STEP Using Odoo 17
Incoming and Outgoing Shipments in 1 STEP Using Odoo 17Incoming and Outgoing Shipments in 1 STEP Using Odoo 17
Incoming and Outgoing Shipments in 1 STEP Using Odoo 17
 
Sanyam Choudhary Chemistry practical.pdf
Sanyam Choudhary Chemistry practical.pdfSanyam Choudhary Chemistry practical.pdf
Sanyam Choudhary Chemistry practical.pdf
 
Staff of Color (SOC) Retention Efforts DDSD
Staff of Color (SOC) Retention Efforts DDSDStaff of Color (SOC) Retention Efforts DDSD
Staff of Color (SOC) Retention Efforts DDSD
 
Concept of Vouching. B.Com(Hons) /B.Compdf
Concept of Vouching. B.Com(Hons) /B.CompdfConcept of Vouching. B.Com(Hons) /B.Compdf
Concept of Vouching. B.Com(Hons) /B.Compdf
 
18-04-UA_REPORT_MEDIALITERAСY_INDEX-DM_23-1-final-eng.pdf
18-04-UA_REPORT_MEDIALITERAСY_INDEX-DM_23-1-final-eng.pdf18-04-UA_REPORT_MEDIALITERAСY_INDEX-DM_23-1-final-eng.pdf
18-04-UA_REPORT_MEDIALITERAСY_INDEX-DM_23-1-final-eng.pdf
 
MENTAL STATUS EXAMINATION format.docx
MENTAL     STATUS EXAMINATION format.docxMENTAL     STATUS EXAMINATION format.docx
MENTAL STATUS EXAMINATION format.docx
 
URLs and Routing in the Odoo 17 Website App
URLs and Routing in the Odoo 17 Website AppURLs and Routing in the Odoo 17 Website App
URLs and Routing in the Odoo 17 Website App
 
How to Make a Pirate ship Primary Education.pptx
How to Make a Pirate ship Primary Education.pptxHow to Make a Pirate ship Primary Education.pptx
How to Make a Pirate ship Primary Education.pptx
 

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