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, а также предотвратить утечки памяти.