Застрял в попытке реализовать официальную стратегию Dagger, чтобы избежать громоздкого кода

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

Согласно статье:

Пользователи AppCompat должны продолжать внедрять AndroidInjector.Factory<? extends Activity> and not <? extends AppCompatActivity> (или же FragmentActivity).

Я придерживаюсь архитектуры MVP, где представления всегда Fragmentи я не хочу привлекать Activity в любом бизнесе DI, но мне интересно, если это необходимо для работы, но пока я не смог. Если я пропущу всю вещь поддержки, приложение вылетает во время выполнения, потому что экземпляр фрагмента является поддержкой (в случае, если это не очевидно). Затем я занялся попыткой реализовать HasSupportFragmentInjector вместо HasFragmentInjector с кучей изменений из-за ошибок компиляции мой разум забыл ради моего психического здоровья. Через некоторое время я подхожу к мысли, как может не поддерживающая деятельность разместить фрагмент поддержки. Ах! Эти хитрые подстановочные знаки. Но как бы я ни старался следовать советам, я не могу придумать пути без EmptyModule что мне также нужно будет настроить в Activity, чтобы он был виден фрагменту кинжалом и его (на самом деле, для меня все же, магией). Почему я не пробовал это? С таким же успехом я могу, но я устал от безнадежных перемен, и мне нужна помощь на этом этапе.

AppModule.kt

@Singleton
@dagger.Module
class AppModule(val application: Application) {
    @Provides @Singleton fun application(): Application = application
    ...
}

AppComponent.java

@ApplicationScope
@Singleton
@Component(modules = {
        AndroidSupportInjectionModule.class,
        ...
        FooFragmentModule.class,
})
public interface AppComponent {
    Application app();
    ...
    void inject(MyApp app);
}

MyApp.java

public class MyApp extends Application implements HasActivityInjector {

    private AppComponent component;
    public AppComponent someWayToReturnAppComponent() {
        ...
    }

    @Inject DispatchingAndroidInjector<Activity> dispatchingActivityInjector;

    @Override
    public void onCreate() {
        component = DaggerAppComponent.builder()
                .appModule(new AppModule(this))
                // more app-scoped modules
                .build();

        component.inject(this);
    }


    @Override
    public AndroidInjector<Activity> activityInjector() {
        return dispatchingActivityInjector;
    }

}

MainActivity.java

public abstract class MainActivity extends AppCompatActivity implements HasSupportFragmentInjector {

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

        setContentView(getLayout()); // inflate the fragment via XML here
    }

    @Inject DispatchingAndroidInjector<Fragment> dispatchingFragmentInjector;

    @Override
    public AndroidInjector<Fragment> fragmentInjector() {
        return dispatchingFragmentInjector;
    }
}

FooFragmentComponent.java

@Subcomponent
public interface FooFragmentComponent extends AndroidInjector<FooFragment> {

    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<FooFragment> {}

}

FooFragmentModule.kt

@dagger.Module(subcomponents = {FooFragmentComponent.class})
public abstract class FooFragmentModule {

    @Binds
    @IntoMap
    @FragmentKey(FooFragment.class)
    abstract AndroidInjector.Factory<? extends Fragment> bindFragmentInjectorFactory(FooFragmentComponent.Builder builder);

    @ActivityScope
    abstract FooFragment contributeFooFragmentInjector();

    @Provides
    static FooPresenter presenter() {
        return new FooPresenter();
    }
}

FooFragment

public class FooFragment extends Fragment implements SomeView {

    @Inject FooPresenter presenter;

}

ХОРОШО. На этом этапе и возвращаясь к

Пользователи AppCompat должны продолжать внедрять AndroidInjector.Factory<? extends Activity>

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

РЕДАКТИРОВАТЬ

После следования советам EpicPandaForce по использованию AndroidSupportInjectionModuleКинжал теперь жалуется

Методы FragmentKey должны связываться dagger.android.AndroidInjector.Factory<? extends android.app.Fragment>не dagger.android.AndroidInjector.Factory<? extends android.support.v4.app.Fragment>,

1 ответ

Решение

Как упоминалось в комментариях @EpicPandaForce, для поддержки фрагментов необходимо использовать AndroidSupportInjectionModule. Вам также необходимо использовать FragmentKey в dagger.android.support, а не в dagger.android. Это должно помочь вам преодолеть проблему при редактировании.

В более широком смысле, фрагменты поддержки не расширяют базовые фрагменты (которые все равно не рекомендуются в API 28 и выше). Это рисует их в отличие от AppCompatActivity и его суперкласса, библиотеки поддержки FragmentActivity, которая расширяет платформу Activity, как это представлено в Android API уровня 1. Таким образом, независимо от того, используете ли вы фрагменты поддержки или встроенные фрагменты, у вас может не быть родительский AppCompatActivity, но у вас всегда будет какое-то действие. Это важно, потому что Android оставляет за собой право создавать экземпляр вашего фрагмента, используя необходимый общедоступный конструктор без аргументов, а это значит, что фрагмент может быть внедрен самостоятельно только с помощью тех вещей, которые он может найти внутри. onAttach (т.е. его родительские фрагменты, его активность или его приложение).

dagger.android не заботится о том, является ли ваша активность активностью AppCompatActivity, потому что она не использует активность, кроме поиска своего собственного инжектора. Вы можете видеть это в частном методе AndroidSupportInjection.findHasFragmentInjector, который проверяет (по порядку) иерархию родительских фрагментов, затем Activity, затем Application. Следовательно, даже несмотря на то, что практически поддерживающие фрагменты будут правильно функционировать только при действиях поддержки, dagger.android может связывать свои ключи на основе действия суперкласса, потому что нет причин различать их и настраивать две отдельные карты (Activity против AppCompatActivity). Даже если бы существовало такое разделение, вы могли бы привязать инжекторы AppCompatActivity к вашей карте активности, и все стало бы очень запутанным.

Из этого порядка поиска также следует принять, что если у вас нет привязок в области действия, вам не нужно создавать компонент в области действия; вы можете иметь в своем приложении реализацию HasSupportFragmentInjector, установить свой FooFragmentModule непосредственно в AppComponent и удалить HasSupportFragmentInjector из своей функции MainActivity. Это нетипично только потому, что большинство приложений имеют некоторое состояние состояния активности или контроллеры, которые должны быть введены (даже просто внедряя сам экземпляр действия, его контекст или ресурсы). Если у вас есть только @ActivityScope аннотации, поскольку вы пытаетесь заставить эту работу работать, вы можете полностью пропустить этот шаг и использовать только компонент приложения и несколько подкомпонентов фрагмента. Тем не менее, я думаю, что очень вероятно, что вам в конечном итоге понадобится @ActivityScopeпоэтому создание компонента для него на ранних этапах вполне разумно.

Другие вопросы по тегам