Кинжал 2 на Android-аннотации @Singleton не вводится

В настоящее время я пытаюсь интегрировать Dagger 2 в приложение для Android. Моя настройка проекта выглядит следующим образом:

  • библиотека
  • приложение (зависит от библиотеки)

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

@Singleton
public class MyManager{
  @Inject
  public MyManager(){
    //Do some initializing
  }
}

Теперь - например, в моих фрагментах или действиях или в обычных классах я бы ввел вышеупомянутый Singleton следующим образом:

public class SomeClass{

  @Inject
  MyManager myManager;
}

Или так я думал, потому что на практике myManager всегда равен нулю. И, судя по всему, конструктор так и не вызывается, поэтому, наверное, я должен упустить что-то конфигурационное? Или, может быть, я неправильно понял документацию, и он не предназначен для работы таким образом вообще? Цель класса MyManager - быть доступным компонентом, накапливающим компоненты, доступным для всего приложения - вот почему я пошел на @Singleton.

UPDATE

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

Когда я начал добавлять @Component, у меня были некоторые проблемы с компилятором, потому что мой кинжал2 не был настроен должным образом - проверьте этот действительно полезный поток о том, как правильно настроить dagger2: qaru.site/info/69355/...

ОБНОВЛЕНИЕ 2

Вот мой обновленный код, основанный на предложениях Дж. Ломбарда - я изменил код следующим образом: оригинальный синглтон находится в проекте библиотеки:

@Singleton
public class MyManager{
  @Inject
  public MyManager(){
    //Do some initializing
  }
}

Также в проекте библиотеки находится класс начальной загрузки:

@Singleton
@Component
public interface Bootstrap {
    void initialize(Activity activity);
}

Затем я использую вышеприведенный класс Bootstrap в своей деятельности (в моем конкретном приложении НЕ в проекте библиотеки! Тем не менее, у меня есть также классы/действия в библиотеке, которые будут обращаться к Bootstrap для ввода MyManager):

public class MyActivity extends Activity{

    @Inject
    MyManager manager;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //DONT DO THIS !!! AS EXPLAINED BY EpicPandaForce
        DaggerBootstrap.create().initialize(this);
    }
}

Но даже после этой строки:

        DaggerBootstrap.create().initialize(this);

экземпляр менеджера по-прежнему остается нулевым, то есть не вводится.

Я только что нашел это: qaru.site/info/229095/...

Что, если я не ошибаюсь, подразумевает, что мне нужно указать каждый отдельный класс в классе Bootstrap, который будет использовать @Inject для ввода данных. К сожалению - это не вариант, поскольку у меня более 40 классов и видов деятельности, для которых я должен был бы это сделать.

Значение моего интерфейса Bootstrap, по-видимому, должно выглядеть примерно так:

@Singleton
@Component
public interface Bootstrap {
    void initialize(ActivityA activity);
    void initialize(ActivityB activity);
    void initialize(ActivityC activity);
    void initialize(ActivityD activity);
    void initialize(ActivityE activity);
    void initialize(ActivityF activity);
    //and so on and so forth...
}

Если вышесказанное верно, это не стоит для моего варианта использования. Плюс: Кажется, нет никакой проверки времени компиляции, если я забыл указать один из моих 40 + классов здесь? Это просто не сработает - то есть сбой приложения во время выполнения.

Ответ 1

Вы ошибаетесь в том, что используете

DaggerBootstrap.create().initialize(this);

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

public class CustomApplication extends Application {
    @Override
    public void onCreate() {
         super.onCreate();
         Bootstrap.INSTANCE.setup();
    }
}

@Component
@Singleton
public interface _Bootstrap {
    void initialize(ActivityA activityA);
    //void initiali...
}

public enum Bootstrap {
    INSTANCE;

    private _Bootstrap bootstrap;

    void setup() {
        bootstrap = Dagger_Bootstrap.create();
    }

    public _Bootstrap getBootstrap() {
        return bootstrap;
    }
}

Тогда вы могли бы назвать его

Bootstrap.INSTANCE.getBootstrap().initialize(this);

Таким образом, вы разделяете компонент по своим классам. Я лично назвал Bootstrap как injector и _Bootstrap как ApplicationComponent, поэтому он выглядит так:

Injector.INSTANCE.getApplicationComponent().inject(this);

Но это только моя типичная настройка. Имена действительно не имеют значения.

EDIT: к вашему последнему вопросу вы можете решить это путем подкопирования и зависимостей компонентов.

Ваш проект библиотеки должен иметь возможность видеть только классы библиотеки, правильно? В этом случае все, что вы делаете, это

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface LibraryScope {
}

@Component(modules={LibraryModule.class})
@LibraryScope
public interface LibraryComponent {
    LibraryClass libraryClass(); //provision method for `MyManager`
}

@Module
public class LibraryModule {
    @LibraryScope
    @Provides
    public LibraryClass libraryClass() { //in your example, LibraryClass is `MyManager`
        return new LibraryClass(); //this is instantiation of `MyManager`
    }
}

public enum LibraryBootstrap {
    INSTANCE;

    private LibraryComponent libraryComponent;

    static {
        INSTANCE.libraryComponent = DaggerLibraryComponent.create();
    }

    public LibraryComponent getLibraryComponent() {
        return libraryComponent;
    }
}

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationScope {
}

@Component(dependencies={LibraryComponent.class}, modules={AdditionalAppModule.class})
@ApplicationScope
public interface ApplicationComponent extends LibraryComponent {
    AdditionalAppClass additionalAppClass();

    void inject(InjectableAppClass1 injectableAppClass1);
    void inject(InjectableAppClass2 injectableAppClass2);
    void inject(InjectableAppClass3 injectableAppClass3);
}

@Module
public class AdditionalAppModule {
    @ApplicationScope
    @Provides
    public AdditionalAppClass additionalAppClass() { //something your app shares as a dependency, and not the library
        return new AdditionalAppClass();
    }
}

public enum ApplicationBootstrap {
    INSTANCE;

    private ApplicationComponent applicationComponent;

    void setup() {
        this.applicationComponent = DaggerApplicationComponent.builder()
                                        .libraryComponent(LibraryBootstrap.INSTANCE.getLibraryComponent())
                                        .build();
    }

    public ApplicationComponent getApplicationComponent() {
        return applicationComponent;
    }
}

Тогда

@Inject
LibraryClass libraryClass; //MyManager myManager;

...
    ApplicationBootstrap.INSTANCE.getApplicationComponent().inject(this);

Ответ 2

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

Предполагая эту логическую структуру:

/app
   MainComponent
   SomeClass    // where MyManager is to be injected
   MainActivity // where SomeClass is to be injected
/library
   LibraryComponent
   MyManager    // Singleton

Затем ваши перечисленные классы будут правильно вводить следующую конфигурацию:

@Singleton
@Component
public interface LibraryComponent {
    MyManager getMyManager();
}

и компонент уровня приложения для встраивания зависимостей в активность:

@ActivityScope
@Component(dependencies = LibraryComponent.class)
public interface MainComponent {
    void inject(MainActivity mainActivity);
}

Обратите внимание, что MainComponent зависит от LibraryComponent, но поскольку у последнего есть однотонная область, вам нужно также определить область для другого, в которой я был использован здесь "область действия". (Или вы также можете просто сделать MainComponent синглтон и полностью избавиться от LibraryComponent, если это соответствует вашим потребностям.)

Наконец, все это вводится в действие следующим образом:

@Inject
SomeClass someClass;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    DaggerMainComponent.builder()
            .libraryComponent(DaggerLibraryComponent.create())
            .build()
            .inject(this);

    someClass.doSomething();
}

Я разместил рабочий образец здесь, на GitHub

Обновление 1:

Если я правильно понял вашу настройку, вы использовали только аннотации @Singleton и @Inject для двух перечисленных классов (MyManager и SomeClass), и нет другого кода, связанного с кинжалом в вашем проекте.

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

Я не знаю, есть ли у вас опыт в концепциях Dependency Injection, но если вы этого не сделаете, я пройду минимальные основы, которые вам нужно будет понять, чтобы ваш MyManager был введен в SomeClass:

Во-первых: когда вы используете DI, вам нужно понять разницу между "новыми" и "инъекционными". Этот blogpost от Misko Hevery объясняет детали.

Это означает, что вы не можете new добавить свой SomeClass. Это не сработает:

mSomeClass = new SomeClass();

Потому что, если вы это сделали (скажем, в действии или фрагменте), Кинжал не будет знать, что вы ожидали, что зависимость будет введена в SomeClass, и у него нет никакой возможности вставить что-либо.

Для того, чтобы его зависимости были введены, вам необходимо создать экземпляр (или ввести) SomeClass через кинжал.

Другими словами, скажите в своей деятельности, где используется SomeClass, вам понадобится:

@Inject
SomeClass mSomeClass;

Затем вам понадобится компонент Dagger для выполнения фактической инъекции. Чтобы создать компонент, вы создаете интерфейс с методом, который принимает ваш корневой объект (например, MainActivity) в качестве аргумента, например:

@Singleton
@Component
public interface Bootstrap {
    void initialize(MainActivity activity);
}

Теперь, когда вы создаете свой проект, Dagger 2 генерирует класс под названием DaggerBootstrap, который реализует этот интерфейс. Вы используете этот сгенерированный класс для выполнения инъекции, например, в своей деятельности onCreate:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    DaggerBootstrap.create().initialize(this);

    mSomeClass.doSomething();
}

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

Некоторые полезные ресурсы Кинжала 2:

Обновление 2:

Похоже, что последний недостающий фрагмент головоломки заключался в том, что ваш компонент предоставил метод ввода для базового класса Activity, но не для вашей конкретной конкретной деятельности.

К сожалению, Dagger 2 требует метода инъекции для каждой активности или другого класса, который вы хотите ввести.

Как вы уже упоминали, это будет раздражать, когда у вас много разных действий в вашем приложении. Там есть некоторые возможные обходные пути для этого, ищите "кинжал 2 впрыскивать базовый класс", например это предложение от @EpicPandaForce: инъекции базового класса Dagger

Также обратите внимание, как отметил @EpicPandaForce в комментариях, что в моем упрощенном примере я называл DaggerLibraryComponent.create() каждый раз, который, вероятно, не тот, который вы хотите, поскольку этот компонент должен предоставлять ваши синглтоны, так что вы вероятно, лучше получить существующий экземпляр из другого места, например, из вашего экземпляра приложения.