Au fil des ans, les technologies de gestion des requêtes réseau sous Android ont considérablement évolué, passant de HttpClient et HttpURLConnection à des bibliothèques comme Volley et OkHttp, puis à Retrofit. Cependant, une utilisation directe de ces outils peut entraîner une duplication de code répétitive pour chaque requête réseau, ce qui est peu pratique pour les développeurs expérimentés.
Cet article présente une approche d'encapsulation des requêtes réseau en tirant parti des technologies suivantes :
- Retrofit2 : Pour la définition des points d'accès API via des interfaces.
- OkHttp : Comme client HTTP sous-jacent pour Retrofit.
- RxJava : Pour gérer les opérations asynchrones et basées sur les événements.
- Dagger2 : Pour l'injection de dépendances, facilitant le découplage et la gestion des dépendances.
- Modèle MVP (Model-View-Presenter) : Pour une architecture logicielle structurée et testable.
Concepts Clés et Leur Application
1. Retrofit2 : Interface et Configuration
Retrofit2 simplifie la définition des requêtes réseau en utilisant des interfaces Java. Il s'appuie sur OkHttp pour exécuter les requêtes HTTP. La configuration typique implique la spécification d'un client HTTP (OkHttp), d'une URL de base, d'un adaptateur pour les appels RxJava et d'un convertisseur pour la sérialisation/désérialisation des données (par exemple, Gson).
Retrofit.Builder retrofitBuilder = new Retrofit.Builder();
retrofitBuilder.client(okHttpClientInstance)
.baseUrl(ApiService.BASE_URL)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = retrofitBuilder.build();
// Définition de l'interface du service API
public interface ApiService {
String BASE_URL = "https://api.example.com/";
@GET("items/{id}")
Observable<itemresponse> getItem(@Path("id") String itemId);
}
// Création d'une instance du service API
ApiService apiService = retrofit.create(ApiService.class);
</itemresponse>
2. RxJava : Programmation Réactive et Asynchrone
RxJava est une bibliothèque puissante pour la programmation réactive en Java. Elle repose sur le modèle Observateur-Observateur, permettant de gérer des flux d'événements asynchrones. Les concepts clés incluent :
Observable: Émet une séquence d'événements (données, erreurs, achèvement).Observer(ouSubscriber) : Réagit aux événements émis par l'Observable.subscribe(): Établit la connexion entre l'Observable et l'Observer.
RxJava améliore la gestion de l'asynchronisme en évitant les "callbacks hell" grâce à des opérateurs de combinaison et en fournissant des notifications explicites pour l'achèvement (onCompleted()) et les erreurs (onError()).
// Exemple d'utilisation de RxJava
apiService.getItem("123")
.subscribeOn(Schedulers.io()) // Exécuter sur un thread d'IO
.observeOn(AndroidSchedulers.mainThread()) // Observer les résultats sur le thread principal
.subscribe(new Subscriber<itemresponse>() {
@Override
public void onCompleted() {
// L'opération est terminée avec succès
}
@Override
public void onError(Throwable e) {
// Gérer les erreurs
}
@Override
public void onNext(ItemResponse response) {
// Traiter les données reçues
}
});
</itemresponse>
3. Dagger2 : Injection de Dépendances
Dagger2 est un framework d'injection de dépendances qui facilite le découplage des composants logiciels. Il utilise des annotations pour gérer la création et l'injection des objets nécessaires. Les annotations principales sont :
@Inject: Pour demander une injection.@Module: Définit des classes qui fournissent des dépendances.@Provides: Marque des méthodes dans un module qui produisent des instances de dépendances.@Component: Connecte les modules aux classes qui nécessitent des injections.@Scope/@Singleton: Définit la durée de vie des instances injectées.
L'utilisation de Dagger2 améliore la testabilité et la maintenabilité du code en rendant les dépendances explicites et en facilitant le remplacement des implémentations.
4. Modèle MVP : Architecture Applicative
Le modèle MVP sépare l'interface utilisateur (View), la logique métier (Model) et la logique de présentation (Presenter). Le Presenter agit comme intermédiaire, récupérant les données du Model et mettant à jour la View. Cette séparation rend le code plus organisé, plus facile à tester et à maintenir, en particulier dans les applications complexes.
Encapsulation d'un Framework de Requête Réseau
Structure du Projet
Une structure de projet bien organisée est essentielle. On peut distinguer :
data: Contient la logique pour récupérer les données (locale et distatne).di: Gère la configuration de Dagger2, y compris les modules pour OkHttp et Retrofit.network: Définit les interfaces d'API et les gestionnaires de réponses.
Initialisation de Retrofit
Configurer Retrofit avec OkHttp, RxJava et un convertisseur Gson.
@Module
public class NetworkModule {
@Provides
@Singleton
OkHttpClient provideOkHttpClient() {
// Configuration d'OkHttpClient avec des intercepteurs, timeouts, etc.
OkHttpClient.Builder builder = new OkHttpClient.Builder();
// ... configuration ...
return builder.build();
}
@Provides
@Singleton
Retrofit provideRetrofit(OkHttpClient okHttpClient) {
Retrofit.Builder builder = new Retrofit.Builder();
builder.client(okHttpClient)
.baseUrl(ApiService.BASE_URL)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create());
return builder.build();
}
@Provides
@Singleton
ApiService provideApiService(Retrofit retrofit) {
return retrofit.create(ApiService.class);
}
}
Gestion des Réponses API Unifiées
Définir une classe de réponse commune pour gérer les codes d'état et les messages d'erreur de manière standardisée.
public class ApiResponse<t> {
private int statusCode;
private String message;
private T data;
// Getters et Setters
public int getStatusCode() { return statusCode; }
public String getMessage() { return message; }
public T getData() { return data; }
}
</t>
Utiliser une fonction RxJava pour transformer la réponse unifiée en un Observable des données réelles ou en une erreur.
public class ApiErrorHandlingFunc<t> implements Func1<apiresponse>, Observable<t>> {
@Override
public Observable<t> call(ApiResponse<t> response) {
if (response.getStatusCode() != 200) {
return Observable.error(new ApiException(response.getMessage(), response.getStatusCode()));
} else {
return Observable.just(response.getData());
}
}
}
</t></t></t></apiresponse></t>
Gestionnaire de Souscripteurs (Subscriber)
Créer un Subscriber personnalisé pour gérer les callbacks RxJava, afficher des indicateurs de chargement, gérer les erreurs et déclencher des actions après la complétion.
public abstract class BaseSubscriber<t> extends Subscriber<t> {
private final IView mView; // Interface pour interagir avec la vue (Presenter -> View)
private final boolean showLoading;
public BaseSubscriber(IView view, boolean showLoading) {
this.mView = view;
this.showLoading = showLoading;
}
@Override
public void onStart() {
if (showLoading && mView != null) {
mView.showLoadingIndicator();
}
}
@Override
public void onCompleted() {
if (showLoading && mView != null) {
mView.hideLoadingIndicator();
}
}
@Override
public void onError(Throwable e) {
if (showLoading && mView != null) {
mView.hideLoadingIndicator();
}
handleError(e);
if (mView != null) {
mView.onRequestFailed(e.getMessage());
}
}
// Méthode abstraite pour traiter les données
public abstract void onSuccess(T data);
@Override
public void onNext(T t) {
onSuccess(t);
}
private void handleError(Throwable e) {
// Logique de gestion des erreurs (ex: erreurs réseau, erreurs serveur)
if (e instanceof SocketTimeoutException) {
// Gérer timeout
} else if (e instanceof ConnectException) {
// Gérer connexion perdue
} else if (e instanceof ApiException) {
// Gérer erreurs API spécifiques
ApiException apiException = (ApiException) e;
// Afficher message d'erreur selon apiException.getStatusCode()
} else {
// Erreur générique
}
}
}
</t></t>
Intercepteurs OkHttp
Utiliser des intercepteurs OkHttp pour ajouter des en-têtes communs, des paramètres de requête (comme les signatures, les timestamps) ou pour le logging.
public class CommonParamsInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Request.Builder requestBuilder = originalRequest.newBuilder();
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String signature = generateSignature(originalRequest, timestamp);
HttpUrl.Builder urlBuilder = originalRequest.url().newBuilder();
if (originalRequest.method().equals("GET")) {
urlBuilder.addQueryParameter("timestamp", timestamp);
urlBuilder.addQueryParameter("signature", signature);
} else if (originalRequest.method().equals("POST")) {
// Pour POST, les paramètres peuvent être dans le corps.
// Il faut gérer la modification du corps si nécessaire.
// L'exemple original modifiait le FormBody.
// Ici, on ajoute potentiellement des en-têtes.
requestBuilder.addHeader("X-Timestamp", timestamp);
requestBuilder.addHeader("X-Signature", signature);
}
Request newRequest = requestBuilder.url(urlBuilder.build()).build();
return chain.proceed(newRequest);
}
private String generateSignature(Request request, String timestamp) {
// Logique de génération de signature personnalisée
// Cela dépend de la manière dont la signature doit être calculée
return "dummy_signature";
}
}
Lancement des Requêtes
Dans le Presenter, appeler la méthode du service API et souscrire au Observable résultant en utilisant le BaseSubscriber personnalisé.
public class LoginPresenter {
private final ApiService apiService;
private final ILoginView loginView; // Interface pour la vue de connexion
public LoginPresenter(ApiService apiService, ILoginView loginView) {
this.apiService = apiService;
this.loginView = loginView;
}
public void attemptLogin(String username, String password) {
apiService.login(username, password)
.map(new ApiErrorHandlingFunc<>()) // Gérer la réponse unifiée
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new BaseSubscriber<user>(loginView, true) { // true pour afficher le chargement
@Override
public void onSuccess(User user) {
loginView.onLoginSuccess(user);
}
@Override
public void onRequestFailed(String message) {
// La méthode onError de BaseSubscriber a déjà appelé cette méthode
// ici on peut ajouter des logiques spécifiques au succès partiel ou autre
}
});
}
}
</user>
Intégration avec Dagger2
Configurer Dagger2 pour fournir les instances nécessaires comme ApiService, OkHttpClient, et injecter ces dépendances dans les Activitys ou Fragments.
@Singleton
@Component(modules = {AppModule.class, NetworkModule.class})
public interface ApiComponent {
ApiService getApiService();
OkHttpClient getOkHttpClient();
// Méthode pour injecter dans une Activity spécifique, par exemple
void inject(LoginActivity activity);
}
// Dans une Activity :
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// Injection des dépendances
((MyApp) getApplication()).getApiComponent().inject(this);
// Initialisation du Presenter avec les dépendances injectées
loginPresenter = new LoginPresenter(apiService, this);
}
Cette approche combinée de Retrofit, RxJava, Dagger2 et MVP permet de construire un système robuste et maintenable pour la gestion des requêtes réseau dans les applications Android.