/* * Copyright 2015 Hannes Dorfmann. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.hannesdorfmann.mosby3.mvp; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import java.lang.ref.WeakReference; /** * A base implementation of a {@link MvpPresenter} that uses a <b>WeakReference</b> for referring * to the attached view. * <p> * You should always check {@link #isViewAttached()} to check if the view is attached to this * presenter before calling {@link #getView()} to access the view. * </p> * * <p> * <b>Why is this class using internally a WeakReference for referring to the attached view and why * do I have to use {@link #isViewAttached()} check?</b> * </p> * <p> * In a "perfect world" you wouldn't need this check nor a WeakReference. In Mosby <b>all * interaction * from Presenter to View must be executed on android's main UI thread</b>. Since screen * orientation changes are also executed on the main UI thread it is not possible to run into the * scenario where view is detached while presenter still wants to update the UI, right? However, we * are not living in a perfect world. Let's say you use an AsyncTask to make an http request and * the Presenter gets the result back and updates the View. If you forget to cancel this AsyncTask * after the View has been destroyed (i.e. android back button pressed, {@link * MvpPresenter#detachView(boolean)} will be called) then you can run into the scenario that the * View is detached from Presenter and then the Presenter gets the result back from AsyncTask and * wants to update the View which is null (because detached). So the `isViewAttached()` check is * basically some kind of safety net. In a perfect implementation you wouldn't need the {@link * #isViewAttached()} check. * </p> * <p> * Furthermore, in a perfect world you wouldn't need a WeakReference for referring to the View. In * Mosby you can create your own MvpDelegate to change Mosby's default implementation and * behaviour. * We have decided to use a WeakReference to avoid memory leaks if you use a custom MvpDelegate * that is not implementing the contract of attaching and detaching View from Presenter * properly (i.e. don't detach view in Activity.onDestroy() ). * </p> * <p> * So using a WeakReference and adding the {@link #isViewAttached()} check are basically just some * kind of safety net and not needed in a "perfect world". Please note that if you are sure that * you are coding in such a "perfect world" then you can also think about implementing your own * Presenter without WeakReference and isViewAttached(). Note also that * {@link MvpPresenter} is an interface. Hence implementing you own Presenter is easy. * </p> * * @param <V> type of the {@link MvpView} * @author Hannes Dorfmann * @since 1.0.0 */ public class MvpBasePresenter<V extends MvpView> implements MvpPresenter<V> { private WeakReference<V> viewRef; @UiThread @Override public void attachView(V view) { viewRef = new WeakReference<V>(view); } /** * Get the attached view. You should always call {@link #isViewAttached()} to check if the view * is * attached to avoid NullPointerExceptions. * * @return <code>null</code>, if view is not attached, otherwise the concrete view instance */ @UiThread public V getView() { return viewRef == null ? null : viewRef.get(); } /** * Checks if a view is attached to this presenter. You should always call this method before * calling {@link #getView()} to get the view instance. */ @UiThread public boolean isViewAttached() { return viewRef != null && viewRef.get() != null; } @UiThread @Override public void detachView(boolean retainInstance) { if (viewRef != null) { viewRef.clear(); viewRef = null; } } }