Android-CleanArchitecture
Android-CleanArchitecture copied to clipboard
Limiting use of Android specific classes in presenters
What do you think about an idea to remove access to Context object in presenters to make it platform-independent? It could be achieved by making, for example, Navigator not Singleton but with PerView scope and add Host activity as a construtor parameter? Is it worth the sweat?
@romansbobans I think, the presenter should't have any platform dependencies, only java and then you can test presenter using only JUnit.
For navigation good solution is to use Router pattern. Like a View interface you have Router interface which define navigation methods. For example:
public interface IRouter {
}
public interface CategoryRouter extends IRouter {
void openEditCategoryScreen(CategoryViewModel category);
}
public interface Router<R extends IRouter> {
void detachRouter();
void attachRouter(R router);
boolean hasRouter();
R getRouter();
}
public abstract class BaseRouterPresenter<V extends MvpView, R extends IRouter>
extends BasePresenter<V> implements Router<R> {
private PresenterRouterDelegate<R> routerDelegate;
public BaseRouterPresenter() {
super();
routerDelegate = new PresenterRouterDelegate<>();
}
@Override public void detachRouter() {
routerDelegate.detachRouter();
}
@Override public void attachRouter(R router) {
routerDelegate.attachRouter(router);
}
@Override public boolean hasRouter() {
return routerDelegate.hasRouter();
}
@Override public R getRouter() {
return routerDelegate.getRouter();
}
}
public class PresenterRouterDelegate<R extends IRouter> implements Router<R> {
private WeakReference<R> routerRef;
/**
* Called to surrender control of taken router.
* <p>
* It is expected that this method will be called with the same argument as {@link
* #attachRouter}.
* Mismatched routers
* are ignored. This is to provide protection in the not uncommon case that {@code detachRouter}
* and {@code attachRouter}
* are called out of order.
* <p>
*/
@Override public void detachRouter() {
releaseRouter();
}
/**
* Checks if a router is attached to this presenter. You should always call this method before
* calling {@link
* #getRouter} to get the router instance.
*
* @return {@code true} if presenter has attached router
*/
@Override public boolean hasRouter() {
return routerRef != null && routerRef.get() != null;
}
/**
* Called to give this presenter control of a router.
*
* @param router router that will be returned from {@link #getRouter()}.
*/
@Override public void attachRouter(R router) {
Preconditions.checkNotNull(router, "router");
final R currentRouter = getRouter();
if (currentRouter != router) {
if (currentRouter != null) {
detachRouter();
}
assignRouter(router);
}
}
/**
* Returns the router managed by this presenter, or {@code null} if {@link #attachRouter} has
* never been called, or
* after {@link #detachRouter}.
* <p>
* You should always call {@link #hasRouter} to check if the router is taken to avoid
* NullPointerExceptions.
*
* @return {@code null}, if router is not taken, otherwise the concrete router instance.
*/
@Override public R getRouter() {
return routerRef == null ? null : routerRef.get();
}
private void assignRouter(R router) {
routerRef = new WeakReference<>(router);
}
private void releaseRouter() {
if (routerRef != null) {
routerRef.clear();
routerRef = null;
}
}
}
In you presenter
public void onListItemClick(CategoryViewModel category) {
if (hasRouter()) {
getRouter().openEditCategoryScreen(category);
}
}
public class CategoriesActivity extends Activity implements CategoryRouter {
@Inject protected Navigator navigator;
@Override public void openEditCategoryScreen(CategoryViewModel category) {
navigator.navigateToAddCategoryScreen(this, category);
}
Why this pattern is good?
-
You code become more readable. Imagine the situation when you open Activity or Fragment and you want to understand navigation flow on this screen. For this you need to find code responsible for navigation. It will take some time, especially when you new on project. With router you just need to open interface and see where you can go from this screen.
-
I think it's bad way to use Navigator in presenter, because Navigator containe all navigation logic. And it's allow us to invoke any navigation method in presenter. It's bad, we need borders and router provide them.
-
Easy to test, just mock interface.
Sorry for my bad english :))
i totally agree! for that reason my presenters, models and view interfaces are in a separated java module.