11. Listeners
No compile-time safety
A dynamic type check needs to be
performed at runtime which could (and will)
lead to crashes.
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is Listener) {
listener = context
} else if (parentFragment is Listener) {
listener = parentFragment as Listener
} else {
throw RuntimeException()
}
}
13. Back Stack
Simple yet hard to use
Back stack works well when you don’t need
to handle back navigation yourself or use
children fragments. However, as soon as you
need to do any customization it quickly falls
apart.
// Attach Root fragment
supportFragmentManager.beginTransaction()
.apply {
add(R.id.fragmentContainer, RootFragment.newInstance(), "Root")
addToBackStack(null)
}
.commit()
// Attach Child fragment
rootFragment.childFragmentManager.beginTransaction()
.apply {
add(R.id.rootContainer, ChildFragment.newInstance(), null)
addToBackStack(null)
}
.commit()
// Press back
15. ● Too many lifecycle states
● No compile-time safety when calling listeners
● Nested fragments is an afterthought
● Passing arguments
● Testability
● Non-restrictive API
● Fragments are an Android thing
● Requires glue code
Problems
17. 500+Mobile Engineers who
worked on new Uber app
200+ Android Engineers who
made at least 1 commint
in Android monorepo
500+iOS Engineers who made
at least 1 commit in iOS
monorepo
Some Numbers
17
26. ● Works well for individual pieces
● Countless variations of the pattern
● Requires glue code
● Development slows down
● Integration points are main source of bugs
Naive MVP / MVVM
37. Interactor
Simple Lifecycle
Just 2 lifecycle events. Sometimes even less
than that. @Override
protected void didBecomeActive(@Nullable Bundle savedInstanceState) {
super.didBecomeActive(savedInstanceState);
// Subscribe to data source. Ready to update UI.
}
@Override
protected void willResignActive(@Nullable Bundle savedInstanceState) {
super.didBecomeActive(savedInstanceState);
// Tear down and remove subscriptions/callbacks.
}
38. Interactor
Business Logic
Loads data from Model layer, makes
decisions and asks Presenter to render UI.
Talks to Router to perform navigation.
@Override
protected void didBecomeActive(@Nullable Bundle savedInstanceState) {
super.didBecomeActive(savedInstanceState);
presenter.showLoading();
productApi.getProducts()
.observeOn(AndroidSchedulers.mainThread())
.as(autoDisposable(this))
.subscribe(result -> {
presenter.hideLoading();
if (result.isSuccess()) {
presenter.showProducts(result.getResult());
} else {
presenter.showError();
}
});
}
39. Router
Navigation
Creates other RIBs and attaches them to
view hierarchy.
void routeToProductDetails(Product product) {
ProductDetailsRouter router = productDetailsBuilder.build(
getView(),
product
);
attachChild(router);
getView().addView(router.getView());
}
40. Builder
Builds
Instantiates Interactor, Router and View.
Provides dependencies. Specifies which
dependencies to request from a parent RIB.
public Router build() {
// Builds and returns a router
}
41. Builder
Configuration
Instantiates Interactor, Router and View.
Provides dependencies. Specifies which
dependencies to request from a parent RIB.
@Override
protected ProductListView inflateView(LayoutInflater inflater, ViewGroup parentViewGroup) {
return (ProductListView) inflater.inflate(R.layout.root_rib, parentViewGroup, false);
}
public interface ParentComponent extends ProductDetailsBuilder.ParentComponent {
ProductNetworkApi productNetworkApi();
}
@dagger.Module
public abstract static class Module {
@BindsInstance
public abstract ProductDetailsInteractor.Listener productDetailsListener(
ProductListInteractor interactor
);
}
@dagger.Component(
modules = RootBuilder.Module.class,
dependencies = ParentComponent.class
)
interface Component extends InteractorBaseComponent<ProductListInteractor>,
ProductDetailsBuilder.ParentComponent {
// Dagger initialization
}
42. Builder
Configuration
Instantiates Interactor, Router and View.
Provides dependencies. Specifies which
dependencies to request from a parent RIB.
@Override
protected ProductListView inflateView(LayoutInflater inflater, ViewGroup parentViewGroup) {
return (ProductListView) inflater.inflate(R.layout.root_rib, parentViewGroup, false);
}
public interface ParentComponent extends ProductDetailsBuilder.ParentComponent {
ProductNetworkApi productNetworkApi();
}
@dagger.Module
public abstract static class Module {
@BindsInstance
public abstract ProductDetailsInteractor.Listener productDetailsListener(
ProductListInteractor interactor
);
}
@dagger.Component(
modules = RootBuilder.Module.class,
dependencies = ParentComponent.class
)
interface Component extends InteractorBaseComponent<ProductListInteractor>,
ProductDetailsBuilder.ParentComponent {
// Dagger initialization
}
43. Builder
Configuration
Instantiates Interactor, Router and View.
Provides dependencies. Specifies which
dependencies to request from a parent RIB.
@Override
protected ProductListView inflateView(LayoutInflater inflater, ViewGroup parentViewGroup) {
return (ProductListView) inflater.inflate(R.layout.root_rib, parentViewGroup, false);
}
public interface ParentComponent extends ProductDetailsBuilder.ParentComponent {
ProductNetworkApi productNetworkApi();
}
@dagger.Module
public abstract static class Module {
@BindsInstance
public abstract ProductDetailsInteractor.Listener productDetailsListener(
ProductListInteractor interactor
);
}
@dagger.Component(
modules = RootBuilder.Module.class,
dependencies = ParentComponent.class
)
interface Component extends InteractorBaseComponent<ProductListInteractor>,
ProductDetailsBuilder.ParentComponent {
// Dagger initialization
}
44. Builder
Configuration
Instantiates Interactor, Router and View.
Provides dependencies. Specifies which
dependencies to request from a parent RIB.
@Override
protected ProductListView inflateView(LayoutInflater inflater, ViewGroup parentViewGroup) {
return (ProductListView) inflater.inflate(R.layout.root_rib, parentViewGroup, false);
}
public interface ParentComponent extends ProductDetailsBuilder.ParentComponent {
ProductNetworkApi productNetworkApi();
}
@dagger.Module
public abstract static class Module {
@BindsInstance
public abstract ProductDetailsInteractor.Listener productDetailsListener(
ProductListInteractor interactor
);
}
@dagger.Component(
modules = RootBuilder.Module.class,
dependencies = ParentComponent.class
)
interface Component extends InteractorBaseComponent<ProductListInteractor>,
ProductDetailsBuilder.ParentComponent {
// Dagger initialization
}
45. Builder
Configuration
Instantiates Interactor, Router and View.
Provides dependencies. Specifies which
dependencies to request from a parent RIB.
@Override
protected ProductListView inflateView(LayoutInflater inflater, ViewGroup parentViewGroup) {
return (ProductListView) inflater.inflate(R.layout.root_rib, parentViewGroup, false);
}
public interface ParentComponent extends ProductDetailsBuilder.ParentComponent {
ProductNetworkApi productNetworkApi();
}
@dagger.Module
public abstract static class Module {
@BindsInstance
public abstract ProductDetailsInteractor.Listener productDetailsListener(
ProductListInteractor interactor
);
}
@dagger.Component(
modules = RootBuilder.Module.class,
dependencies = ParentComponent.class
)
interface Component extends InteractorBaseComponent<ProductListInteractor>,
ProductDetailsBuilder.ParentComponent {
// Dagger initialization
}