web-dev-qa-db-ja.com

Android Dagger2 + OkHttp + Retrofit依存サイクルエラー

こんにちは、Dagger2RetrofitOkHttpを使用していますが、依存関係サイクルの問題に直面しています。

OkHttpを提供する場合:

@Provides
@ApplicationScope
OkHttpClient provideOkHttpClient(TokenAuthenticator auth,Dispatcher dispatcher){
    return new OkHttpClient.Builder()
            .connectTimeout(Constants.CONNECT_TIMEOUT, TimeUnit.SECONDS)
            .readTimeout(Constants.READ_TIMEOUT,TimeUnit.SECONDS)
            .writeTimeout(Constants.WRITE_TIMEOUT,TimeUnit.SECONDS)
            .authenticator(auth)
            .dispatcher(dispatcher)
            .build();
}

Retrofitを提供する場合:

@Provides
@ApplicationScope
Retrofit provideRetrofit(Resources resources,Gson gson, OkHttpClient okHttpClient){
    return new Retrofit.Builder()
            .baseUrl(resources.getString(R.string.base_api_url))
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(okHttpClient)
            .build();
}

APIServiceを提供する場合:

@Provides
@ApplicationScope
APIService provideAPI(Retrofit retrofit) {
    return retrofit.create(APIService.class);
}

私のAPIServiceインターフェース:

public interface  APIService {
@FormUrlEncoded
@POST("token")
Observable<Response<UserTokenResponse>> refreshUserToken();

--- other methods like login, register ---

}

私のTokenAuthenticatorクラス:

@Inject
public TokenAuthenticator(APIService mApi,@NonNull ImmediateSchedulerProvider mSchedulerProvider) {
    this.mApi= mApi;
    this.mSchedulerProvider=mSchedulerProvider;
    mDisposables=new CompositeDisposable();
}

@Override
public  Request authenticate(Route route, Response response) throws IOException {

    request = null;

    mApi.refreshUserToken(...)
            .subscribeOn(mSchedulerProvider.io())
            .observeOn(mSchedulerProvider.ui())
            .doOnSubscribe(d -> mDisposables.add(d))
            .subscribe(tokenResponse -> {
                if(tokenResponse.isSuccessful()) {
                    saveUserToken(tokenResponse.body());
                    request = response.request().newBuilder()
                            .header("Authorization", getUserAccessToken())
                            .build();
                } else {
                    logoutUser();
                }
            },error -> {

            },() -> {});

    mDisposables.clear();
    stop();
    return request;

}

私のlogcat:

Error:(55, 16) error: Found a dependency cycle:
com.yasinkacmaz.myapp.service.APIService is injected at com.yasinkacmaz.myapp.darkvane.modules.NetworkModule.provideTokenAuthenticator(…, mApi, …)
com.yasinkacmaz.myapp.service.token.TokenAuthenticator is injected at
com.yasinkacmaz.myapp.darkvane.modules.NetworkModule.provideOkHttpClient(…, tokenAuthenticator, …)
okhttp3.OkHttpClient is injected at
com.yasinkacmaz.myapp.darkvane.modules.NetworkModule.provideRetrofit(…, okHttpClient)
retrofit2.Retrofit is injected at
com.yasinkacmaz.myapp.darkvane.modules.NetworkModule.provideAPI(retrofit)
com.yasinkacmaz.myapp.service.APIService is provided at
com.yasinkacmaz.myapp.darkvane.components.ApplicationComponent.exposeAPI()

だから私の質問:私のTokenAuthenticatorクラスはAPIServiceに依存していますが、TokenAuthenticatorを作成するときにAPIServiceを提供する必要があります。これにより、依存サイクルエラーが発生します。どうすればこれを克服できますか?この問題に直面している人はいますか?前もって感謝します。

17
Yasin Kaçmaz

あなたの問題は:

  1. OKHttpClientはオーセンティケーターに依存します
  2. オーセンティケーターは後付けサービスに依存しています
  3. 後付けはOKHttpClientに依存します(ポイント1と同様)

したがって、循環依存関係です。

ここで考えられる1つの解決策は、TokenAuthenticatorAPIServiceHolderではなくAPIServiceに依存することです。次に、TokenAuthenticatorがインスタンス化されているかどうかに関係なく、OKHttpClientを構成するときに、APIServiceを依存関係として提供できます。

非常にシンプルなAPIServiceHolder:

public class APIServiceHolder {

    private APIService apiService;

    @Nullable
    APIService apiService() {
        return apiService;
    }

    void setAPIService(APIService apiService) {
        this.apiService = apiService;
    }
}

次に、TokenAuthenticatorをリファクタリングします。

@Inject
public TokenAuthenticator(@NonNull APIServiceHolder apiServiceHolder, @NonNull ImmediateSchedulerProvider schedulerProvider) {
    this.apiServiceHolder = apiServiceHolder;
    this.schedulerProvider = schedulerProvider;
    this.disposables = new CompositeDisposable();
}

@Override
public  Request authenticate(Route route, Response response) throws IOException {

    if (apiServiceHolder.get() == null) {
         //we cannot answer the challenge as no token service is available

         return null //as per contract of Retrofit Authenticator interface for when unable to contest a challenge
    }    

    request = null;            

    TokenResponse tokenResponse = apiServiceHolder.get().blockingGet()

    if (tokenResponse.isSuccessful()) {
        saveUserToken(tokenResponse.body());
        request = response.request().newBuilder()
                     .header("Authorization", getUserAccessToken())
                     .build();
    } else {
       logoutUser();
    }

    return request;
}

トークンを取得するコードはsynchronousである必要があります。これはAuthenticatorの契約の一部です。 Authenticator内のコードはoffメインスレッドを実行します。

もちろん、同じために@Providesメソッドを書く必要があります:

@Provides
@ApplicationScope
apiServiceHolder() {
    return new APIServiceHolder();
}

そして、プロバイダーメソッドをリファクタリングします。

@Provides
@ApplicationScope
APIService provideAPI(Retrofit retrofit, APIServiceHolder apiServiceHolder) {
    APIService apiService = retrofit.create(APIService.class);
    apiServiceHolder.setAPIService(apiService);
    return apiService;
}

変更可能なグローバル状態は通常、良い考えではないことに注意してください。ただし、パッケージが適切に構成されている場合は、アクセス修飾子を適切に使用して、ホルダーの意図しない使用を回避できる場合があります。

25
David Rawson

Dagger 2のLazyインターフェイスを使用することがここでのソリューションです。 TokenAuthenticatorで_APIService mApi_を_Lazy<APIService> mApiLazyWrapper_に置き換えます

_@Inject
public TokenAuthenticator(Lazy<APIService> mApiLazyWrapper,@NonNull ImmediateSchedulerProvider mSchedulerProvider) {
    this.mApiLazyWrapper= mApiLazyWrapper;
    this.mSchedulerProvider=mSchedulerProvider;
    mDisposables=new CompositeDisposable();
}
_

そして、ラッパーからAPIServiceインスタンスを取得するには、mApiLazyWrapper.get()を使用します

mApiLazyWrapper.get()がnullを返す場合、authenticateTokenAuthenticatorメソッドからもnullを返します。

1
Tartar

@Selvinと@Davidに感謝します。私には2つのアプローチがあります。1つは Davidの答え で、もう1つは:

別のOkHttpまたはRetrofitまたはTokenAuthenticatorクラス内の操作を処理する別のライブラリを作成します。

別のOkHttpまたはRetrofitインスタンスを使用する場合は、修飾子アノテーションを使用する必要があります。

例えば ​​:

@Qualifier
public @interface ApiClient {}


@Qualifier
public @interface RefreshTokenClient {}

次に提供:

@Provides
@ApplicationScope
@ApiClient
OkHttpClient provideOkHttpClientForApi(TokenAuthenticator tokenAuthenticator, TokenInterceptor tokenInterceptor, Dispatcher dispatcher){
    return new OkHttpClient.Builder()
            .connectTimeout(Constants.CONNECT_TIMEOUT, TimeUnit.SECONDS)
            .readTimeout(Constants.READ_TIMEOUT,TimeUnit.SECONDS)
            .writeTimeout(Constants.WRITE_TIMEOUT,TimeUnit.SECONDS)
            .authenticator(tokenAuthenticator)
            .addInterceptor(tokenInterceptor)
            .dispatcher(dispatcher)
            .build();
}

@Provides
@ApplicationScope
@RefreshTokenClient
OkHttpClient provideOkHttpClientForRefreshToken(Dispatcher dispatcher){
    return new OkHttpClient.Builder()
            .connectTimeout(Constants.CONNECT_TIMEOUT, TimeUnit.SECONDS)
            .readTimeout(Constants.READ_TIMEOUT,TimeUnit.SECONDS)
            .writeTimeout(Constants.WRITE_TIMEOUT,TimeUnit.SECONDS)
            .dispatcher(dispatcher)
            .build();
}

@Provides
@ApplicationScope
@ApiClient
Retrofit provideRetrofitForApi(Resources resources, Gson gson,@ApiClient OkHttpClient okHttpClient){
    return new Retrofit.Builder()
            .baseUrl(resources.getString(R.string.base_api_url))
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(okHttpClient)
            .build();
}

@Provides
@ApplicationScope
@RefreshTokenClient
Retrofit provideRetrofitForRefreshToken(Resources resources, Gson gson,@RefreshTokenClient OkHttpClient okHttpClient){
    return new Retrofit.Builder()
            .baseUrl(resources.getString(R.string.base_api_url))
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(okHttpClient)
            .build();
}

次に、分離したインターフェースを提供できます。

@Provides
@ApplicationScope
public APIService provideApi(@ApiClient Retrofit retrofit) {
    return retrofit.create(APIService.class);
}

@Provides
@ApplicationScope
public RefreshTokenApi provideRefreshApi(@RefreshTokenClient Retrofit retrofit) {
    return retrofit.create(RefreshTokenApi.class);
}

TokenAuthenticatorを提供する場合:

@Provides
@ApplicationScope
TokenAuthenticator provideTokenAuthenticator(RefreshTokenApi mApi){
    return new TokenAuthenticator(mApi);
}

利点:2つの分離したAPIインターフェースがあるため、それらを個別に保守できます。また、プレーンなOkHttpまたはHttpUrlConnectionまたは別のライブラリを使用することもできます。

短所:2つの異なるOkHttpおよびRetrofitインスタンスが作成されます。

PS:Authenticatorクラス内で同期呼び出しを行うようにしてください。

0
Yasin Kaçmaz

Lazyタイプを介して、サービスの依存関係をオーセンティケーターに注入できます。これにより、インスタンス化への循環的な依存を回避できます。

Lazyがどのように機能するかについては、この link を確認してください。

0
max