Кинжал 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() каждый раз, когда это, вероятно, не то, что вы хотите, так как этот компонент должен предоставлять ваши синглеты, поэтому вам, вероятно, лучше получить существующий экземпляр откуда-то еще, например, из вашего экземпляра приложения.