Retrofit: Rediriger vers LoginActivity si le code de réponse est 401

Comment démarrer LoginActivity partir de l’intercepteur (classe de non-activité)? J’ai essayé le code ( Interceptor ) ci-dessous mais je ne travaille pas pour moi.

Intercepteur

 OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request newRequest = chain.request().newBuilder() .addHeader("Authorization", "Bearer " + auth_token_ssortingng) .build(); Response response = chain.proceed(newRequest); Log.d("MyApp", "Code : "+response.code()); if (response.code() == 401){ Intent intent = new Intent(SplashActivity.getContextOfApplication(), LoginActivity.class); startActivity(intent); finish(); //Not working return response; } return chain.proceed(newRequest); } }).build(); 

C’est la solution actuelle que j’utilise. Existe-t-il une meilleure solution? Cette solution doit être répétée à chaque appel de l’API.

Activité principale

 call.enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { if(response.isSuccessful()) { //success } else { Intent intent = new Intent(MainActivity.this.getApplicationContext(), LoginActivity.class); startActivity(intent); finish(); } } @Override public void onFailure(Call call, Throwable t) { } }); 

Envisagez d’introduire une implémentation personnalisée de l’interface retrofit2.Callback , par exemple BaseCallback :

 public abstract class BaseCallback implements Callback { private final Context context; public BaseCallback(Context context) { this.context = context; } @Override public void onResponse(Call call, Response response) { if (response.code() == 401) { // launch login activity using `this.context` } else { onSuccess(response.body()); } } @Override public void onFailure(Call call, Throwable t) { } abstract void onSuccess(T response); } 

Maintenant, depuis le site de l’appelant, vous devez modifier le new Callback avec le new BaseCallback :

 call.enqueue(new BaseCallback(context) { @Override void onSuccess(Token response) { // do something with token } }); 

Bien que cette approche ne réponde pas à votre affirmation suivante:

donc je n’ai pas à continuer à répéter le même code pour chaque appel d’api

Néanmoins, je ne peux pas proposer une meilleure approche.

Le moyen le plus simple consiste à injecter un contexte d’activité dans l’instance d’Interceptor. Si vous utilisez des outils DI, comme Dagger2 ou Cure-dents, ce sera très simple. Je recommande d’utiliser un cure-dent)

https://github.com/stephanenicolas/toothpick

Le code le plus proche sera dans kotlin, car c’est mon code standard. Ceux qui pensent que vous devez résoudre votre problème, j’écrirai en Java.

La solution sera comme ça:

 @Qualifier annotation class BackendUrl class ActivityModule(activity: BaseActivity): Module() { init { bind(Activity::class.java).toInstance(activity) } } class NetworkModule: Module() { init { bind(Ssortingng::class.java).withName(BackendUrl::class.java).toInstance(Constants.URL) bind(Gson::class.java).toInstance(GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create()) bind(CacheHolder::class.java).toProvider(CacheProvider::class.java).singletonInScope() bind(OkHttpClient::class.java).toProvider(OkHttpProvider::class.java).instancesInScope() bind(BackendApi::class.java).toProvider(BackendApiProvider::class.java).instancesInScope() bind(RedirectInterceptor::class.java).to(RedirectInterceptor::class.java) } } 

Que vous devez créer des fournisseurs pour la dépendance à l’injection

 class BackendApiProvider @Inject constructor( private val okHttpClient: OkHttpClient, private val gson: Gson, @BackendUrl private val serverPath: Ssortingng ) : Provider { override fun get() = Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .client(okHttpClient) .baseUrl(serverPath) .build() .create(BackendApi::class.java) } 

Et votre intercepteur de redirection:

 public class RedirectInterceptor implements Interceptor { private final Context context; @Inject public RedirectInterceptor(Activity context) { this.context = context; } @Override public Response intercept(Chain chain) throws IOException { Request newRequest = chain.request().newBuilder() .build(); Response response = chain.proceed(newRequest); Log.d("MyApp", "Code : "+response.code()); if (response.code() == 401){ Intent intent = new Intent(context, LoginActivity.class); context.startActivity(intent); ((Activity) context).finish(); return response; } return chain.proceed(newRequest); } } 

Oh oui. Pour l’autorisation, il sera préférable de créer une nouvelle instance d’un autre intercepteur.

 class HeaderInterceptor(private val token: Ssortingng?) : Interceptor { @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() val newRequest = request.newBuilder() Log.d(TAG, "token: $token") if (token != null && token.isNotBlank()) { newRequest.addHeader("Authorization", "Bearer $token") } return chain.proceed(newRequest.build()) } companion object { private val TAG = HeaderInterceptor::class.java.toSsortingng() } } 

Et votre OkhttpProvder

 class OkHttpProvider @Inject constructor(cacheHolder: CacheHolder, prefs: IPreferences, redirectInterceptor: RedirectInterceptor) : Provider { private val client: OkHttpClient init { val builder = OkHttpClient.Builder() builder .addNetworkInterceptor(redirectInterceptor) .addNetworkInterceptor(HeaderInterceptor(prefs.getAuthToken())) .readTimeout(30, TimeUnit.SECONDS) .cache(cacheHolder.okHttpCache) client = builder.build() } override fun get() = client } 

C’est tout! Maintenant, il vous suffit de placer vos modules dans les bonnes scopes.

 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.init_view) Toothpick.openScopes("activity scope").apply { installModules(ActivityModule(this@YourActivity)) Toothpick.inject(this@YourActivity, this) } Toothpick.openScopes("activity scope", "network scope").apply { installModules(NetworkModule()) } // your activity code } 

Personnellement, je voudrais suggérer l’utilisation du modèle de bus d’événements ici. Vous pouvez utiliser l’ implémentation de greenrobot ou ce que vous voulez, car il s’agit davantage d’une approche d’architecture que d’une implémentation concrète.

  1. Créer un modèle d’événement

     public class UnauthorizedEvent { private static final UnauthorizedEvent INSTANCE = new UnauthorizedEvent(); public static UnauthorizedEvent instance() { return INSTANCE; } private UnauthorizedEvent() { } } 
  2. Implémenter un Interceptor personnalisé qui décompose un événement à propos de demandes non autorisées

     class UnauthorizedInterceptor implements Interceptor { @Override public Response intercept(@NonNull Chain chain) throws IOException { Response response = chain.proceed(chain.request()); if (response.code() == 401) { EventBus.getDefault().post(UnauthorizedEvent.instance()); } return response; } } 
  3. Créer BaseActivity classe BaseActivity qui gère UnauthorizedEvent

     public class BaseActivity extends Activity { @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); } @Subscribe public final void onUnauthorizedEvent(UnauthorizedEvent e) { handleUnauthorizedEvent(); } protected void handleUnauthorizedEvent() { Intent intent = new Intent(this, LoginActivity.class); startActivity(intent); } } 
  4. Empêcher le lancement de LoginActivity partir de LoginActivity

     public class LoginActivty extends BaseActivity { @Override protected void handleUnauthorizedEvent() { //Don't handle unauthorized event } } 

    Une autre approche consiste à ne pas étendre BaseActivity ici.

  5. Enregistrez votre intercepteur

     OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(new UnauthorizedInterceptor()) .build(); 

Avantages:

  • Couplage lâche entre les composants
  • Étendre facilement la logique en surchargeant handleUnauthorizedEvent
  • Pas besoin de réécrire le code pour utiliser de nouveaux types de callbacks
  • Réduisez le facteur humain lié aux erreurs (utilisez Callback au lieu de BaseCallback )

Les inconvénients:

  • Le modèle EventBus rend le débogage plus compliqué
  • Une dépendance supplémentaire ou une propre implémentation qui apporte un nouveau code au projet

Notez également que cet exemple ne couvre pas les problèmes de multithreading. Cela résout votre problème de traitement des demandes non autorisées. Ainsi, si deux demandes reçoivent 401 demandes, il est possible que 2 instances de LoginActivity soient démarrées.

Solution généralisée: Vous pouvez le résoudre en généralisant le traitement des erreurs. Vous pouvez utiliser CallAdapterFactory personnalisé pour le générateur Retrofit. S’il vous plaît se référer ci-dessous classes:

RxErrorHandlingCallAdapterFactory:

 public class RxErrorHandlingCallAdapterFactory extends CallAdapter.Factory { private static Context mContext = null; private final RxJava2CallAdapterFactory original; private RxErrorHandlingCallAdapterFactory() { original = RxJava2CallAdapterFactory.create(); } public static CallAdapter.Factory create(Context context) { mContext = context; return new RxErrorHandlingCallAdapterFactory(); } @Override public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) { return new RxCallAdapterWrapper(retrofit, original.get(returnType, annotations, retrofit)); } private static class RxCallAdapterWrapper implements CallAdapter { private final Retrofit retrofit; private final CallAdapter wrapped; public RxCallAdapterWrapper(Retrofit retrofit, CallAdapter wrapped) { this.retrofit = retrofit; this.wrapped = wrapped; } @Override public Type responseType() { return wrapped.responseType(); } @Override public Object adapt(Call call) { Object result = wrapped.adapt(call); if (result instanceof Single) { return ((Single) result).onErrorResumeNext(new Function() { @Override public SingleSource apply(@NonNull Throwable throwable) throws Exception { return Single.error(asRetrofitException(throwable)); } }); } if (result instanceof Observable) { return ((Observable) result).onErrorResumeNext(new Function() { @Override public ObservableSource apply(@NonNull Throwable throwable) throws Exception { return Observable.error(asRetrofitException(throwable)); } }); } if (result instanceof Completable) { return ((Completable) result).onErrorResumeNext(new Function() { @Override public CompletableSource apply(@NonNull Throwable throwable) throws Exception { return Completable.error(asRetrofitException(throwable)); } }); } return result; } private RetrofitException asRetrofitException(Throwable throwable) { // We had non-200 http error Log.v("log", "eror"); throwable.printStackTrace(); if (throwable instanceof HttpException) { HttpException httpException = (HttpException) throwable; final Response response = httpException.response(); //if ((mContext instanceof Activity)) { Ssortingng s = "Something went wrong."; //mContext.getSsortingng(R.ssortingng.something_went_wrong); try { s = new JSONObject(response.errorBody().ssortingng()).getSsortingng("message"); if (response.code() == 401) { // 401 Unauthorized Intent intent = new Intent(mContext, LoginActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); mContext.startActivity(intent); } } catch (JSONException | IOException e) { e.printStackTrace(); } return RetrofitException.unexpectedError(s, response, retrofit); //showErrorDialog(mContext, response); //} // return RetrofitException.httpError(response.errorBody().toSsortingng(), response, retrofit); } // A network error happened if (throwable instanceof IOException) { return RetrofitException.networkError((IOException) throwable); } // We don't know what happened. We need to simply convert to an unknown error return RetrofitException.unexpectedError(throwable); } } } 

RetrofitException:

 public class RetrofitException extends RuntimeException { private final Ssortingng url; private final Response response; private final Kind kind; private final Retrofit retrofit; RetrofitException(Ssortingng message, Ssortingng url, Response response, Kind kind, Throwable exception, Retrofit retrofit) { super(message, exception); this.url = url; this.response = response; this.kind = kind; this.retrofit = retrofit; } public static RetrofitException httpError(Ssortingng url, Response response, Retrofit retrofit) { Ssortingng message = response.code() + " " + response.message(); return new RetrofitException(message, url, response, Kind.HTTP, null, retrofit); } public static RetrofitException networkError(IOException exception) { return new RetrofitException(exception.getMessage(), null, null, Kind.NETWORK, exception, null); } public static RetrofitException unexpectedError(Throwable exception) { return new RetrofitException(exception.getMessage(), null, null, Kind.UNEXPECTED, exception, null); } public static RetrofitException unexpectedError(Ssortingng s, Response response, Retrofit retrofit) { return new RetrofitException(s, null, null, Kind.UNEXPECTED, null, null); } /** * The request URL which produced the error. */ public Ssortingng getUrl() { return url; } /** * Response object containing status code, headers, body, etc. */ public Response getResponse() { return response; } /** * The event kind which sortingggered this error. */ public Kind getKind() { return kind; } /** * The Retrofit this request was executed on */ public Retrofit getRetrofit() { return retrofit; } /** * HTTP response body converted to specified {@code type}. {@code null} if there is no * response. * * @throws IOException if unable to convert the body to the specified {@code type}. */ public  T getErrorBodyAs(Class type) throws IOException { if (response == null || response.errorBody() == null) { return null; } Converter converter = retrofit.responseBodyConverter(type, new Annotation[0]); return converter.convert(response.errorBody()); } /** * Identifies the event kind which sortingggered a {@link RetrofitException}. */ public enum Kind { /** * An {@link IOException} occurred while communicating to the server. */ NETWORK, /** * A non-200 HTTP status code was received from the server. */ HTTP, /** * An internal error occurred while attempting to execute a request. It is best practice to * re-throw this exception so your application crashes. */ UNEXPECTED } } 

Constructeur de rénovation:

 Retrofit retrofit = new Retrofit.Builder() .addCallAdapterFactory(RxErrorHandlingCallAdapterFactory.create(context)) .addConverterFactory(GsonConverterFactory.create()) .baseUrl(API_URL) .client(client) .build(); 

Vous pouvez gérer 401 dans RxErrorHandlingCallAdapterFactory et d’autres erreurs via Throwable .

Utilisez la méthode autheticator () de OkHttpClient.Builder.

Ce qui réessayera automatiquement pour une demande non autorisée, quand une réponse est 401.

 OkHttpClient client = new OkHttpClient.Builder().authenticator(new Authenticator() { @Override public Request authenticate(Route route, Response response) throws IOException { // Handle 401 error code if (response.code() == 401) { Intent intent = new Intent(MainActivity.this.getApplicationContext(), LoginActivity.class); startActivity(intent); finish(); return null; } return null; } }