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_string)
.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<Token>() {
@Override
public void onResponse(Call<Token> call, Response<Token> response) {
if(response.isSuccessful())
{
//success
}
else
{
Intent intent = new Intent(MainActivity.this.getApplicationContext(), LoginActivity.class);
startActivity(intent);
finish();
}
}
@Override
public void onFailure(Call<Token> call, Throwable t) {
}
});
Pensez à introduire une implémentation personnalisée de l'interface retrofit2.Callback
, par exemple. BaseCallback
:
public abstract class BaseCallback<T> implements Callback<T> {
private final Context context;
public BaseCallback(Context context) {
this.context = context;
}
@Override
public void onResponse(Call<T> call, Response<T> response) {
if (response.code() == 401) {
// launch login activity using `this.context`
} else {
onSuccess(response.body());
}
}
@Override
public void onFailure(Call<T> call, Throwable t) {
}
abstract void onSuccess(T response);
}
Maintenant, depuis le site de l'appelant, vous devriez changer new Callback<Token>
avec new BaseCallback<Token>
:
call.enqueue(new BaseCallback<Token>(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.
Personnellement, je voudrais suggérer l’utilisation du modèle de bus d’événements ici. Vous pouvez utiliser implémentation de greenrobot ou ce que vous voulez, puisqu'il s'agit davantage d'une approche d'architecture que d'une implémentation concrète.
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() {
}
}
Implémentation de la variable personnalisée Interceptor
qui décompresse l'événement concernant les requêtes 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;
}
}
Créez la 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);
}
}
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 prolonger BaseActivity
ici.
Enregistrez votre intercepteur
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new UnauthorizedInterceptor())
.build();
Avantages:
handleUnauthorizedEvent
Callback
au lieu de BaseCallback
)Les inconvénients:
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 requêtes reçoivent 401, il est possible que 2 instances de LoginActivity
soient démarrées.
Solution généralisée: Vous pouvez la résoudre en généralisant le traitement des erreurs. Vous pouvez utiliser CallAdapterFactory personnalisé pour le générateur Retrofit. Veuillez vous référer aux cours ci-dessous:
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<R> implements CallAdapter<R, Object> {
private final Retrofit retrofit;
private final CallAdapter<R,
Object> wrapped;
public RxCallAdapterWrapper(Retrofit retrofit, CallAdapter<R, Object> wrapped) {
this.retrofit = retrofit;
this.wrapped = wrapped;
}
@Override
public Type responseType() {
return wrapped.responseType();
}
@Override
public Object adapt(Call<R> call) {
Object result = wrapped.adapt(call);
if (result instanceof Single) {
return ((Single) result).onErrorResumeNext(new Function<Throwable, SingleSource>() {
@Override
public SingleSource apply(@NonNull Throwable throwable) throws Exception {
return Single.error(asRetrofitException(throwable));
}
});
}
if (result instanceof Observable) {
return ((Observable) result).onErrorResumeNext(new Function<Throwable, ObservableSource>() {
@Override
public ObservableSource apply(@NonNull Throwable throwable) throws Exception {
return Observable.error(asRetrofitException(throwable));
}
});
}
if (result instanceof Completable) {
return ((Completable) result).onErrorResumeNext(new Function<Throwable, CompletableSource>() {
@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)) {
String s = "Something went wrong."; //mContext.getString(R.string.something_went_wrong);
try {
s = new JSONObject(response.errorBody().string()).getString("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().toString(), 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 String url;
private final Response response;
private final Kind kind;
private final Retrofit retrofit;
RetrofitException(String message, String 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(String url, Response response, Retrofit retrofit) {
String 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(String s, Response response, Retrofit retrofit) {
return new RetrofitException(s, null, null, Kind.UNEXPECTED, null, null);
}
/**
* The request URL which produced the error.
*/
public String getUrl() {
return url;
}
/**
* Response object containing status code, headers, body, etc.
*/
public Response getResponse() {
return response;
}
/**
* The event kind which triggered 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> T getErrorBodyAs(Class<T> type) throws IOException {
if (response == null || response.errorBody() == null) {
return null;
}
Converter<ResponseBody, T> converter = retrofit.responseBodyConverter(type, new Annotation[0]);
return converter.convert(response.errorBody());
}
/**
* Identifies the event kind which triggered 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
}
}
Retrofit Builder:
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
.
Le moyen le plus simple consiste à injecter un contexte d'activité dans l'instance d'Interceptor . Si vous utilisez des outils d'identification directe, tels que Dagger2 ou Toothpick, 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 avez besoin de 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(String::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 Providers pour la dépendance à l'injection
class BackendApiProvider @Inject constructor(
private val okHttpClient: OkHttpClient,
private val gson: Gson,
@BackendUrl private val serverPath: String
) : Provider<BackendApi> {
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: String?) : 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.toString()
}
}
Et votre OkhttpProvder
class OkHttpProvider @Inject constructor(cacheHolder: CacheHolder, prefs: IPreferences, redirectInterceptor: RedirectInterceptor) : Provider<OkHttpClient> {
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 portées.
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
}