Обнаружение зависимостей Android компонентов

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

scan

Цель данной публикации — продемонстрировать подход к получению зависимостей во фрагментах, диалогах и активити.

Установка слушателя для диалога


В одном из проектов, перешедших по наследству, наткнулся на следующую реализацию диалога:

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, равно как и экземпляры обычных классов, нам нужно нужно соблюсти следующие условия:

  1. Слушатель должен реализовать интерфейс Serializable или Parcelable
  2. Передать слушателя через аргументы фрагмета при его создании setArguments(Bundle args)
  3. Слушатель должен быть сохранен в методе onSaveInstanceState(Bundle outState)
  4. Слушатель должен быть восстановлен в методе 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.

Небольшой пример с реализацией подобного подхода лежит здесь.

Надеюсь, описанный подход пригодится Вам в реализации проектов и принесет пользу.
Спасибо за внимание!
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 36
  • 0
    Я правильно понимаю, что вверху — «плохой» пример из 10 строчек реализации диалога, а в самом низу — то же самое, но на нескольких страницах, с медленной сериализацией и самописным DI, который является «хорошим» кодом? Как думаете, что будет проще перенести в другой проект — «плохой» или «хороший» пример?
    • +1
      Давайте по порядку. Начнем с сериализации: согласен, что при таком подходе появляются накладные расходы на сериализацию, но, на мой взгляд, сохранение и восстановление одного объекта (не обладающего внутренним состоянием) не приведет к падению производительности. Можно вместо Serializable использовать Pacrelable, если Вы хотите более быстрой сериализации. К тому же, если Android SDK предоставляет возможность сохранять в Bundle сериализуемые объекты, то почему бы нам этим не воспользоваться, а не прикрываться фразами типа «медленная сериализация».

      Теперь по поводу «самописного DI». Если Вы внимательно читали статью, то я нигде не призывал отказываться от DI фреймворков, а наоборот предлагаю использовать подход, описанный в публикации, совместно c ними. Давайте возьмем для примера Dagger до появления функционала AndroidInjection, приходилось писать подобное (код с официального сайта):
      public class FrombulationActivity extends Activity {
        @Inject Frombulator frombulator;
      
        @Override
        public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          // DO THIS FIRST. Otherwise frombulator might be null!
          ((SomeApplicationBaseType) getContext().getApplicationContext())
              .getApplicationComponent()
              .newActivityComponentBuilder()
              .activity(this)
              .build()
              .inject(this);
          // ... now you can write the exciting code
        }
      }
      

      Я лишь предлагал спрятать создание активити компонента. Примерно так.
      interface Provider {
          Builder getBuilder(FrombulationActivity activity);
      }
      
      class ProviderImpl implements Provider {
          @Override
          Builder getActivityComponent(FrombulationActivity activity) {
              return ((SomeApplicationBaseType) getContext().getApplicationContext())
                  .getApplicationComponent()
                  .newActivityComponentBuilder()
                  .activity(activity)
                  .build();
          }
      }
      
      public class FrombulationActivity extends Activity {
        @Inject Frombulator frombulator;
      
        @Override
        public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          // DO THIS FIRST. Otherwise frombulator might be null!
          Provider provider = getIntent().getExtras().getSerializable(PROVIDER);
          provider.getActivityComponent(this)
              .inject(this);
          // ... now you can write the exciting code
        }
      }
      

      При таком подходе активити не знает откуда берется компонент. Если активити лежит в общей библиотеке, которую использую несколько приложений, то такой способ внедрения зависимостей очень удобен. А также при тестировании активити.
      • 0
        Просто померяйте любым методом сколько времени занимает сериализация Serializable. Он на порядки медленнее всего остального. Мой коммент был не об этом. Короткий и простой код гораздо более переносим чем «правильный» но огромный. Да, диалогфрагмент провоцирует на написание плохого кода, однако в самом начале приведена практически каноническая реализация. Никто по своей воле не будет делать сериализацию, инжекты и кучу бойлерплейт кода вокруг уже необходимого кода для решения задачи в стиле «показать попап да/нет».
        • +2
          «Каноническое» означает основанное на вере, что так должно быть. Но разве разработка не подразумевает поиск новых подходов?! Я лишь поделился подходом, а применять его или нет — на усмотрение каждого конкретного разработчика. В любом случае, спасибо за конструктивную критику.
    • –5
      Используйте EventBus (шину событий) и вы забудете о слушателях, фрагментах, активити и их жизненных циклах.
      • +1
        ИМХО, EventBus — не панацея. Я хотел сделать компонент как можно более независимым от разных библиотек, чтобы его можно было переиспользовать в других проектах (где этих библиотек может не быть и добавление их не одобрят).
        К тому же, хоть EventBus добавляет гибкости во взаимодействии между компонентами, он также накладывает больше ответственности на разработчиков — чтобы приложение не превратилось в запутанный клубок из событий. Лично я предпочитаю не злоупотреблять рассылкой событий и использовать EventBus только при острой необходимости.
        • –1
          Событие — по своему определению независимо от всех библиотек/компонентов/пр. Полностью согласен, что EventBus накладывает ответственность на разработчика. Так наша задача и состоит в том, что бы учиться правильно проектировать. И где нужна асинхронность + отвязка от ui/гибкая(меняющееся) бизнес логика/разделение приложения на слои — то лучше использовать события, а где можно использовать последовательности при неизменной бизнес логике — то лучший инструмент Rx и подобные инструменты. Поэтому выбранный Вами пример решается ну очень просто — генерацией события с данными — и всего одна строчка кода.
        • +2

          Это худшее, что можно было посоветовать! Евентбас неявно связывает все компоненты между собой. Все компоненты в приложении могут слушать всех. А дебажить это дело…
          Закопайте его и не вспоминайте

              • 0
                Если не представляете, что такое сервис-ориентированная архитектура, то события Вам никогда не понадобятся. Я понимаю, что для приложения типа галерея — оно может и не нужно, но есть класс приложений, в которой использование сервис-ориентированной архитектуры важно.
                • +1

                  Я отлично представляю, где может понадобится рассылка сообщений компонентами. Но это не для общения между диалогом и активити. А именно в данном контексте я и не рекомендую слушать ваш совет.
                  Из-за использования инструментов в неправильных местах потом и получаются монстры, которых невозможно поддерживать и отлаживать.

                  • 0
                    Диалог — это независимая визуальная сущность, создание единого интерфейса для взаимодействия с ним, независимого от порождающей и принимающей стороны — это задача, которая для реализации может использовать события. Т.е. создав сервис, который может порождать Диалоги и установив единый интерфейс получения результатов (например через событие) — вы просто забудете, что такое диалоги и их различные реализации (DialogFragment/AlertDialog). Умение проектировать независимыми сервисами — тоже умение. И единый транспорт (шина событий) в данной архитектуре — важнейшая часть.
                    • 0
                      Например есть задача вывода сообщений. Реализуйте все сервисом. И если завтра вас попросят выводить все через Toast, а послезавтра — через SnackBar — то вы просто меняете настройку прямо в приложении. Добавьте серьезность в сообщения. Вы поменяете только сервис, не трогая интерфейс взаимодействия. Интерфейс взаимодействия с сервисом един — через одно единственное событие. Тоже самое с диалогами — одно входящее событие и одно исходящее событие.
                      • 0

                        Минус данного подхода, что вы отправляете сообщение в "комос", а кто его получит — неизвестно. Это влечет за собой проблемы отладки и связывает слушателей и источники событий. А кто эти источники и кто у них слушатели можно определить только глобальным поиском по проекту.
                        Это все равно что во всех интерфейсах передавать объекты типа Object и по мере их использования кастовать к нужным типам. А после этого заменить все интерфейсы на один:
                        interface Listener { fun doAction(obj: Object) }

                        • 0
                          Я специально обратил внимание на Серьезность сообщения. В общем случае она не нужна — и в случае отсутствия слушателей оно теряется — закрыли фрагмент и нам не важно, что сообщение «Привет Вася» пропало. Но если приложение серьезно — и нам важен результат, то сервис обязан иметь зарегистрированных подписчиков(слушателей). Не важно какого типа они будут — важно, что они имеют нужный нам интерфейс. Нет живых подписчиков — бога ради используйте сервис по типу e-mail(т.е. появился слушатель — отправили ему мыло). Самое главное — нам не нужно никуда передавать ссылки на что-то — мы передаем только данные. А сервис сам решает — как поступить в каждом случае — отбросить событие/передать/переслать(положить в очередь/кэш). Я понимаю — а что делать, если зарегистрированного несколько слушателей? Добавьте текущего. Т.е. как с квартирой — хотите — сами ищите ее, а хотите — просто пишите объявление и ждете. Каждый выбирает свое.
                          • 0
                            Стандартный пример при плохой связи — получили отлуп и нужно вывести сообщение об этом. Но пользователь уже давно ушел из данного фрагмента и он уже давно уничтожен. В случае сервиса у вас есть список живых фрагментов/активити и вы выбираете один — текущий, тот который в состоянии на экране и спокойно передаете данные ему. Нет живых — приложение в фоне — положите в очередь в сервис мыла.
                            • 0

                              Вы очень много пишите и уходите от темы в аналогии. Евентбас — зло для андроид приложений, так как он совершенно неконтролируем. Если проект маленький и его пилит один человек, который умудряется все держать в голове, то еще прокатит. Для серьезной разработки — это недопустимо.


                              Я думаю стоит завершить этот спор. Есди я вас не переубедил — это ваше дело. Но воздержитесь рекомендовать Евентбас кому-то еще.

                              • 0
                                Перед тем как давать рекомендации — просто посмотрите процент использования EventBus. Я думаю также будет Вам полезно почитать о событийно-ориентированном программировании/clean architecture и прочих аналогичных штуках. И как их используют в крупных компаниях.
                                habrahabr.ru/company/yamoney/blog/334500
                                habrahabr.ru/post/128772
                                • 0
                                  Использование в крупных компаниях — не показатель. Я в исходниках андроида порой такое нахожу…

                                  Дабы не быть голословным, достаточно безобидный пример:

                                  Есть LocalSocket. В доках ничего не сказано про параметр timeout в методе connect(). В исходниках сказано, что он игнорится. Угадаете, что происходит на самом деле?

                                  Что происходит на самом деле
                                  Он кидает исключение!


                                  Так что говнокодеры и тут и там :)
                                • 0
                                  А если хотите вообще копнуть глубоко — почитайте про MIMD системы (системы потока данных)
                                  • 0
                                    И наверно сидите за компом, в котором нет контроллера прерываний или его разрабатывал один человек :)
                                  • 0

                                    И как рассылка глобального сообщения по всему приложению связана с темой статьи — получения результата с диалога/соседнего фрагмента? Какая-то демагогия получается.
                                    А по вашей теме — как я понимаю, вы хотите какой-то глобальный AlertDialog показывать в любом месте приложения при, скажем, получении какого-то сообщения из некого WebSocket-а. Не сказал бы, что эта идея мне нравится, но допустим, надо. Вы хотите куда-то EventBus-ом слать сообщения. Только непонятно, куда и кто должен на это реагировать. А вот как это сделать нормально (насколько слово «нормально» вообще применимо при подобной задаче. Берёте, регистрируете ActivityLifecycleEvents И при смене Activity, видимой для пользователя, запоминаете ее, а при необходимости берёте и показываете на ней, что хотите. Хотите — берёте ее контекст и Alertdialog/toast показываете, а может, вообще в decorview что-то вставляйте. А нет активных — пихаете сообщение куда-то в очередь или что у вас там. И никаких безликих event-ов в пустоту.

                                    • 0
                                      Если не использовали никогда EventBus зачем такие комментарии? Например завтра вам скажут пересылать все на сервер или запихивать все в БД, то каким способом вам поможет ActivityLifecycleEvents. В статических системах, в которых расписано все и на весь цикл разработки и жизни приложения не место событийно-ориентированным системам. Но в динамических системах, в которых вы не знаете, что от вас могут потребовать завтра (например — медицинские системы) — то используют вообще EventBus на каждый поток.
                                      • 0

                                        А каким боком EventBus к базе данных и серверу? :) И с чего вы взяли, что я не использовал EventBus? Пока что кроме пространных суждений я ничего не увидел. Медицинские системы зачем-то уже приплели. Мы тут вроде под Android пишем, судя по хабу.

                                        • 0
                                          EventBus служит просто транспортом для доставки сообщений и ничего больше. И ничем иным она (шина событий) не будет. Событие — это сущность, которая переносит только данные и ничего другого в них нет. Т.е. мы получаем систему, объединенную одной транспортной системой. По аналогии — мы имеем единую федеральную транспортную систему. Мы можем перемещать по ней что угодно — главная ее задача — это обеспечение своевременной ДОСТАВКИ и ничего более. Если вы живете в поселке — вы можете не видеть ее всю жизнь. Но любая транспортная система связывает что-то. В нашем случае модули (сервисы). Модулю (сервису — не путать с сервисом андроида) глубоко плевать — откуда/кто/как доставил данные/как доставит результат — он только производит специфичные ему преобразования. Причем их динамически можно подгружать/выгружать. Т.е. обеспечивать в приложении сервис в зависимости от требований. Поэтому на них и строят динамические системы. Т.е. если завтра потребуется дополнительно перенаправить поток сообщений с экрана смарта на удаленный сервер — никаких заминок не происходит — вы просто дополнительно перенаправляете поток событий в другой сервис. Т.е. Шина событий + сервисно-ориентированная архитектура обеспечивает максимальную гибкость в проектировании и эксплуатации приложения не ограничивая приложение ничем. Уберите шину событий и будете применять слушатели, callbacks, intents, broadcasts — все это уже давно проходили.
                                          • 0

                                            По вашим словам поди и JavaScript (и любой не статически типизированный язык) лучше Java (любой статик), тем что в динамичном приложении нам не надо париться о конкретных типах, и мы можем на лету трактовать типы как удобнее. Ну-ну.
                                            А про устойчивость и надежность вы думали? Статические анализаторы не просто так придумали.
                                            Потоки событий подойдут в распределенных системах, потому что там иначе никак. Но не в мобильных приложениях, где надежность, скорость и простота отладки гораздо важнее чем написание абстрактных коней в вакууме, которые шлют события абы куда и принимаю абы откуда

                                            • 0
                                              Специалистам по надежности я прошу обратить внимание на Ada — язык специально для военных систем. И когда приводите примеры на Java всегда вспоминайте наш любимый NullPointerException. Также не хочется выслушивать лекции про оптимизацию приложения на предмет утечек памяти, хотя 20 лет назад нам пели песни о супер оптимизаторе памяти Java, который все делает за вас. Честно скажу г… а в Java Android полно. И если ваша задача — это максимум Галерея фоток со стандартным набором Dagger2 + Picasso), то некоторые разработчики пилят расширяемые системы на нескольких потоках.
                        • +1

                          Если я встречу подобный подход в чьих-то исходниках, то сразу поставлю диагноз: "архитектура головного мозга". Без обид.
                          С точки зрения оторванной от реальности теории — может и не очень хорошо, что диалог ищет своего слушателя, но если вспомнить, что после восстановления система (андроид) сама создает компоненты, то это вполне нормально. Надо просто помнить, что активити/фрагменты/диалоги — независимые сущности, которые должны сами о себе позаботиться.

                          • 0
                            >> А с каких это пор компонент должен заниматься поиском слушателя

                            Так принято.

                            Я статью до конца не читал, только до этого момента, но у вас там, кажется, утечка будет. Потому при нормальной реализации вы создаете сильную ссылку в onAttach(), и обнуляете ее в onDetach(), а так у вас останется сильная ссылка и активити утечет. Нужно викреф делать.

                            Короче, вы велосипед ненужный сделали. Первоначальный вариант — лучший.
                            • 0
                              UPD. Дочитал-таки. Теперь точно уверен, что это велосипед.

                              return App.get()

                              Апп-синглтон, конечно, не приведет к утечке, но это индикатор плохой архитектуры.
                              • 0
                                Поделитесь, пожалуйста, каким образом Вы обходитесь без Апп-синглтона при разработке приложения?
                                • 0
                                  Просто не использую контекст там, где нет возможности получить его без статических инстансов.
                                  Можете показать пример, в котором по-вашему не обойтись без аппа-синглтона?
                                  • 0
                                    Дело тут вовсе не в контексте. Давайте на простом примере. В приложении создан класс, ответственный за общение с сервером. Как правило, чтобы не создавать несколько инстансов такого класса используют либо синглтон, либо создают его в Application (руками или с помощью компонента даггера) и соответственно добираются к инстансу этого класса через Application. В примере выше как раз описывался примерно такой случай.
                              • 0
                                Согласен, что ссылку, сохраненную в onAttach(), нужно чистить в onDetach(), чтобы не было утечки памяти. И в реальном коде это было. Я не стал это копировать сюда, так как это напрямую не относилось к теме статьи.

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.