Кинжал и вложенные инъекции

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

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

Что работает

Действие, в которое вводится мой помощник:

public class MyActivity extends Activity {
    @Inject SampleHelper helper;

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

        ((MyApplication) getApplication()).inject(this);

        Log.i("debug", "helper = " + helper);
        Log.i("debug", "helper context = " + helper.context);
    }
}

Приложение, создающее граф объекта:

public class MyApplication extends Application {
    private ObjectGraph graph;

    @Override
    public void onCreate() {
        super.onCreate();

        graph = ObjectGraph.create(getModules());
    }

    private Object[] getModules() {
        return new Object[] { new MyModule(this) };
    }

    public void inject(Object target) {
        graph.inject(target);
    }
}

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

@Singleton
public class SampleHelper {

    @Inject public Context context;

    @Inject
    public SampleHelper() {}
}

Со следующим модулем:

@Module(
    injects = { MyActivity.class },
    complete = false,
    library = true
)
public class MyModule {
    private final MyApplication application;

    public MyModule(MyApplication application) {
      this.application = application;
    }

    @Provides @Singleton Context provideApplicationContext() {
        return application;
    }
}

Что не работает

Однако, когда я отделяю вспомогательный интерфейс от его реализации:

public interface SampleHelper {
}

@Singleton
public class SampleHelperImpl implements SampleHelper {

    @Inject public Context context;

    @Inject
    public SampleHelperImpl() {}
}

И добавьте это в модуль кинжала:

public class MyModule {
    ...

    // added this method
    @Provides @Singleton public SampleHelper provideSampleHelper() {
        return new SampleHelperImpl();
    }

    ...
}

Контекст не вводится в мой SampleHelperImpl, как я ожидал. Теперь, я полагаю, это связано с тем, что экземпляр SampleHelperImpl создается посредством прямого вызова конструктора, а не вызова конструктора, инициированного инъекцией, потому что MyModule # provideApplicationContext () даже не вызывается, поэтому я предполагаю, что мне что-то не хватает в Dagger (который является вероятно, поскольку мой предыдущий опыт DI включал только Spring).

Есть идеи о том, как внедрить мой контекст в мою внедренную реализацию помощника «чистым кинжалом»?

Большое спасибо!


person mrlem    schedule 15.07.2013    source источник
comment
Я также сейчас борюсь с кинжалом, и у меня такая же конфигурация, как у вас. Мне нужно внедрять экземпляры действий в помощники, а не в само приложение, но в остальном это очень похоже. Вот почему у меня к вам вопрос: почему вы вставляете контекст как член в SampleHelper, а не как параметр конструктора? В моем случае инъекция через параметр конструктора не работает, и поэтому мне интересно, есть ли в этом что-то особенное, чего следует избегать. Я понимаю, что это не по теме, но если вы можете помочь, я буду признателен.   -  person Haspemulator    schedule 24.07.2013
comment
Фактически, теперь я передаю контекст как параметр конструктора (как упоминалось в моем ответе ниже), хотя он не вводится непосредственно в сам конструктор, а скорее как часть метода @Provides в модуле dagger (и он работает). Моя первоначальная идея заключалась в том, чтобы отбросить весь код, связанный с назначением моих членов, благодаря кинжалу, но я не мог сделать его таким же простым в использовании, как, скажем, Spring-Injection. При этом я понимаю, что они работают по-разному и с теми же ограничениями, и кинжал по-прежнему приносит много пользы.   -  person mrlem    schedule 24.07.2013
comment
Привет, спасибо за ответ. Может быть, вы можете взглянуть на мой вопрос? stackoverflow.com/questions/17839451/   -  person Haspemulator    schedule 24.07.2013


Ответы (4)


Это довольно старый вопрос, но я думаю, что вам нужно следующее:

@Provides @Singleton public SampleHelper provideSampleHelper(SampleHelperImpl impl) {
    return impl;
}

Таким образом, Dagger создаст ваш SampleHelperImpl и, следовательно, внедрит его.

person alexanderblom    schedule 27.08.2013
comment
Еще не поздно: он действительно отлично отвечает на мой вопрос. Большое спасибо! - person mrlem; 28.08.2013
comment
@alexanderblom Не могли бы вы объяснить разницу между тем, что вы сделали, и тем, что сделал mrlem? - person peacepassion; 14.09.2014

В случае, если кому-то интересно, при реализации метода @Provides в модуле кинжала вы можете получить такие экземпляры объектов, обрабатываемых кинжалом:

@Provides @Singleton public SampleHelper provideSampleHelper(Context context) {
    SampleHelper helper = new SampleHelperImpl();
    helper.setContext(context);
    return helper;
}

Это работает, но я все еще чувствую, что это немного неуклюже, поскольку мне приходится явно вызывать мои установщики помощников (обычно это то, от чего вы хотите избавиться с помощью инъекции).

(Я немного подожду, если кто-то предложит лучшее решение)

person mrlem    schedule 16.07.2013

(Относится к Dagger v1.0.1)

Убедитесь, что вы используете инъекцию адаптера. С отражающей инъекцией кинжал явно не выполняет транзитивную инъекцию @Provides объектам. Я считаю это ошибкой.

person André W8    schedule 23.07.2013
comment
Это похоже на мою проблему. Но не могли бы вы прояснить 2 вещи: 1 / Я думал, что он не использует отражение для своих инъекций? (что, я полагаю, вы имели в виду под отражающей инъекцией, или вы имели в виду инъекцию на основе аннотаций?) 2 / что вы имеете в виду под инъекцией адаптера: инъекции с использованием @Provides в модуле кинжала? - person mrlem; 24.07.2013
comment
Dagger предоставляет два способа установки @Inject аннотированных полей. Один - через отражение, другой - через сгенерированный класс адаптера. В обоих случаях поля должны быть доступны как минимум для пакета. Метод отражения является запасным и может быть недоступен в будущих версиях. Чтобы проверить, какой метод внедрения вы используете, установите точку останова в методе @Provides вашего модуля и проверьте трассировку стека. Обратите внимание: injectMembers():118, ReflectiveAtInjectBinding {dagger.internal.plugins.reflect} vs injectMembers():82, YourClassname$$InjectAdapter {your.classes.package} - person André W8; 31.07.2013

Что касается внедрения правильного контекста, вы можете взглянуть на этот образец https://github.com/square/dagger/tree/master/examples/android-activity-graphs.

person jfrey    schedule 16.07.2013
comment
Спасибо за ответ: я уже сталкивался с этим примером (их не так уж много;). Действительно, в реальном приложении вам нужно ввести правильный контекст. Но мой первоначальный вопрос был немного более общим: разделение интерфейса Helper от реализации предотвращает работу @Inject внутри реализации, поскольку создание экземпляра помощника больше не обрабатывается напрямую Dagger. - person mrlem; 17.07.2013