Это не очередная статья про Dagger и его возможности. Не будет ни слова про другие DI фреймворки.

Цель данной публикации — продемонстрировать подход к получению зависимостей во фрагментах, диалогах и активити.
В одном из проектов, перешедших по наследству, наткнулся на следующую реализацию диалога:
«А с каких это пор компонент должен заниматься поиском слушателя» — думал я в тот момент.
Давайте сделаем так, чтобы фрагмент не знал, кто конкретно реализует интерфейс слушателя.
Многие могут сразу предложить, например, такой вариант:
и код активити, в которую будем встраивать данный диалог:
У данного решения есть один существенный недостаток. При изменении конфигурации (к примеру, переворот экрана) получим следующую цепочку: диалог сохранит свое состояние в
Как правило интерфейс слушателя ре��лизуют такие компоненты Android как
Если наш «сыщик» не должен менять свое состояние в процессе взаимодействия с компонентом, то можно не переопределять метод
Давайте посмотрим на реализацию:
В таком случае код нашей активити примет вид:
Если нам понадобится реализовать показ диалога из фрагмента, то получим слудеющий код:
В итоге мы получаем, что вызывающий компонент сам помогает диалогу найти слушателя. При этом фрагмент понятия не имеет кто именно им (слушателем) является. К примеру, если по какой-либо причине нам не хочется обращаться к
С диалогами вроде разобрались. Едем дальше.
Часто приходится организовывать взаимодействие типа
В качестве примера, разберем случай с получением презентера во фрагменте. Думаю, каждый из нас сталкивался с подобным:
И все вроде хорошо, создание зависимостей мы спрятали, а вот подобное их получение теперь разбросано по всему проекту. На мой взгляд, как минимум тот, кто вызывает, должен либо предоставить эти зависимости, либо помочь их найти.
Опять применим наш прием с «сыщиком»:
Таким образом, мы сделали наш фрагмент более универсальным, соответственно, можем без проблем переносить его из проекта в проект не меняя код компонента без надобности.
Аналогичным способом можно организовать получение зависимостей в
Небольшой пример с реализацией подобного подхода лежит здесь.
Надеюсь, описанный подход пригодится Вам в реализации проектов и принесет пользу.
Спасибо за внимание!

Цель данной публикации — продемонстрировать подход к получению зависимостей во фрагментах, диалогах и активити.
Установка слушателя для диалога
В одном из проектов, перешедших по наследству, наткнулся на следующую реализацию диалога:
public class ExampleDialogFragment extends DialogFragment { private Listener listener; public interface Listener { void onMessageEntered(String msg); } @Override public void onAttach (Context context) { super.onAttach(context); if(context instanceOf Listener) { listener = (Listener) context; } else { listener = (Listener) getParentFragment(); } } }
«А с каких это пор компонент должен заниматься поиском слушателя» — думал я в тот момент.
Давайте сделаем так, чтобы фрагмент не знал, кто конкретно реализует интерфейс слушателя.
Многие могут сразу предложить, например, такой вариант:
public class ExampleDialogFragment extends DialogFragment { private Listener listener; public interface Listener { void onMessageEntered(String msg); } public static DialogFragment newInstance(Listener listener) { ExampleDialogFragment dialogFragment = new ExampleDialogFragment(); dialogFragment.listener = listener; return dialogFragment; } }
и код активити, в которую будем встраивать данный диалог:
public class ExampleActivity extends AppCompatActivity { void showDialog() { DialogFragment dialogFragment = ExampleDialogFragment .newInstance(new DialogFragment.Listener() { @Override void onMessageEntered(String msg) { // TODO } }); dialogFragment.show(getFragmentManager(), "dialog"); } }
У данного решения есть один существенный недостаток. При изменении конфигурации (к примеру, переворот экрана) получим следующую цепочку: диалог сохранит свое состояние в
Bundle и будет уничтожен -> активити будет удалена -> новый экземпляр активити будет создан -> диалог будет создан заново на основе сохраненного в Bundle состояния. В итоге, мы потеряем ссылку на слушателя в диалоге, так как она явно не была сохранена и восстановлена. Мы, конечно, можем вручную вызвать setListener() в одном из колбэков жизненного цикла активити, но есть и другой вариант. Так как анонимный класс мы не можем сохранить в Bundle, равно как и экземпляры обычных классов, нам нужно нужно соблюсти следующие условия:- Слушатель должен реализовать интерфейс
SerializableилиParcelable - Передать слушателя через аргументы фрагмета при его создании
setArguments(Bundle args) - Слушатель должен быть сохранен в методе
onSaveInstanceState(Bundle outState) - Слушатель должен быть восстановлен в методе
Dialog onCreateDialog(Bundle savedInstanceState)
Как правило интерфейс слушателя ре��лизуют такие компоненты Android как
Activity или Fragment. Подобные комоненты не предназначены для сохранения в Bundle, поэтому нам надо найти другой подход к решению. Давайте попробуем передавать не самого слушателя, а «сыщика»(Provider), который способен его найти. В этом случае, нам никто не помешает сделать его сериализуемым и сохранять в Bundle. Если наш «сыщик» не должен менять свое состояние в процессе взаимодействия с компонентом, то можно не переопределять метод
onSaveInstanceState(Bundle outState) и при вызове метода Dialog onCreateDialog(Bundle savedInstanceState) восстанавливать зависимость из аргументов.Давайте посмотрим на реализацию:
public class ExampleDialogFragment extends DialogFragment { private static final String LISTENER_PROVIDER = "listener_provider"; private Listener listener; public interface ListenerProvider extends Serializable { Listener from(DialogFragment dialogFragment); } public interface Listener { void onMessageEntered(String msg); } public static DialogFragment newInstance(ListenerProvider provider) { ExampleDialogFragment dialogFragment = new ExampleDialogFragment(); Bundle args = new Bundle(); args.putSerializable(LISTENER_PROVIDER, provider); dialogFragment.setArguments(args); return dialogFragment; } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { Bundle args = getArguments(); if(args == null || !args.containsKey(LISTENER_PROVIDER)) { throw new IllegalStateException("Listener provider is missing"); } ListenerProvider listenerProvider = (ListenerProvider) args.getSerializable(LISTENER_PROVIDER); Listener listener = listenerProvider.from(this); ... } }
В таком случае код нашей активити примет вид:
public class ExampleActivity extends AppCompatActivity implements ExampleDialogFragment.Listener { @Override public void onMessageEntered(String msg) { // TODO } void showDialog() { DialogFragment dialogFragment = ExampleDialogFragment .newInstance(new ListenerProvider()); dialogFragment.show(getFragmentManager(), "dialog"); } private static class ListenerProvider implements ExampleDialogFragment.ListenerProvider { private static final long serialVersionUID = -5986444973089471288L; @Override public ExampleDialogFragment.Listener from(DialogFragment dialogFragment) { return (ExampleDialogFragment.Listener) dialogFragment.getActivity(); } } }
Если нам понадобится реализовать показ диалога из фрагмента, то получим слудеющий код:
public class ExampleFragment extends Fragment implements ExampleDialogFragment.Listener { @Override public void onMessageEntered(String msg) { // TODO } void showDialog() { DialogFragment dialogFragment = ExampleDialogFragment.newInstance(new ListenerProvider()); dialogFragment.show(getFragmentManager(), "dialog"); } private static class ListenerProvider implements ExampleDialogFragment.ListenerProvider { private static final long serialVersionUID = -5986444973089471288L; @Override public ExampleDialogFragment.Listener from(DialogFragment dialogFragment) { return (ExampleDialogFragment.Listener) dialogFragment.getParentFragment(); } } }
В итоге мы получаем, что вызывающий компонент сам помогает диалогу найти слушателя. При этом фрагмент понятия не имеет кто именно им (слушателем) является. К примеру, если по какой-либо причине нам не хочется обращаться к
Activity напрямую, то никто нам не мешает просто кинуть событие, а затем в нужном месте поймать его и обработать (код диалога даже не потребуется менять):public class ExampleFragment extends Fragment { void onMessageEvent(Message message) { // TODO } void showDialog() { DialogFragment dialogFragment = ExampleDialogFragment.newInstance(new ListenerProvider()); dialogFragment.show(getFragmentManager(), "dialog"); } private static class Message { public final String content; private Message(String content) { this.content = content; } } private static class ListenerProvider implements ExampleDialogFragment.ListenerProvider { private static final long serialVersionUID = -5986444973089471288L; @Override public ExampleDialogFragment.Listener from(DialogFragment dialogFragment) { return new ExampleDialogFragment.Listener() { @Override public void onMessageEntered(String msg) { EventBus.getDefault().post(new Message(msg)); } }; } } }
С диалогами вроде разобрались. Едем дальше.
Поиск зависимостей во фрагментах
Часто приходится организовывать взаимодействие типа
Activity <-> Fragment или Fragment <-> Fragment в рамках одной активити. Общий принцип остается тот же, что был описан выше: посредством интерфейса (к примеру, Listener) и «сыщика» организуется общение между компонентами. В рамках данной статьи будем рассматривать одностороннее взаимодействие.В качестве примера, разберем случай с получением презентера во фрагменте. Думаю, каждый из нас сталкивался с подобным:
public interface Presenter { ... } public class ExampleFragment extends Fragment { private Presenter presenter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); presenter = App.get().getExampleFragmentComponent().getPresenter(); } }
И все вроде хорошо, создание зависимостей мы спрятали, а вот подобное их получение теперь разбросано по всему проекту. На мой взгляд, как минимум тот, кто вызывает, должен либо предоставить эти зависимости, либо помочь их найти.
Опять применим наш прием с «сыщиком»:
public class ExampleFragment extends Fragment { private static final String DI_PROVIDER = "di_provider"; private Presenter presenter; public interface DependencyProvider implements Serializable { Presenter getPresenterOf(Fragment fragment); } public static Fragment newInstance(DependencyProvider dependencyProvider) { Fragment fragment = new ExampleFragment(); Bundle args = new Bundle(); args.putSerializable(DI_PROVIDER, dependencyProvider); fragment.setArguments(args); return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle args = getArguments(); if(args == null || !args.containsKey(DI_PROVIDER)) { throw new IllegalStateException("DI provider is missing"); } DependencyProvider diProvider = (DependencyProvider) args.getSerializable(DI_PROVIDER); presenter = diProvider.getPresenterOf(this); } } public class ExampleActivity extends AppCompatActivity { void showFragment() { FragmentTransaction ft = getFragmentManager().beginTransaction(); Fragment fragment = ExampleFragment .newInstance(new DiProvider()); ft.add(R.id.container, fragment); ft.commit(); } private static class DiProvider implements ExampleFragment.DependencyProvider { private static final long serialVersionUID = -5986444973089471288L; @Override public Presenter get(Fragment fragment) { return App.get().getExampleFragmentComponent().getPresenter(); } } }
Таким образом, мы сделали наш фрагмент более универсальным, соответственно, можем без проблем переносить его из проекта в проект не меняя код компонента без надобности.
Аналогичным способом можно организовать получение зависимостей в
Activity. Небольшой пример с реализацией подобного подхода лежит здесь.
Надеюсь, описанный подход пригодится Вам в реализации проектов и принесет пользу.
Спасибо за внимание!