Android MVP: безопасное использование Контекст в Presenter

В моем приложении я работаю с ContentProvider и использую LoaderManager.LoaderCallbacks<Cursor>.

Фрагмент (просмотр)

public class ArticleCatalogFragment extends BaseFragment
        implements ArticleCatalogPresenter.View,
        LoaderManager.LoaderCallbacks<Cursor> {

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return onCreateArticleCatalogLoader(args);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {        
         data.registerContentObserver(new LoaderContentObserver(new Handler(), loader));
         updateUI(data);        
    }   

    private Loader onCreateArticleCatalogLoader(Bundle args) {
            int categoryId = args.getInt(CATEGORY_ID);
            Loader loader = new ArticleCatalogLoader(this.getActivity(), categoryId);            
            return loader;
    }

}

С точки зрения MVP мне нужно:

Presenter

public class ArticleCatalogPresenter extends BasePresenter
        implements LoaderManager.LoaderCallbacks<Cursor> {

    View view;

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return onCreateArticleCatalogLoader(args);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {        
         data.registerContentObserver(new LoaderContentObserver(new Handler(), loader));
         view.updateUI(data);        
    }               

    private Loader onCreateArticleCatalogLoader(Bundle args) {    
            int categoryId = args.getInt(CATEGORY_ID);
            Loader loader = new ArticleCatalogLoader(context, categoryId); // need Context
            return loader;
    }


    interface View {
        updateUI(Cursor data)
    }

}

Итак, мне нужен контекст в Presenter.

Есть некоторые нюансы:

  • Ведущий знает о Контексте - это плохо, Ведущий не должен узнайте об Android.

  • Наличие контекста в Presenter может привести к утечке памяти.

Теперь я беспокоюсь о том, как избежать таких проблем, как утечка памяти и как лучше передать Context в Presenter, использовать контекст приложения или активность/фрагмент?

Ответы

Ответ 1

Добавление контекста к Presenter не является хорошим, поскольку ведущий отвечает за бизнес-логику. Чтобы иметь дело с контекстом, вам необходимо иметь фрагмент/действия использовать обратные вызовы с помощью интерфейсов, которые укажут, какие действия необходимо выполнять с помощью действия/фрагмента при работе с представлениями. Фрагмент/Деятельность несет ответственность за предоставление контекста.

Пример:

interface BaseContract {
        interface BaseView {
            //Methods for View
            void onDoSomething();
        }

        interface BasePresenter {
            void doSomething();

        }
    }

    class BaseMainPresenter implements BaseContract.BasePresenter {
        BaseContract.BaseView view;

        BaseMainPresenter(BaseContract.BaseView view) {
            this.view = view;
        }

        @Override
        public void doSomething() {
            if (view != null)
                view.onDoSomething();
        }
    }

    class DemoClass implements BaseContract.BaseView {

        //Create object of Presenter 

        /****
         * Example :
         * BaseMainPresenter baseMainPresenter = new BaseMainPresenter(this);
         */
        @Override
        public void onDoSomething() {
            //Deal with Context here.
        }
    }

Ответ 2

Просто не регистрируйте своего докладчика как целевую цель обратного вызова Android (например, BroadcastReceiver, LoaderManager.LoaderCallbacks и т.д.). Обработайте методы обратного вызова в вашем представлении (фрагмент или действие) и передайте все связанные данные ведущему.

Если вам нужно Context для создания объекта, пусть ваш вид создаст этот объект (так как он имеет ссылку на Context). В вашем случае вызов

Loader loader = new ArticleCatalogLoader(context, categoryId)

должен быть реорганизован на

view.createLoaderForCategory(categoryId)

Ответ 3

Код, подобный этому

Loader loader = new ArticleCatalogLoader(context, categoryId);

приводит к непроверяемому коду. Вам следует избегать создания "бизнес-объектов" в вашем коде, и пусть кто-то еще сделает это за вас (любая инфраструктура DI, такая как Dagger 2 будет лучший вариант, чем обработка его самостоятельно)

Сказав это, ваша проблема - это то, что DI решил давно. Вам нужен новый новый экземпляр любого объекта? Используйте Provider

A Provider - объект, который "предоставляет" экземпляры объектов. Поэтому вместо

Loader loader = new ArticleCatalogLoader(context, categoryId);

у вас будет

Loader loader = loaderProvider.get(categoryId);

Итак, единственное, что вам нужно, это что-то вроде этого:

public class ArticleCatalogPresenter ... {
    ...
    private final Provider<Loader> loaderProvider;

    public ArticleCatalogPresenter(Provider<Loader> loaderProvider, ...) {
        this.loaderProvider = loaderProvider;
        ...
    }

    private Loader onCreateArticleCatalogLoader(Bundle args) {    
        int categoryId = args.getInt(CATEGORY_ID);
        Loader loader = loaderProvider.get(categoryId); // no context needed anymore!
        return loader;
    }

}

Ответ 4

public class ArticleCatalogPresenter extends BasePresenter
        implements LoaderManager.LoaderCallbacks<Cursor> {

    View view;             
    ...
    private Loader onCreateArticleCatalogLoader(Bundle args) {    
            int categoryId = args.getInt(CATEGORY_ID);
            Loader loader = new ArticleCatalogLoader(context, categoryId); // need Context
            return loader;
    }
}

Итак, вы хотите, чтобы context в вашем докладчике построил новый экземпляр ArticleCatalogLoader. Правильно?

Если это так, передайте экземпляр Presenter через конструктор. Поэтому в своем контейнере Activity или DI, когда вы хотите построить объект Presenter, сделайте что-то вроде:

ArticleCatalogPresenter articleCatalogPresenter=new ArticleCatalogPresenter(articleCatalogView,new ArticleCatalogLoader(context,categoryId));

Таким образом, ваш докладчик не будет зависеть от context и будет полностью тестируемым.

Что касается вашей озабоченности по поводу утечки памяти, вы можете легко избежать этого, прослушав onStop() в вашем View, а затем вызвав соответствующий метод в Presenter, чтобы отменить любой сетевой запрос или context зависимую задачу.

Я написал библиотеку MVP, которая очень помогает сэкономить количество шаблонов, необходимых для MVP, а также предотвратить утечки памяти.