Как вы организуете свои модули и компоненты Dagger 2?

У вас есть конкретный пакет, в который вы помещаете все классы, связанные с кинжалом?

Или вы помещаете их рядом с соответствующим классом, который они вводят, например. если у вас есть MainActivityModule и MainActivityComponent, вы помещаете их в тот же пакет, что и ваш MainActivity.

Кроме того, я видел немало людей, определяющих компоненты как внутренние классы, например. a ApplicationComponent, который определен внутри класса Application. Считаете ли вы, что это хорошая практика?

Ответ 1

EDIT: Позвольте мне начать с того, что это близко к истине здесь, но это antipattern, как описано Мартином Фаулером Data Domain Presentation Layering article ЗДЕСЬ (НАЖМИТЕ ЛИНК!), в котором указывается, что не должен иметь MapperModule и PresenterModule, вы должны иметь GalleryModule и SomeFeatureModule который имеет в нем все картографы, докладчики и т.д.

Умный маршрут для этого - использовать зависимости компонентов для подкачки вашего оригинального синглтон-компонента для каждой вашей функции. Это то, что я описал, это "полный стек" , разделение .

То, что записано ниже, - это "анти-шаблон", где вы вырезаете модули верхнего уровня приложения в "слои". Для этого есть множество недостатков. Не делай этого. Но вы можете прочитать его и узнать, что не делать.

ОРИГИНАЛЬНЫЙ ТЕКСТ:

Обычно вы должны использовать один Component, такой как ApplicationComponent, чтобы содержать все однопользовательские зависимости, которые вы используете во всем приложении, пока существует все приложение. Вы должны создать это в своем классе Application и сделать это доступным из других источников.

Структура проекта для меня в настоящее время:

+ injection
|- components
   |-- ApplicationComponent.java
|- modules
   |- data
      |-- DbMapperModule.java
      |-- ...
   |- domain
      |-- InteractorModule.java
      |-- ...
   |- presentation
      |-- ...
   |- utils
      |-- ...
|- scope
|- subcomponents
   |- data
      |-- ...
   |- domain
      |-- DbMapperComponent.java
      |-- ...
   |- presentation
      |-- ...
   |- utils
      |-- ...
   |-- AppContextComponent.java
   |-- AppDataComponent.java
   |-- AppDomainComponent.java
   |-- AppPresentationComponent.java
   |-- AppUtilsComponent.java

Например, мой выглядит следующим образом:

public enum Injector {
    INSTANCE;
    private ApplicationComponent applicationComponent;

    private Injector() {
    }

    public ApplicationComponent getApplicationComponent() {
        return applicationComponent;
    }

    ApplicationComponent initializeApplicationComponent(CustomApplication customApplication) {
        AppContextModule appContextModule = new AppContextModule(customApplication);
        RealmModule realmModule = new RealmModule(customApplication.getRealmHolder());
        applicationComponent = DaggerApplicationComponent.builder()
                .appContextModule(appContextModule)
                .realmModule(realmModule)
                .build();
        return applicationComponent;
    }
}

И вам понадобится ApplicationComponent, который может вставлять любые защищенные оболочкой поля любого класса, который вы хотите ввести в поле.

@Singleton
@Component(modules = {
        AppContextModule.class,
        DbMapperModule.class,
        DbTaskModule.class,
        RealmModule.class,
        RepositoryModule.class,
        InteractorModule.class,
        ManagerModule.class,
        ServiceModule.class,
        PresenterModule.class,
        JobManagerModule.class,
        XmlPersisterModule.class
})
public interface ApplicationComponent
        extends AppContextComponent, AppDataComponent, AppDomainComponent, AppUtilsComponent, AppPresentationComponent {
    void inject(CustomApplication customApplication);

    void inject(DashboardActivity dashboardActivity);

    ...
}

Для меня AppContextComponent будет @Subcomponent, но на самом деле это не значит. Это всего лишь способ создать подскоп, а не способ сократить ваш компонент на более мелкие части. Таким образом, интерфейс, который я наследую, на самом деле является обычным interface с методами предоставления. То же самое для остальных.

public interface AppContextComponent {
    CustomApplication customApplication();

    Context applicationContext();

    AppConfig appConfig();

    PackageManager packageManager();

    AlarmManager alarmManager();
}

Компонентные зависимости (которые позволяют подкапывать, как и подкомпоненты) не позволяют использовать несколько компонентов с областью, что также означает, что ваши модули не будут доступны. Это связано с тем, как вы не можете наследовать из нескольких областей, так же, как вы не можете наследовать от нескольких классов в Java.

Поставщики без доступа делают это так, чтобы модуль не сохранял один экземпляр, а новый - при каждом вызове инъекции. Чтобы получить зависимые от области действия области действия, вам также необходимо предоставить область применения методов модуля.

@Module
public class InteractorModule {
    @Provides
    @Singleton
    public LeftNavigationDrawerInteractor leftNavigationDrawerInteractor() {
        return new LeftNavigationDrawerInteractorImpl();
    }

    ...
}

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

@Component(dependencies = {ApplicationComponent.class}, modules = {DetailActivityModule.class}) 
@ActivityScope
public interface DetailActivityComponent extends ApplicationComponent {
    DataObject data();

    void inject(DetailActivity detailActivity);
}

@Module
public class DetailActivityModule {
    private String parameter;

    public DetailActivityModule(String parameter) {
        this.parameter = parameter;
    }

    @Provides
    public DataObject data(RealmHolder realmHolder) {
        Realm realm = realmHolder.getRealm();
        return realm.where(DataObject.class).equalTo("parameter", parameter).findFirst();
    }
}

Подсканирование позволяет вам иметь несколько экземпляров вашего презентатора, которые затем могут сохранять состояние. Это имеет смысл, например, в Mortar/Flow, где каждый экран имеет свой собственный "путь" , и каждый путь имеет свой собственный компонент - для предоставить данные в качестве "плана".

public class FirstPath
        extends BasePath {
    public static final String TAG = " FirstPath";

    public final int parameter;

    public FirstPath(int parameter) {
        this.parameter = parameter;
    }

    //...

    @Override
    public int getLayout() {
        return R.layout.path_first;
    }

    @Override
    public FirstViewComponent createComponent() {
        FirstPath.FirstViewComponent firstViewComponent = DaggerFirstPath_FirstViewComponent.builder()
                .applicationComponent(InjectorService.obtain())
                .firstViewModule(new FirstPath.FirstViewModule(parameter))
                .build();
        return firstViewComponent;
    }

    @Override
    public String getScopeName() {
        return TAG + "_" + parameter;
    }

    @ViewScope //needed
    @Component(dependencies = {ApplicationComponent.class}, modules = {FirstViewModule.class})
    public interface FirstViewComponent
            extends ApplicationComponent {
        String data();

        FirstViewPresenter firstViewPresenter();

        void inject(FirstView firstView);

        void inject(FirstViewPresenter firstViewPresenter);
    }

    @Module
    public static class FirstViewModule {
        private int parameter;

        public FirstViewModule(int parameter) {
            this.parameter = parameter;
        }

        @Provides
        public String data(Context context) {
            return context.getString(parameter);
        }

        @Provides
        @ViewScope //needed to preserve scope
        public FirstViewPresenter firstViewPresenter() {
            return new FirstViewPresenter();
        }
    }

    public static class FirstViewPresenter
            extends ViewPresenter<FirstView> {
        public static final String TAG = FirstViewPresenter.class.getSimpleName();

        @Inject
        String data;

        public FirstViewPresenter() {
            Log.d(TAG, "First View Presenter created: " + toString());
        }

        @Override
        protected void onEnterScope(MortarScope scope) {
            super.onEnterScope(scope);
            FirstViewComponent firstViewComponent = scope.getService(DaggerService.TAG);
            firstViewComponent.inject(this);
            Log.d(TAG, "Data [" + data + "] and other objects injected to first presenter.");
        }

        @Override
        protected void onSave(Bundle outState) {
            super.onSave(outState);
            FirstView firstView = getView();
            outState.putString("input", firstView.getInput());
        }

        @Override
        protected void onLoad(Bundle savedInstanceState) {
            super.onLoad(savedInstanceState);
            if(!hasView()) {
                return;
            }
            FirstView firstView = getView();
            if(savedInstanceState != null) { //needed check
                firstView.setInput(savedInstanceState.getString("input"));
            }
        }

        public void goToNextActivity() {
            FirstPath firstPath = Path.get(getView().getContext());
            if(firstPath.parameter != R.string.hello_world) {
                Flow.get(getView()).set(new FirstPath(R.string.hello_world));
            } else {
                Flow.get(getView()).set(new SecondPath());
            }
        }
    }
}

Ответ 2

У вас есть конкретный пакет, в который вы помещаете все связанные с кинжалом классы?

Или вы помещаете их рядом с соответствующим классом, который они вводят, например. если вы имеют MainActivityModule и MainActivityComponent, вы помещаете их в тот же пакет, что и ваш MainActivity.

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

Я обычно организую такие классы Dagger 2:

- di
|
+-- ApplicationComponent class
|    
+-- modules
   |
   +-- AndroidModule class
   |
   +-- WebServiceModule class
   |
   +-- ...
   |
  • di пакет содержит классы, связанные с кинжалом 2 и инъекцией зависимостей.
  • В большинстве случаев Android-приложение обычно имеет один компонент - здесь он называется ApplicationComponent. Я еще не создал приложение для Android со многими компонентами Dagger 2, и я видел решения только с одним компонентом.
  • modules пакет содержит модули Dagger 2

Я не создаю модуль для каждой операции. Функциональные возможности групп. Например. элементы, сильно связанные с системным интерфейсом для SharedPreferences, EventBus (если вы используете что-то подобное), сетевое подключение и т.д. можно найти в AndroidModule. Если у вашего проекта есть важные интерфейсы для WebService или их много, вы можете сгруппировать их в WebServiceModule. Если ваше приложение, например, отвечает за анализ сети и имеет множество интерфейсов для подобных задач, связанных с сетью, вы можете сгруппировать эти интерфейсы в NetworkModule. Когда ваше приложение прост, может случиться, что у вас будет только один модуль. Когда это осложняется, у вас может быть много модулей. На мой взгляд, у вас не должно быть много интерфейсов в одном модуле. Когда в такой ситуации вы можете разделить их на отдельные модули. Вы также можете сохранить определенную бизнес-логику для своего проекта в отдельном модуле.

Кроме того, я видел немало людей, определяющих компоненты как внутренние классы, например. ApplicationComponent, который определен внутри Класс приложения. Считаете ли вы, что это хорошая практика?

Я не уверен, что это хорошая или плохая практика. Я думаю, что нет необходимости это делать. Вы можете создать открытый статический метод get() внутри класса, расширяющий класс Application, который вернет экземпляр Application в качестве одноэлементного. Это гораздо более простое решение, и у нас должен быть только один экземпляр класса Application. Если мы хотим издеваться над контекстом в модульных тестах, мы можем принять Контекст как параметр и в коде приложения, передать контекст приложения или контекст активности в зависимости от ситуации.

Обратите внимание: это просто мой подход, и некоторые более опытные разработчики могут организовать свои проекты другим и лучшим способом.