Кинжал 2, вводящий несколько экземпляров одного и того же типа объекта

Фон

Я конвертирую свое приложение в архитектуру MVP и обнаружил, что Dagger 2 полезен для внедрения зависимостей, когда это необходимо. Мое приложение должно взаимодействовать с двумя веб-API (моим собственным и сторонним API). Могут быть случаи, когда запросы к моему собственному api и стороннему api могут запускаться одновременно. Я использую Retrofit для связи с этими API и GSON для сериализации / десериализации.

Что я делал раньше

Я создал два Retrofit RestAdapter и использовал шаблон Service Locator, чтобы получить их при необходимости. RestAdapter, предназначенный для использования в моем собственном api, включает GSONConverter с некоторыми настраиваемыми TypeAdapter, поскольку я не хочу десериализации 1: 1 JSON моего ответа в приложении. Другой RestAdapter предназначен для стороннего API и использует другой GSONConverter с определенной политикой именования полей.

Проблема

Я пытаюсь использовать DI вместо Service Locator для получения моего RestAdapter (и интерфейса API). У меня есть настройка класса NetModule, как показано ниже

@Module
public class NetModule {

    private static final String MY_API_URL = "my_api_url";
    private static final String THIRD_PARTY_API_URL = "third_party_api_url";

    @Provides
    @Singleton
    Cache provideOkHttpCache(Application application) {
        int cacheSize = 10 * 1024 * 1024; // 10 MiB
        return new Cache(application.getCacheDir(), cacheSize);
    }

    @Provides
    @Singleton
    OkHttpClient provideOkHttpClient(Cache cache) {
        OkHttpClient client = new OkHttpClient();
        client.setCache(cache);
        return client;
    }

    @Provides
    @Singleton
    TypeAdapter<MyClass> provideMyAPITypeAdapter() {
        return new TypeAdapter<MyClass>() {
            // implementation ignored
        };
    }

    @Provides
    @Named("myApiGson")
    Gson provideGsonForMyAPI(TypeAdapter<MyClass> adapter) {
        return new GsonBuilder()
                .registerTypeAdapter(MyClass.class, adapter)
                .setDateFormat("yyyy-MM-dd HH:mm:ss")
                .create();
    }

    @Provides
    @Named("thirdPartyApiGson")
    Gson provideGsonForThirdPartyAPI() {
        return new GsonBuilder()
                .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
                .create();
    }

    @Provides
    @Named("myApiRestAdapter")
    RestAdapter provideMyRestAdapter(Gson gson, OkHttpClient okHttpClient) {
       return new RestAdapter.Builder()
                .setEndpoint(MY_API_URL)
                .setConverter(new GsonConverter(gson))
                .setClient(new OkClient(okHttpClient))
                .build();
    }

    @Provides
    @Named("thirdPartyApiRestAdapter")
    RestAdapter provideThirdPartyRestAdapter(Gson gson, OkHttpClient okHttpClient) {
       return new RestAdapter.Builder()
                .setEndpoint(THIRD_PARTY_API_URL)
                .setConverter(new GsonConverter(gson))
                .setClient(new OkClient(okHttpClient))
                .build();
    }

    @Provides
    @Singleton
    MyAPI provideMyAPI(RestAdapter adapter){
        return adapter.create(MyAPI.class);
    }

    @Provides
    @Singleton
    ThirdPartyAPI provideThirdPartyAPI(RestAdapter adapter){
        return adapter.create(ThirdPartyAPI.class);
    }
}

Как вы можете видеть в коде выше, NetModule имеет методы для возврата двух объектов Gson и двух объектов RestAdapter. Мои вопросы:

  1. Как убедиться, что правильные зависимости вводятся при создании конкретных интерфейсов RestAdapter и API? (provideMyRestAdapter() требует, чтобы GSON возвращался из provideGsonForMyAPI(), а provideMyAPI() требует RestAdapter, возвращенного из provideMyRestAdapter().)

  2. Как я могу убедиться, что только два экземпляра RestAdapter (один для моего api и другой для стороннего api) когда-либо создаются в течение срока службы приложения, поскольку создание RestAdapter считается дорогостоящим. Я использую атрибут @Named в методах, возвращающих RestAdapters. Скажем, например, при введении зависимости непосредственно в поле следующим образом: @Inject("myApiRestAdapter") RestAdapter myRestadapter; Dagger 2 будет каждый раз создавать новый RestAdapter или будет использовать созданный ранее (например, @Singleton, но для конкретного объекта)?

Я только начал использовать Dagger 2, и мое понимание того, как им пользоваться, все еще может быть неверным. Пожалуйста, поправьте меня, если я здесь что-то не так делаю. Спасибо, что согласились с этим длинным вопросом.


person Much Overflow    schedule 09.03.2016    source источник


Ответы (2)


Вы уже на полпути к решению. Для завершения решения попробуйте сделать следующее:

@Provides
@Named("myApiRestAdapter")
RestAdapter provideMyRestAdapter(@Named("myApiGson") Gson gson, OkHttpClient okHttpClient) {
   return new RestAdapter.Builder()
            .setEndpoint(MY_API_URL)
            .setConverter(new GsonConverter(gson))
            .setClient(new OkClient(okHttpClient))
            .build();
}

@Provides
@Named("thirdPartyApiRestAdapter")
RestAdapter provideThirdPartyRestAdapter(@Named("thirdPartyApiGson") Gson gson, OkHttpClient okHttpClient) {
   return new RestAdapter.Builder()
            .setEndpoint(THIRD_PARTY_API_URL)
            .setConverter(new GsonConverter(gson))
            .setClient(new OkClient(okHttpClient))
            .build();
}

Чтобы обеспечить создание только двух экземпляров ваших RestAdapter за время существования приложения, аннотируйте оба метода, обеспечивающие RestAdapter, с помощью @Singleton, как вы это делали с другими вашими методами. Что касается вашего другого вопроса, будет ли Dagger 2 создавать новый экземпляр RestAdapter каждый раз, когда он должен его вводить, я думаю, что он делает это именно так, но я не уверен в этом.

Надеюсь это поможет!

person Harry    schedule 09.03.2016
comment
Спасибо @pratt, попробую. У меня есть один вопрос: разве @Singleton не должен создавать только один объект для данного типа класса? В этом случае, если я аннотирую оба моих остальных адаптера как @Singleton (который имеет тип RestAdapter), что произойдет за кулисами? - person Much Overflow; 09.03.2016
comment
В дополнение к аннотации вашего метода как @Singleton, вы также аннотируете его аннотацией @Named, которая сообщит dagger создать два отдельных экземпляра RestAdapter для каждого имени. Обязательно укажите, какой RestAdapter вам нужен, используя @Named, как я показал в ответе выше. - person Harry; 09.03.2016
comment
В этом есть смысл, спасибо. Я попробую это сегодня вечером и вернусь к вам! - person Much Overflow; 09.03.2016

Я увидел эту ветку после того, как опубликовал свой ответ на аналогичный вопрос. Я хотел предоставить ссылку, потому что считаю, что тот же подход может быть полезен в зависимости от вашей ситуации. Это может быть излишним для этого точного вопроса, но я хотел бы поделиться, если это поможет кому-то другому.

https://stackoverflow.com/a/52348744/5046784

Короче говоря, вы можете создавать уникальные интерфейсы / классы для каждого именованного объекта (например, MyApiGson и ThirdPartyApiGson), а затем создавать для них @Provides, а не общий класс Gson. Таким образом, вы можете вводить экземпляры по классу / интерфейсу, а не по имени магической строки, которое вам нужно найти или запомнить. Это немного больше работы, но это помогает, когда у вас есть куча независимых модулей, которые предоставляют разные экземпляры одного и того же класса.

person Justin Fiedler    schedule 15.09.2018