Понимание Dagger 2 для разработки под Android

Вот мой код, основанный на каком-то старом учебнике, найденном в Интернете. На основном сайте Dagger 2 действительно должны быть какие-то примеры, мне было очень сложно понять, как все это реализовать.

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

Должен ли я вызывать DaggerLoggerComponent в каждом классе, в котором я хочу получить некоторые компоненты, такие как мой класс Logger?

Также как я могу сделать область класса Logger одноэлементной? Прямо сейчас каждое нажатие кнопки создает новый экземпляр регистратора.

Вероятно, я не понимаю некоторые основные концепции, раньше я использовал только внедрение зависимостей в Spring, и все это кажется мне странным.

public class MainActivity extends AppCompatActivity {

    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    private void init(){
        button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                LoggerComponent component = DaggerLoggerComponent.builder().loggerModule(new LoggerModule()).build();
                component.getLogger().log("Hello!",MainActivity.this);
            }
        });
    }

}


public class Logger {

    private static int i = 0;

    public Logger(){
        i++;
    }

    public static int getI() {
        return i;
    }

    public void log(String text, Context context){
        Toast.makeText(context,text+" "+i,Toast.LENGTH_SHORT).show();
    }
}


@Singleton
@Component(modules={LoggerModule.class})
public interface LoggerComponent {

    Logger getLogger();

}


@Module
public class LoggerModule {
    @Provides
    @Singleton
    Logger provideLogger(){
        return new Logger();
    }
}

person Greyshack    schedule 28.08.2015    source источник


Ответы (2)


Ответ

public class MainActivity extends AppCompatActivity {
    @OnClick(R.id.button) //ButterKnife
    public void onClickButton() {
        logger.log("Hello!");
    }

    @Inject
    Logger logger;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Injector.INSTANCE.getApplicationComponent().inject(this);
        ButterKnife.bind(this);
    }

    @Override
    protected void onDestroy() {
        ButterKnife.unbind(this);
        super.onDestroy();
    }
}

public class Logger {
    private static int i = 0;

    private CustomApplication customApplication;

    public Logger(CustomApplication application) {
        this.customApplication = application;
        i++;
    }

    public static int getI() {
        return i;
    }

    public void log(String text){
        Toast.makeText(customApplication, text + " " + i,Toast.LENGTH_SHORT).show();
    }
}


public interface LoggerComponent {
    Logger logger();
}

@Module
public class ApplicationModule {
    private CustomApplication customApplication;

    public ApplicationModule(CustomApplication customApplication) {
        this.customApplication = customApplication;
    }

    @Provides
    public CustomApplication customApplication() {
        return customApplication;
    }
}

@Module
public class LoggerModule {
    @Provides
    @Singleton
    Logger provideLogger(){
        return new Logger();
    }
}


@Singleton
@Component(modules={LoggerModule.class, ApplicationModule.class})
public interface ApplicationComponent extends LoggerComponent {
    CustomApplication customApplication();

    void inject(MainActivity mainActivity);
}

public class CustomApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Injector.INSTANCE.initializeApplicationComponent(this);
    }
}

public enum Injector {
    INSTANCE;

    private ApplicationComponent applicationComponent;

    public ApplicationComponent getApplicationComponent() {
        return applicationComponent;
    }

    void initializeApplicationComponent(CustomApplication customApplication) {
        this.applicationComponent = DaggerApplicationComponent.builder()
            .applicationModule(new ApplicationModule(customApplication))
            .build();
    }
}

В настоящее время это наша архитектура Dagger2.< /а>

РЕДАКТИРОВАТЬ: Это из нашего фактического кода для дооснащения из нашего приложения, которое мы делаем:

public interface RecordingService {    
    ScheduledRecordsXML getScheduledRecords(long userId)
            throws ServerErrorException;
}

public class RecordingServiceImpl
        implements RecordingService {

    private static final String TAG = RecordingServiceImpl.class.getSimpleName();

    private RetrofitRecordingService retrofitRecordingService;

    public RecordingServiceImpl(RetrofitRecordingService retrofitRecordingService) {
        this.retrofitRecordingService = retrofitRecordingService;
    }

    @Override
    public ScheduledRecordsXML getScheduledRecords(long userId)
            throws ServerErrorException {
        try {
            return retrofitRecordingService.getScheduledPrograms(String.valueOf(userId));
        } catch(RetrofitError retrofitError) {
            Log.e(TAG, "Error occurred in downloading XML file.", retrofitError);
            throw new ServerErrorException(retrofitError);
        }
    }
}

@Module
public class NetworkClientModule {
    @Provides
    @Singleton
    public OkHttpClient okHttpClient() {
        OkHttpClient okHttpClient = new OkHttpClient();
        okHttpClient.interceptors().add(new HeaderInterceptor());
        return okHttpClient;
    }
}

@Module(includes = {NetworkClientModule.class})
public class ServiceModule {
    @Provides
    @Singleton
    public RecordingService recordingService(OkHttpClient okHttpClient, Persister persister, AppConfig appConfig) {
        return new RecordingServiceImpl(
                new RestAdapter.Builder().setEndpoint(appConfig.getServerEndpoint())
                        .setConverter(new SimpleXMLConverter(persister))
                        .setClient(new OkClient(okHttpClient))
                        .setLogLevel(RestAdapter.LogLevel.NONE)
                        .build()
                        .create(RetrofitRecordingService.class));
    }

    //...
}

public interface RetrofitRecordingService {
    @GET("/getScheduledPrograms")
    ScheduledRecordsXML getScheduledPrograms(@Query("UserID") String userId);
}

public interface ServiceComponent {
    RecordingService RecordingService();

    //...
}

public interface AppDomainComponent
        extends InteractorComponent, ServiceComponent, ManagerComponent, ParserComponent {
}

@Singleton
@Component(modules = {
        //...
        InteractorModule.class,
        ManagerModule.class,
        ServiceModule.class,
        ParserModule.class
    //...
})
public interface ApplicationComponent
        extends AppContextComponent, AppDataComponent, AppDomainComponent, AppUtilsComponent, AppPresentationComponent {
    void inject(DashboardActivity dashboardActivity);
    //...
}
person EpicPandaForce    schedule 28.08.2015
comment
Отличный ответ, спасибо. Есть ли какая-нибудь ссылка, как это сделать, или где-нибудь прочитать о том, как все это работает? Я хотел бы понять это лучше - person Greyshack; 29.08.2015
comment
Честно говоря, ссылка — это официальная документация, которая просто ужасна, я узнал об этом из учебника, примеров кода, моего предыдущего использования Dagger1 (на основе Android Bootstrap) и использования его в проекте, который мы делаем на работе (и вычисление например, вам нужно предоставить область для методов поставщика модуля, а также получить поставщиков с ограниченной областью действия). Кроме этого, проверьте тег Dagger-2 здесь, в Stack Overflow, я ответил на целую кучу вещей, основываясь на своем опыте работы с ним до сих пор, хотя я не писал о правильном способе выполнения издеваюсь еще. - person EpicPandaForce; 29.08.2015
comment
Я видел ваши ответы, и они действительно полезны, обязательно проверю их более подробно, большое спасибо! - person Greyshack; 29.08.2015
comment
Нет проблем :) Просто убедитесь, что вы знаете, что я не официальный или что-то в этом роде, и я узнаю новые вещи о Dagger2 и других вещах по мере нашего продвижения вперед :P Так что всегда будьте критически настроены. Я много думал о том, как сделать правильное издевательство, но я бы не хотел параметризовать каждый модуль с конкретным экземпляром поставщика для тестирования и производства (вы не можете расширять модули.. -_-).. .. так что да, если вы когда-нибудь поймете, как правильно издеваться, дайте мне знать! Вероятно, это как-то связано с наследованием методов провайдера и инжектора для реального и тестового компонентов. - person EpicPandaForce; 29.08.2015
comment
Правильно ли я понимаю, что для каждой активности мне нужно добавить дополнительный метод внедрения в ApplicationComponent? Также, если бы я хотел использовать Retrofit, достаточно ли добавить метод предоставления в модуль приложения и метод get в какой-либо компонент, либо в компонент приложения, либо в новый, но тогда ApplicationComponent необходимо будет расширить новый как Что ж. Это правильно? - person Greyshack; 29.08.2015
comment
Технически мы делаем так, что у нас есть интерфейс ___Component для каждого модуля, но на самом деле это просто интерфейсы для хранения методов предоставления. Они не являются реальными компонентами. В нашем есть только один компонент, но вы также можете добавить компоненты с подобластями, если хотите (например, компонент для активности и иметь свои собственные модули с областью действия). Итак, быстрые ответы, I need to add an additional inject method to the ApplicationComponent да, во второй половине вам просто нужно добавить метод поставщика в модуль (не забывайте о области действия) и предоставить компоненту. Да - person EpicPandaForce; 29.08.2015
comment
В любом случае, я добавил некоторый код из нашего приложения, чтобы показать, что мы сделали для вещей, связанных с дооснащением. - person EpicPandaForce; 29.08.2015
comment
Спасибо, это помогает! Ты замечательный :) - person Greyshack; 29.08.2015
comment
Также об этом классе enum Injector, он мог бы быть обычным одноэлементным классом, верно? Почему вы выбрали перечисление? - person Greyshack; 29.08.2015
comment
Рад, что помог! :) Технически я использую enum singletons, потому что stackoverflow.com/a/26292956/2413303 - person EpicPandaForce; 29.08.2015
comment
Но да, я на самом деле почти уверен, чем больше я об этом думаю, что правильный способ разрешить насмешку — это наследовать методы предоставления и методы ввода из другого интерфейса в ApplicationComponent, а затем компонент в Injector можно было бы заменить на компонент. в котором указаны правильные модули, а не только конкретный ApplicationComponent. Либо это, либо интерфейс провайдера для модулей, но мне такой подход не нравится. Как я уже сказал, мы этим не занимались, но это проблема, которую нам придется решать, и я думаю, что это и будет решением. - person EpicPandaForce; 29.08.2015
comment
Честно говоря, я не уверен, что понимаю, что вы имеете в виду, возможно, слишком продвинутый на данный момент: D У меня есть еще один вопрос, возможно ли внедрить в мой пример контекст класса регистратора, используя аннотацию @Inject? Мне удалось добиться этого, добавив параметр Context в метод LoggerModule ProvideLogger, а затем добавив Context в конструктор Logger. Но это много ручной работы. - person Greyshack; 29.08.2015
comment
using @Inject annotation да, но вы должны добавить inject(Logger logger); к ApplicationComponent, а затем вызвать Injector.INSTANCE.getApplicationComponent().inject(this); в конструкторе Logger. - person EpicPandaForce; 29.08.2015
comment
Ага я так и сделал. В чем разница между этим и передачей зависимостей через конструктор? Какой из них вы используете? - person Greyshack; 29.08.2015
comment
Если вы посмотрите на внедрение конструктора и внедрение поля, вы увидите, что это очень горячая тема с множеством аргументов для каждой стороны, но лично я предпочитаю внедрение поля, потому что это то, к чему я привык при использовании Spring Framework. Если есть один вопиющий недостаток внедрения полей, так это то, что вам нужно указать класс реализации в списке внедрения, что немного усложняет перенос кода из одного места в другое (мне нужно было подмножество проекта, и я подумал: тьфу), но я все же предпочитаю поле конструктору, потому что это делает модули и классы более чистыми. Личное мнение. - person EpicPandaForce; 29.08.2015
comment
Ну, я написал о теоретических решениях mocking с Dagger2 на данный момент: - person EpicPandaForce; 30.08.2015
comment
Разве нельзя создать интерфейс для издевательской службы и иметь две реализации: производство и макет, а затем в модуле есть два метода предоставления, один из которых возвращает макет, а другой - оригинал с аннотацией @Named, чтобы мы могли внедрить один мы хотим позже в деятельность? Также я не могу заставить работать аннотацию Named, я только добавил ее в класс модуля к методу предоставления, а затем в действии для ввода аннотации, и она не компилируется. - person Greyshack; 31.08.2015
comment
Это не имеет смысла, но сработает, если я добавлю ту же аннотацию @Named к методу компонента. Это отстой, потому что иначе теперь мне пришлось бы создавать два метода в компоненте. Думаю, я могу просто переключиться на единственный метод предоставления, если я хочу использовать Mock, я просто возвращаю новый Mock, а в противном случае возвращаю новый производственный сервис. - person Greyshack; 31.08.2015
comment
Да, это тоже работает... Я просто не хотел добавлять макеты к продакшену! Тем более, что позже это просто не работает; необходимость случайного добавления аннотаций @Named() в код. - person EpicPandaForce; 31.08.2015

Должен ли я вызывать DaggerLoggerComponent в каждом классе, в котором я хочу получить некоторые компоненты, такие как мой класс Logger?

Да для всех классов, созданных системой, таких как Application, Activity и Service. но для ваших собственных классов вам это не нужно. просто аннотируйте свой конструктор с помощью @inject, и кинжал предоставит ваши зависимости.

Также как я могу сделать область класса Logger одноэлементной? Прямо сейчас каждое нажатие кнопки создает новый экземпляр регистратора.

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

    @Inject
    Logger logger;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        LoggerComponent component = DaggerLoggerComponent.builder().loggerModule(new LoggerModule()).build();

        component.inject(this);

        init();
    }

Затем вы можете получить доступ к своему объекту без ссылки на компонент:

private void init(){
        button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                logger.log("Hello!",MainActivity.this);
            }
        });
    }

Подводя итог: вы должны инициализировать компонент во всех классах, которые используют инъекции полей.

ОБНОВЛЕНИЕ: чтобы сделать фактическую инъекцию, вы должны объявить метод inject() в своем компоненте, и кинжал автоматически его реализует. Этот метод позаботится о предоставлении любого объекта, аннотированного @Inject.

person sudanix    schedule 28.08.2015
comment
Но можно ли как-то обернуть созданные системой классы кинжалом? Я где-то видел это, расширяя класс приложения и т. д., но я не могу найти хороший пример. Вы знаете, как это сделать? Потому что, если я хочу иметь только один экземпляр данной службы, я не могу создавать DaggerLoggerComponent в каждом действии, которое я делаю. Я думаю, что действия должны управляться в каком-то контейнере. - person Greyshack; 28.08.2015
comment
Также ваш код не работает. @Inject Logger — это нулевой указатель, вы уверены, что этого достаточно, чтобы создать локальный LoggerComponent в onCreate? - person Greyshack; 29.08.2015
comment
Я обновляю ответ. Я забыл вызвать метод инъекции для компонента. Интерфейс вашего компонента должен иметь метод инъекции (активность MainActivity). - person sudanix; 29.08.2015
comment
Да, это сработало. Все равно мне это не очень нравится. Компонент регистратора тесно связан с MainActivity. Я действительно не вижу здесь выгоды от использования этой библиотеки... похоже, что для выполнения простых вещей требуется много работы. - person Greyshack; 29.08.2015
comment
На самом деле, ваша реальная забота должна заключаться в том, что область действия компонента привязана к экземпляру компонента, а это означает, что ваш одноэлементный регистратор будет убит и воссоздан вместе с активностью, содержащей компонент. - person EpicPandaForce; 29.08.2015