Кинжал 2 на Android @Singleton аннотированный класс не вводится
в настоящее время я пытаюсь интегрировать Dagger 2 в приложение для Android. Моя настройка проекта выглядит следующим образом:
- библиотека
- app (зависит от библиотеки)
в моем проекте библиотеки я определил класс, который я позже введу в другие классы, которые нуждаются в нем (действия и регулярные классы) в библиотеке, а также в проекте приложения.
@Singleton
public class MyManager{
@Inject
public MyManager(){
//Do some initializing
}
}
теперь-например, в моих фрагментах или действиях или регулярных классах, которые я бы ввел вышеприведенный Синглтон выглядит следующим образом:
public class SomeClass{
@Inject
MyManager myManager;
}
или так я думал, потому что на практике myManager всегда равен нулю. И, по-видимому, это конструктор никогда не вызывается, поэтому я думаю, что я должен пропустить что-то по конфигурации? Или, может быть, я неправильно понял документацию, и она вообще не должна работать таким образом? Цель класса MyManager-быть доступным для всего приложения компонентом-аккумулирующим объектом - вот почему я пошел на @Одиночка.
обновление
чтобы избежать путаницы: я упомянул, что у меня есть компоненты где - то в комментарии, я думаю, - это относится к компонентам в смысле "компонентного дизайна" и не имеет ничего общего с кинжалом. Код на основе кинжала, который у меня есть, приведен выше - в моем коде нет ничего, связанного с кинжалом.
когда я начал добавлять @Component у меня были некоторые проблемы с компилятором, потому что мой dagger2 не был настроен должным образом-проверьте это очень полезно, резьба по настройке dagger2 правильно: https://stackoverflow.com/a/29943394/1041533
обновление 2
вот мой обновленный код, основанный на предложениях г. ломбарда - я изменил код следующим образом-оригинальный Синглтон находится в проекте библиотеки:
@Singleton
public class MyManager{
@Inject
public MyManager(){
//Do some initializing
}
}
также в проекте библиотеки есть класс bootstrap:
@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);
экземпляр manager по-прежнему имеет значение null, т. е. не вводится.
Я только что нашел это:https://stackoverflow.com/a/29326023/1041533
что, если я не ошибаюсь, означает, что мне нужно указать каждый отдельный класс в Класс Bootstrap, который будет использовать @Inject для ввода материала. К сожалению - это не вариант, так как у меня более 40 классов и мероприятий, для которых мне придется это сделать.
означает, что мой интерфейс начальной загрузки, по-видимому, должен выглядеть примерно так:
@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+ классов здесь? Это просто не сработает - т. е. сбой приложения во время выполнения.
2 ответов
вы делаете ошибку в том, что используете
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
as injector
и _Bootstrap
as ApplicationComponent
, так это выглядит так:
Injector.INSTANCE.getApplicationComponent().inject(this);
но это только мое типичная установка. Имена не имеют значения.
изменить: на ваш последний вопрос, вы можете решить эту subscoping и зависимостей компонентов.
ваш проект библиотеки должен иметь возможность видеть только классы библиотеки, правильно? В этом случае все, что вы делаете, это
@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
), ваши зависимости не будут вводиться автоматически.
я не знаю, есть ли у вас опыт работы с концепциями инъекций зависимостей, но если вы этого не сделаете, я пройду через минимальные основы, которые вам нужно будет понять, чтобы получить ваш MyManager
вводят в SomeClass
:
во-первых: когда вы используете DI, вам нужно понять разницу между "newables" и "injectables". этот блогпост по Мисько Hevery объясняет подробности.
это означает, что вы не можете new
ваш SomeClass
. Это не сработает:
mSomeClass = new SomeClass();
потому что, если вы это сделали (скажем, в активности или фрагменте), Dagger не будет знать, что вы ожидали, что зависимость будет введена в SomeClass
и у него нет возможности ничего вводить.
для того, чтобы его зависимости были введены, вы должны создать экземпляр (или ввести) SomeClass
себя через Кинжал тоже.
другими словами, скажите в ваша деятельность, где необходимо:
@Inject
SomeClass mSomeClass;
Далее, вам нужен компонент кинжала для выполнения фактической инъекции. Чтобы создать компонент, вы создаете интерфейс с методом, который принимает ваш корневой объект (скажем 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 требует метода инъекции для каждого activity или другой класс, который вы хотите ввести.
как вы упомянули, это будет раздражать, когда у вас много различных мероприятий в вашем приложении. Существуют некоторые возможные обходные пути для этого, поиск "dagger 2 inject base class", например, это предложение @EpicPandaForce: инъекции базового класса Dagger 2
также обратите внимание, как указал @EpicPandaForce в комментариях, что в моем упрощенном примере я назвал DaggerLibraryComponent.create()
каждый раз, когда это, вероятно, не то, что вы хотите, так как этот компонент должен предоставлять ваши синглеты, поэтому вам, вероятно, лучше получить существующий экземпляр откуда-то еще, например, из вашего экземпляра приложения.