Android MVP — An End to if (view != null)
⚠️ Warning ⚠️
Since writing this article, I’ve realised there is a fatal flaw with this approach.
In short, this approach doesn’t handle the use cases where your View
methods might have a non-void
return type.
Whenever the proxy encounters methods which are expected to return something, the best it can do is return null
, meaning the proxy might still cause a NullPointerException
from the next line of code if it doesn’t gracefully handle receiving null
values, which was the very scenario it was designed to protect against.
I’ll leave the post up in case it helps anyone, but really, I wouldn’t recommend this approach any longer; it works well for methods with no return type but falls apart proxying methods which do return something. In my opinion, the cognitive overhead of trying to remember when it is safe to use the proxy and when not is too high a price to pay.
A Presenter, in an Android app architected using Model View Presenter (MVP), might see its reference to the View become null at any time. This article presents a solution using a dynamic java Proxy to avoid having to use ugly null checks throughout the Presenter.
Where Did the View Go?
If the View is an Android Activity, then it will be destroyed and recreated for a configuration change, such as what happens when the user rotates their device. When the View is being destroyed, the Presenter is typically notified of this so that it can avoid operating on an invalid View.
public interface BaseMvpPresenter<T> {
void setView(T view);
void viewDetached(boolean changingConfigurations);
}
Your Presenters might follow the typical pattern above, such that it makes sense for each of your Presenters to inherit from this Base Presenter. During the viewDetached() method, the Presenter’s reference to the View is typically set to null.
Checking For Nulls
Unless you manage your threading carefully, then, there is the opportunity for your Presenter to execute code which would normally invoke one or more methods on your View when the View is null. The reference to the View is typically held as a WeakReference to avoid memory leaking your Activity.
Here is an example of a typical Presenter for me:
private WeakReference<SampleMvp.View> viewReference;@Nullable
private SampleMvp.View getView() {
if (viewReference == null) {
return null;
}
return viewReference.get();
}
@Override
public void viewDetached(boolean changingConfigurations) {
viewReference = null;
}
Whenever you want to invoke a method on the View you have to check the nullability of it first.
View view = getView();
if(view == null) {
return;
}view.doStuff();
The larger your Presenter grows, the more of these null checks that will litter your code.
Null Object Design Pattern
One such alternative to the null checks is to switch to using a null object. The purpose of a null object is literally to do nothing when called. It is a no-op. What it does do, however, is implement your View interface meaning that the Presenter doesn’t have to do any checks before invoking methods on its View reference.
If the View is currently attached, the Presenter will invoke the real View.
If the View is not currently attached, the Presenter will instead invoke the no-op version, which will do nothing. When you look at the typical handling of the code when (view == null), doing nothing is almost always the desired choice anyway.
The problem with this is that you have to then generate these empty implementations for all of your View interfaces. You have to keep these up to date. You have to refactor these as you refactor the real Views to ensure it still implements the View interface correctly. In other words, it is a great solution but is a pain to maintain.
The Solution — Java Proxy
The potential solution is to use a Proxy class. This proxy is dynamic, meaning that after writing it once, you don’t have any additional classes to write and maintain. No matter how many different View interfaces you define, you can use the same no-op proxy for them all.
The purpose of the Proxy is to either call its underlying reference to the View if attached, or to do nothing if the View is not attached.
Defining the No-Op Proxy
Defining the proxy is as simple as copying the class below into your project. You might want to consider adding some logging to help debug the scenario when the proxy is choosing to do nothing, but other than that, you won’t have to make any changes.
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;public class NoOpProxy implements InvocationHandler { private WeakReference<Object> view; public NoOpProxy(Object view) {
this.view = new WeakReference<>(view);
} @Override
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
if(view == null || view.get() == null) {
return null;
}
return method.invoke(view.get(), args);
}
}
As you can see, it is using reflection to achieve this. I know there are some strong opinions on reflection; I would love to hear some of them in regards to this problem in the comments.
Using the No-Op Proxy
In order to use the proxy, your existing Presenter needs to be tweaked. Where an existing setView method on a Presenter might look like this
private WeakReference<View> viewReference;public void setView(View view) {
this.viewReference = new WeakReference<>(view);
}
It would be changed to look like this
import java.lang.reflect.Proxy;...private View view;public void setView(View view) {
this.view = (View) Proxy.newProxyInstance(getClass().getClassLoader(),
new Class[]{View.class}, new NoOpProxy(view));
}
Does that look pretty? Absolutely not. But you can find a way to abstract some of the nastier parts away from sight.
The important parts of this is that you are creating a new instance of your proxy. To do so, you need to provide access to the class loader; you need to specify the type of class being proxied (in this case, our View.class); finally you need to pass the reference to the View to the new proxy.
We no longer need to define the variable as a WeakReference as the proxy does that for us. Instead, we simply define the type as a View. This is part of the proxy magic; it is treated as being the exact same type as the class it proxies. The code which interacts with the proxy doesn’t even know that it is a proxy.
Advantages
- No more checking in a Presenter if a View reference is null. If it isn’t attached, you can safely operate on it knowing it will not do anything; crucially, it will not throw a NullPointerException.
- No need to manually define implementations for all of your View interfaces
- No need to maintain all of the manual implementations
Disadvantages
- Requires use of reflection
- [UPDATE] Does not have good support for proxying methods with a non-void return type; see the added warning at the beginning of the post for a description of why.
Conclusion
Love it? Hate it? Think I’m mad? Always keen to know. If Android is your thing, you should definitely be following me on Twitter: @trionkidnapper.
If you learned anything new from this, anything at all, consider hitting the “recommend” button; it goes a long way to ensuring this article helps others too.