Кинжал 2 на Android @Singleton аннотированный класс, который не вводится

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

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

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

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

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

 public class SomeClass{ @Inject MyManager myManager; } 

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

ОБНОВИТЬ

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

Когда я начал добавлять @Component, у меня были некоторые проблемы с компилятором, потому что мой кинжал2 не был настроен правильно – ознакомьтесь с этим очень полезным вопросом о том, как правильно настроить кинжал2: https://stackoverflow.com/a/29943394/1041533

ОБНОВЛЕНИЕ 2

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

 @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); 

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

Я только что нашел это: https://stackoverflow.com/a/29326023/1041533

Что, если я не ошибаюсь, подразумевает, что мне нужно указать каждый класс в классе 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 + классов здесь? Это просто не работает, т. Е. Сбой приложения во время выполнения.

Solutions Collecting From Web of "Кинжал 2 на Android @Singleton аннотированный класс, который не вводится"

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

 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); 

Трудно сказать, какова была ваша проблема, поскольку вы не показывали, что выглядит ваш 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 минимальные основы, которые вам нужно понять, чтобы получить MyManager введенный в SomeClass :

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

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

 mSomeClass = new SomeClass(); 

Потому что, если вы сделали это (скажем, в 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:

  • Официальное руководство Dagger 2
  • Reddit страница с большим количеством ссылок

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

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

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

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

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