2017-08-17 6 views
0

J'utilise retorift pour frapper getAricle api et obtenir la liste des articles liés à l'utilisateur. getArticle api lancera erreur si jeton passé est expiré si oui je dois appeler refreshToken api pour obtenir un nouveau jeton puis de nouveau je dois appeler le getArticle apiRxJava: Exécuter les secondes observables seulement si le premier jette une erreur et répéter de la première

ApiController.createRx().getArticle(token) 
      .subscribeOn(Schedulers.io()) 
      .observeOn(AndroidSchedulers.mainThread()) 
      .subscribe({ response -> toast(response.body().url) }, { e -> 
       println(e.printStackTrace()) 
       if(e is HttpException && e.code() in arrayOf(401,403)){      
        //Here I want to call refresh tolken api 
        toast("Auth error") 
       } 
       else 
        toast(R.string.something_went_wrong) 
      }) 

Modifier

Même si les réponses données a montré une certaine direction, mais ceux-ci ne sont pas une réponse directe à ma question. Voici comment résoudre mais je pense que ce peut être refactorisé dans beaucoup meilleur code

ApiController.createRx().getArticle(Preference.getToken()) 
      .flatMap { value -> 
       if (value.code() in arrayOf(403, 401)) { 
        ApiController.refreshToken() 
        ApiController.createRx().getArticle(Preference.getToken()) 
       } else Observable.just(value) 
      } 
      .subscribeOn(Schedulers.io()) 
      .observeOn(AndroidSchedulers.mainThread()) 
      .subscribe({ response -> println("Success") }, { e -> 
       e.printStackTrace() 
       toast(R.string.something_went_wrong) 
      }) 



fun refreshToken() { 
     val token:String?=ApiController.createRx().refreshToken(Preferences.getRefreshToken()).blockingFirst()?.body()?.token 
     if (token != null) Preferences.setAuthToken(token) 
    } 

EDIT

Je refactorisé mon code à un peu plus de version plus propre

Observable.defer { ApiController.createRx().getArticle(Preferences.getToken()) } 
      .flatMap { 
       if (it.code() in arrayOf(401, 403)) { 
        ApiController.refreshToken() 
        Observable.error(Throwable()) 
       } else Observable.just(it) 
      } 
      .retry(1) 
      .subscribeOn(Schedulers.io()) 
      .observeOn(AndroidSchedulers.mainThread()) 
      .subscribe({println("Success") }, { 
       it.printStackTrace() 
       toast(R.string.something_went_wrong) 
      }) 



fun refreshToken() { 
     var token: String? = null 
     try { 
      token = createRx().refreshToken(Preferences.getRefreshToken()).blockingFirst().body()!!.token 
     } catch (e: Exception) { 
      throw e 
     } 
     println("saving token") 
     if (token != null) Preferences.setAuthToken(token) 
    } 

EDIT

S'il vous plaît vérifier ma réponse pour le code refactorisé final

+0

Tout d'abord, l'adaptation ultérieure, vous pouvez éviter d'aller directement à 'onError'. Vous pouvez retourner 'Single >' qui ne retourne jamais d'erreur. Mais si vous voulez continuer à obtenir 'onError', essayez d'utiliser les opérateurs de gestion des erreurs de RxJava [ici] (https://github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators) – masp

+0

@masp Dans le scénario I Je n'arrive pas à comprendre comment rappeler l'api getArticle. Pouvez-vous s'il vous plaît me montrer comment faire cela – Praveen

+0

Avez-vous pu résoudre votre problème? Certaines réponses ont-elles été utiles? Si c'est le cas, pensez à enrichir les réponses utiles et si l'une d'entre elles vous a conduit à une solution, acceptez cette réponse. – theFunkyEngineer

Répondre

0

J'ai implémenté exactement cela. Voici une version légèrement modifiée de ce code:

private Observable<Object> refreshTokenIfNotAuthorized(Observable<? extends Throwable> errors) { 
    final AtomicBoolean alreadyRetried = new AtomicBoolean(false); 

    return errors.flatMap(error -> { 

     boolean isAuthorizationError = /* some logic analyzing each error*/ ; 

     if (isAuthorizationError && !alreadyRetried.get()) { 
      try { 
       alreadyRetried.set(true); 
       String newToken = federatedTokenRefresher.refreshToken() 
                 .toBlocking() 
                 .first(); 

       setLogin(newToken); 
       return Observable.just(null); 

      } catch (Exception e) { 
       return Observable.error(error); 
      } 

     } 
     return Observable.error(error); 
    }); 
} 

Vous pouvez utiliser cette méthode comme ceci:

doSomethingRequiringAuth().retryWhen(this::refreshTokenIfNotAuthorized); 
+0

S'il vous plaît chek mon édition. Merci – Praveen

+0

@Praveen Je suis d'accord que votre solution fonctionne, mais comme vous le dites, pas vraiment élégant. Y a-t-il quelque chose dans ma proposition qui ne répond pas à vos besoins? Si oui, qu'est-ce que c'est? – theFunkyEngineer

+0

au début je pensais que retrofit jetant toutes les erreurs 4xx à 'onError' plus tard, je me suis rendu compte que c'est seulement l'erreur réseau qui allait directement à' onError' alors "retryWhen" ne fonctionnera pas. Je devais utiliser 'flatMap' uniquement. Dans votre code, comment vous désabonner de ce 'federatedTokenRefresher.refreshToken() .toBlocking() .first()' – Praveen

0

Quel genre d'erreur que vous recevrez ?. Il semble que vous pourriez utiliser l'opérateur onErrorResumeNext.

Cet opérateur une fois que recevoir un throwable, vous permettent de retourner un Observable plutôt le throwable dans le onError

@Test 
    public void observableOnErrorResumeException() { 
     Integer[] numbers = {0, 1, 2, 3, 4, 5}; 

     Observable.from(numbers) 
       .doOnNext(number -> { 
        if (number > 3) { 
         try { 
          throw new IllegalArgumentException(); 
         } catch (Exception e) { 
          throw new RuntimeException(e); 
         } 
        } 

       }) 
       .onErrorResumeNext(t -> Observable.just(666)) 
       .subscribe(System.out::println); 

    } 

Vous pouvez voir d'autres exemples ici https://github.com/politrons/reactive/blob/master/src/test/java/rx/observables/errors/ObservableExceptions.java

+0

alors comment répétez-vous après l'erreur de traitement et l'actualisation du jeton – Praveen

+0

Vous pouvez utiliser l'opérateur retryWhen, regardez le reste des exemples que je vous collez dans le lien – paul

+0

S'il vous plaît chek mon édition. Merci – Praveen

0

Je vais vous donner une autre option à l'aide groupBy opérateur

/** 
* In this example we create a response code group. 
*/ 
@Test 
public void testGroupByCode() { 
    Observable.from(Arrays.asList(401,403, 200)) 
      .groupBy(code -> code) 
      .subscribe(groupByCode -> { 
       switch (groupByCode.getKey()) { 
        case 401: { 
         System.out.println("refresh token"); 
         processResponse(groupByCode); 
         break; 
        } 
        case 403: { 
         System.out.println("refresh token"); 
         processResponse(groupByCode); 
         break; 
        } 
        default: { 
         System.out.println("Do the toast"); 
         processResponse(groupByCode); 
        } 
       } 
      }); 
} 

private void processResponse(GroupedObservable<Integer, Integer> groupByCode) { 
    groupByCode.asObservable().subscribe(value -> System.out.println("Response code:" + value)); 
} 
+0

Je ne suis pas capable de comprendre cela. C'est trop générique. Comment appliquer cela à mon scénario et il n'y a pas non plus de 'asObservable' dans Rx java2. Et où essayez-vous l'original observable après avoir rafraîchi le jeton? – Praveen

+0

Jetez un oeil à l'opérateur groupby dans la documentation – paul

+0

Je connais l'opérateur 'groupBy' mais comment l'appliquer à mon scénario – Praveen

0

Je résolu mon problème après avoir lu plus sur RxJava et voici comment je mis en œuvre il. Tout d'abord va retrofit jeter 4xx erreur à onError ou onNext\onSuccess dépend de la façon dont nous le définissons. Ex:

@GET("content") fun getArticle(@Header("Authorization") token: String):Single<Article>

ce jetteront toutes les erreurs 4xx à onError et au lieu de Single<Article> si vous le définir comme Single<Response<Article>> alors toute la réponse du serveur, y compris 4xx ira à onNext\onSuccess

Single.defer { ApiController.createRx().getArticle(Preferences.getAuthToken())} 
       .doOnError { 
        if (it is HttpException && it.code() == 401) 
         ApiController.refreshToken() 
       } 
       .retry { attempts, error -> attempts < 3 && error is HttpException && error.code() == 401 } 
       .subscribeOn(Schedulers.io()) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .subscribe({println("Success") }, { 
        it.printStackTrace() 
        toast(R.string.something_went_wrong) 
       }) 

J'utilise defer comme une enveloppe autour de mes réels Observable parce que je veux recréer l'article aller chercher observable sur réessayer après jeton actualiser parce que je veux Preferences.getAuthToken() pour être appelé à nouveau car mon code de jeton d'actualisation stocke le jeton récupéré de préférence.

retry renvoie true si le HttpException est de 401 et pas essayé une nouvelle tentative plus de 2 fois