# Reseau

<div id="introduction"></div>

## Introduction

Nylo rend le reseau simple. Vous definissez les points d'acces API dans des classes de service qui etendent `NyApiService`, puis vous les appelez depuis vos pages. Le framework gere le decodage JSON, la gestion des erreurs, la mise en cache et la conversion automatique des reponses en classes de modeles (appelee "morphing").

Vos services API se trouvent dans `lib/app/networking/`. Un nouveau projet inclut un `ApiService` par defaut :

```dart
class ApiService extends NyApiService {
  ApiService() : super(decoders: modelDecoders);

  @override
  String get baseUrl => getEnv('API_BASE_URL');

  @override
  Map<Type, Interceptor> get interceptors => {
    ...super.interceptors,
  };

  Future fetchUsers() async {
    return await network(
      request: (request) => request.get("/users"),
    );
  }
}
```

Il existe trois facons d'effectuer des requetes HTTP :

| Approche | Retourne | Ideal pour |
|----------|---------|----------|
| Methodes pratiques (`get`, `post`, etc.) | `T?` | Operations CRUD simples |
| `network()` | `T?` | Requetes necessitant mise en cache, relances ou en-tetes personnalises |
| `networkResponse()` | `NyResponse<T>` | Quand vous avez besoin des codes de statut, en-tetes ou details d'erreur |

En arriere-plan, Nylo utilise <a href="https://pub.dev/packages/dio" target="_BLANK">Dio</a>, un client HTTP puissant.


<div id="convenience-methods"></div>

## Methodes pratiques

`NyApiService` fournit des methodes raccourcies pour les operations HTTP courantes. Elles appellent `network()` en interne.

### Requete GET

```dart
Future<User?> fetchUser(int id) async {
  return await get<User>(
    "/users/$id",
    queryParameters: {"include": "profile"},
  );
}
```

### Requete POST

```dart
Future<User?> createUser(Map<String, dynamic> data) async {
  return await post<User>("/users", data: data);
}
```

### Requete PUT

```dart
Future<User?> updateUser(int id, Map<String, dynamic> data) async {
  return await put<User>("/users/$id", data: data);
}
```

### Requete DELETE

```dart
Future<bool?> deleteUser(int id) async {
  return await delete<bool>("/users/$id");
}
```

### Requete PATCH

```dart
Future<User?> patchUser(int id, Map<String, dynamic> data) async {
  return await patch<User>("/users/$id", data: data);
}
```

### Requete HEAD

Utilisez HEAD pour verifier l'existence d'une ressource ou obtenir les en-tetes sans telecharger le corps :

```dart
Future<bool> checkResourceExists(String url) async {
  Response response = await head(url);
  return response.statusCode == 200;
}
```


<div id="network-helper"></div>

## Helper Network

La methode `network` vous donne plus de controle que les methodes pratiques. Elle retourne directement les donnees transformees (`T?`).

```dart
class ApiService extends NyApiService {
  ...

  Future<User?> fetchUser(int id) async {
    return await network<User>(
      request: (request) => request.get("/users/$id"),
    );
  }

  Future<List<User>?> fetchUsers() async {
    return await network<List<User>>(
      request: (request) => request.get("/users"),
    );
  }

  Future<User?> createUser(Map<String, dynamic> data) async {
    return await network<User>(
      request: (request) => request.post("/users", data: data),
    );
  }
}
```

Le callback `request` recoit une instance <a href="https://pub.dev/packages/dio" target="_BLANK">Dio</a> avec votre URL de base et vos intercepteurs deja configures.

### Parametres de network

| Parametre | Type | Description |
|-----------|------|-------------|
| `request` | `Function(Dio)` | La requete HTTP a effectuer (requis) |
| `bearerToken` | `String?` | Jeton Bearer pour cette requete |
| `baseUrl` | `String?` | Remplacer l'URL de base du service |
| `headers` | `Map<String, dynamic>?` | En-tetes supplementaires |
| `retry` | `int?` | Nombre de tentatives de relance |
| `retryDelay` | `Duration?` | Delai entre les relances |
| `retryIf` | `bool Function(DioException)?` | Condition pour la relance |
| `connectionTimeout` | `Duration?` | Delai de connexion |
| `receiveTimeout` | `Duration?` | Delai de reception |
| `sendTimeout` | `Duration?` | Delai d'envoi |
| `cacheKey` | `String?` | Cle de cache |
| `cacheDuration` | `Duration?` | Duree du cache |
| `cachePolicy` | `CachePolicy?` | Strategie de cache |
| `checkConnectivity` | `bool?` | Verifier la connectivite avant la requete |
| `handleSuccess` | `Function(NyResponse<T>)?` | Callback de succes |
| `handleFailure` | `Function(NyResponse<T>)?` | Callback d'echec |


<div id="network-response-helper"></div>

## Helper networkResponse

Utilisez `networkResponse` lorsque vous avez besoin d'acceder a la reponse complete — codes de statut, en-tetes, messages d'erreur — pas seulement les donnees. Il retourne un `NyResponse<T>` au lieu de `T?`.

Utilisez `networkResponse` lorsque vous devez :
- Verifier les codes de statut HTTP pour un traitement specifique
- Acceder aux en-tetes de la reponse
- Obtenir des messages d'erreur detailles pour le retour utilisateur
- Implementer une logique de gestion d'erreur personnalisee

```dart
Future<NyResponse<User>> fetchUser(int id) async {
  return await networkResponse<User>(
    request: (request) => request.get("/users/$id"),
  );
}
```

Puis utilisez la reponse dans votre page :

```dart
NyResponse<User> response = await _apiService.fetchUser(1);

if (response.isSuccessful) {
  User? user = response.data;
  print('Status: ${response.statusCode}');
} else {
  print('Error: ${response.errorMessage}');
  print('Status: ${response.statusCode}');
}
```

### network vs networkResponse

```dart
// network() — retourne les donnees directement
User? user = await network<User>(
  request: (request) => request.get("/users/1"),
);

// networkResponse() — retourne la reponse complete
NyResponse<User> response = await networkResponse<User>(
  request: (request) => request.get("/users/1"),
);
User? user = response.data;
int? status = response.statusCode;
```

Les deux methodes acceptent les memes parametres. Choisissez `networkResponse` lorsque vous devez inspecter la reponse au-dela des donnees seules.


<div id="ny-response"></div>

## NyResponse

`NyResponse<T>` encapsule la reponse Dio avec les donnees transformees et des aides de statut.

### Proprietes

| Propriete | Type | Description |
|----------|------|-------------|
| `response` | `Response?` | Reponse Dio originale |
| `data` | `T?` | Donnees transformees/decodees |
| `rawData` | `dynamic` | Donnees brutes de la reponse |
| `headers` | `Headers?` | En-tetes de la reponse |
| `statusCode` | `int?` | Code de statut HTTP |
| `statusMessage` | `String?` | Message de statut HTTP |
| `contentType` | `String?` | Type de contenu depuis les en-tetes |
| `errorMessage` | `String?` | Message d'erreur extrait |

### Verifications de statut

| Getter | Description |
|--------|-------------|
| `isSuccessful` | Statut 200-299 |
| `isClientError` | Statut 400-499 |
| `isServerError` | Statut 500-599 |
| `isRedirect` | Statut 300-399 |
| `hasData` | Les donnees ne sont pas null |
| `isUnauthorized` | Statut 401 |
| `isForbidden` | Statut 403 |
| `isNotFound` | Statut 404 |
| `isTimeout` | Statut 408 |
| `isConflict` | Statut 409 |
| `isRateLimited` | Statut 429 |

### Aides pour les donnees

```dart
NyResponse<User> response = await apiService.fetchUser(1);

// Obtenir les donnees ou lancer une exception si null
User user = response.dataOrThrow('User not found');

// Obtenir les donnees ou utiliser un fallback
User user = response.dataOr(User.guest());

// Executer le callback uniquement en cas de succes
String? greeting = response.ifSuccessful((user) => 'Hello ${user.name}');

// Correspondance de pattern sur succes/echec
String result = response.when(
  success: (user) => 'Welcome, ${user.name}!',
  failure: (response) => 'Error: ${response.statusMessage}',
);

// Obtenir un en-tete specifique
String? authHeader = response.getHeader('Authorization');
```


<div id="base-options"></div>

## Options de base

Configurez les options Dio par defaut pour votre service API en utilisant le parametre `baseOptions` :

```dart
class ApiService extends NyApiService {
  ApiService() : super(
    decoders: modelDecoders,
    baseOptions: (BaseOptions baseOptions) {
      return baseOptions
        ..connectTimeout = Duration(seconds: 5)
        ..sendTimeout = Duration(seconds: 5)
        ..receiveTimeout = Duration(seconds: 5);
    },
  );
  ...
}
```

Vous pouvez egalement configurer les options dynamiquement sur une instance :

```dart
apiService.setConnectTimeout(Duration(seconds: 10));
apiService.setReceiveTimeout(Duration(seconds: 30));
apiService.setSendTimeout(Duration(seconds: 10));
apiService.setContentType('application/json');
```

Cliquez <a href="https://pub.dev/packages/dio#request-options" target="_BLANK">ici</a> pour voir toutes les options de base que vous pouvez definir.


<div id="adding-headers"></div>

## Ajouter des en-tetes

### En-tetes par requete

```dart
Future fetchWithHeaders() async => await network(
  request: (request) => request.get("/test"),
  headers: {
    "Authorization": "Bearer aToken123",
    "Device": "iPhone"
  }
);
```

### Jeton Bearer

```dart
Future fetchUser() async => await network(
  request: (request) => request.get("/user"),
  bearerToken: "hello-world-123",
);
```

### En-tetes au niveau du service

```dart
apiService.setHeaders({"X-Custom-Header": "value"});
apiService.setBearerToken("my-token");
```

### Extension RequestHeaders

Le type `RequestHeaders` (un typedef `Map<String, dynamic>`) fournit des methodes d'aide :

```dart
@override
Future<RequestHeaders> setAuthHeaders(RequestHeaders headers) async {
  String? token = Auth.data(field: 'token');
  if (token != null) {
    headers.addBearerToken(token);
  }
  headers.addHeader('X-App-Version', '1.0.0');
  return headers;
}
```

| Methode | Description |
|---------|-------------|
| `addBearerToken(token)` | Definir l'en-tete `Authorization: Bearer` |
| `getBearerToken()` | Lire le jeton Bearer depuis les en-tetes |
| `addHeader(key, value)` | Ajouter un en-tete personnalise |
| `hasHeader(key)` | Verifier si un en-tete existe |


<div id="uploading-files"></div>

## Envoi de fichiers

### Envoi d'un seul fichier

```dart
Future<UploadResponse?> uploadAvatar(String filePath) async {
  return await upload<UploadResponse>(
    '/upload',
    filePath: filePath,
    fieldName: 'avatar',
    additionalFields: {'userId': '123'},
    onProgress: (sent, total) {
      double progress = sent / total * 100;
      print('Progress: ${progress.toStringAsFixed(0)}%');
    },
  );
}
```

### Envoi de plusieurs fichiers

```dart
Future<UploadResponse?> uploadDocuments() async {
  return await uploadMultiple<UploadResponse>(
    '/upload',
    files: {
      'avatar': '/path/to/avatar.jpg',
      'document': '/path/to/doc.pdf',
    },
    additionalFields: {'userId': '123'},
    onProgress: (sent, total) {
      print('Progress: ${(sent / total * 100).toStringAsFixed(0)}%');
    },
  );
}
```


<div id="downloading-files"></div>

## Telechargement de fichiers

```dart
Future<void> downloadFile(String url, String savePath) async {
  await download(
    url,
    savePath: savePath,
    onProgress: (received, total) {
      if (total != -1) {
        print('Progress: ${(received / total * 100).toStringAsFixed(0)}%');
      }
    },
    deleteOnError: true,
  );
}
```


<div id="interceptors"></div>

## Intercepteurs

Les intercepteurs vous permettent de modifier les requetes avant leur envoi, de gerer les reponses et de gerer les erreurs. Ils s'executent sur chaque requete effectuee via le service API.

Utilisez les intercepteurs lorsque vous devez :
- Ajouter des en-tetes d'authentification a toutes les requetes
- Journaliser les requetes et reponses pour le debogage
- Transformer les donnees de requete/reponse globalement
- Gerer des codes d'erreur specifiques (ex. rafraichir les jetons sur 401)

```dart
class ApiService extends NyApiService {
  ApiService() : super(decoders: modelDecoders);

  @override
  Map<Type, Interceptor> get interceptors => {
    ...super.interceptors,
    BearerAuthInterceptor: BearerAuthInterceptor(),
    LoggingInterceptor: LoggingInterceptor(),
  };
  ...
}
```

### Creer un intercepteur personnalise

```bash
metro make:interceptor logging
```

**Fichier :** `app/networking/dio/interceptors/logging_interceptor.dart`

```dart
import 'package:nylo_framework/nylo_framework.dart';

class LoggingInterceptor extends Interceptor {
  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    print('REQUEST[${options.method}] => PATH: ${options.path}');
    return super.onRequest(options, handler);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    print('RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path}');
    handler.next(response);
  }

  @override
  void onError(DioException dioException, ErrorInterceptorHandler handler) {
    print('ERROR[${dioException.response?.statusCode}] => PATH: ${dioException.requestOptions.path}');
    handler.next(dioException);
  }
}
```


<div id="network-logger"></div>

## Journal reseau

Nylo inclut un intercepteur `NetworkLogger` integre. Il est active par defaut lorsque `APP_DEBUG` est `true` dans votre environnement.

### Configuration

```dart
class ApiService extends NyApiService {
  ApiService() : super(
    decoders: modelDecoders,
    useNetworkLogger: true,
    networkLogger: NetworkLogger(
      logLevel: LogLevelType.verbose,
      request: true,
      requestHeader: true,
      requestBody: true,
      responseBody: true,
      responseHeader: false,
      error: true,
    ),
  );
}
```

Vous pouvez le desactiver en definissant `useNetworkLogger: false`.

```
class ApiService extends NyApiService {
  ApiService()
      : super(
          decoders: modelDecoders,
          useNetworkLogger: false, // <-- Desactiver le journal
        );
```

### Niveaux de journalisation

| Niveau | Description |
|--------|-------------|
| `LogLevelType.verbose` | Afficher tous les details de la requete/reponse |
| `LogLevelType.minimal` | Afficher uniquement la methode, l'URL, le statut et le temps |
| `LogLevelType.none` | Aucune sortie de journalisation |

### Filtrage des journaux

```dart
NetworkLogger(
  filter: (options, args) {
    // Journaliser uniquement les requetes vers des points d'acces specifiques
    return options.path.contains('/api/v1');
  },
)
```


<div id="using-an-api-service"></div>

## Utiliser un service API

Il existe deux facons d'appeler votre service API depuis une page.

### Instanciation directe

```dart
class _MyHomePageState extends NyPage<MyHomePage> {

  ApiService _apiService = ApiService();

  @override
  get init => () async {
    List<User>? users = await _apiService.fetchUsers();
    print(users);
  };
}
```

### Utiliser le helper api()

Le helper `api` cree des instances en utilisant vos `apiDecoders` depuis `config/decoders.dart` :

```dart
class _MyHomePageState extends NyPage<MyHomePage> {

  @override
  get init => () async {
    User? user = await api<ApiService>((request) => request.fetchUser());
    print(user);
  };
}
```

Avec des callbacks :

```dart
await api<ApiService>(
  (request) => request.fetchUser(),
  onSuccess: (response, data) {
    // data est l'instance User? transformee
  },
  onError: (DioException dioException) {
    // Gerer l'erreur
  },
);
```

### Parametres du helper api()

| Parametre | Type | Description |
|-----------|------|-------------|
| `request` | `Function(T)` | La fonction de requete API |
| `headers` | `Map<String, dynamic>` | En-tetes supplementaires |
| `bearerToken` | `String?` | Jeton Bearer |
| `baseUrl` | `String?` | Remplacer l'URL de base |
| `page` | `int?` | Page de pagination |
| `perPage` | `int?` | Elements par page |
| `retry` | `int` | Tentatives de relance |
| `retryDelay` | `Duration?` | Delai entre les relances |
| `onSuccess` | `Function(Response, dynamic)?` | Callback de succes |
| `onError` | `Function(DioException)?` | Callback d'erreur |
| `cacheKey` | `String?` | Cle de cache |
| `cacheDuration` | `Duration?` | Duree du cache |


<div id="create-an-api-service"></div>

## Creer un service API

Pour creer un nouveau service API :

```bash
metro make:api_service user
```

Avec un modele :

```bash
metro make:api_service user --model="User"
```

Cela cree un service API avec des methodes CRUD :

```dart
class UserApiService extends NyApiService {
  ...

  Future<List<User>?> fetchAll({dynamic query}) async {
    return await network<List<User>>(
      request: (request) => request.get("/endpoint-path", queryParameters: query),
    );
  }

  Future<User?> find({required int id}) async {
    return await network<User>(
      request: (request) => request.get("/endpoint-path/$id"),
    );
  }

  Future<User?> create({required dynamic data}) async {
    return await network<User>(
      request: (request) => request.post("/endpoint-path", data: data),
    );
  }

  Future<User?> update({dynamic query}) async {
    return await network<User>(
      request: (request) => request.put("/endpoint-path", queryParameters: query),
    );
  }

  Future<bool?> delete({required int id}) async {
    return await network<bool>(
      request: (request) => request.delete("/endpoint-path/$id"),
    );
  }
}
```


<div id="morphing-json-payloads-to-models"></div>

## Transformation du JSON en modeles

Le "morphing" est le terme de Nylo pour la conversion automatique des reponses JSON en classes de modeles Dart. Lorsque vous utilisez `network<User>(...)`, le JSON de la reponse est passe a travers votre decodeur pour creer une instance `User` — aucun parsing manuel necessaire.

```dart
class ApiService extends NyApiService {
  ApiService() : super(decoders: modelDecoders);

  // Retourne un seul User
  Future<User?> fetchUser() async {
    return await network<User>(
      request: (request) => request.get("/user/1"),
    );
  }

  // Retourne une liste de Users
  Future<List<User>?> fetchUsers() async {
    return await network<List<User>>(
      request: (request) => request.get("/users"),
    );
  }
}
```

Les decodeurs sont definis dans `lib/bootstrap/decoders.dart` :

```dart
final Map<Type, dynamic> modelDecoders = {
  User: (data) => User.fromJson(data),

  List<User>: (data) =>
      List.from(data).map((json) => User.fromJson(json)).toList(),
};
```

Le parametre de type que vous passez a `network<T>()` est compare a votre map `modelDecoders` pour trouver le bon decodeur.

**Voir aussi :** [Decoders](/docs/7.x/decoders#model-decoders) pour les details sur l'enregistrement des decodeurs de modeles.


<div id="caching-responses"></div>

## Mise en cache des reponses

Mettez en cache les reponses pour reduire les appels API et ameliorer les performances. La mise en cache est utile pour les donnees qui changent rarement, comme les listes de pays, les categories ou la configuration.

Fournissez une `cacheKey` et une `cacheDuration` optionnelle :

```dart
Future<List<Country>> fetchCountries() async {
  return await network<List<Country>>(
    request: (request) => request.get("/countries"),
    cacheKey: "app_countries",
    cacheDuration: const Duration(hours: 1),
  ) ?? [];
}
```

### Vider le cache

```dart
// Vider une cle de cache specifique
await apiService.clearCache("app_countries");

// Vider tout le cache API
await apiService.clearAllCache();
```

### Mise en cache avec le helper api()

```dart
api<ApiService>(
  (request) => request.fetchCountries(),
  cacheKey: "app_countries",
  cacheDuration: const Duration(hours: 1),
);
```


<div id="cache-policies"></div>

## Politiques de cache

Utilisez `CachePolicy` pour un controle fin du comportement de mise en cache :

| Politique | Description |
|-----------|-------------|
| `CachePolicy.networkOnly` | Toujours recuperer depuis le reseau (par defaut) |
| `CachePolicy.cacheFirst` | Essayer le cache d'abord, se rabattre sur le reseau |
| `CachePolicy.networkFirst` | Essayer le reseau d'abord, se rabattre sur le cache |
| `CachePolicy.cacheOnly` | Utiliser uniquement le cache, erreur si vide |
| `CachePolicy.staleWhileRevalidate` | Retourner le cache immediatement, mettre a jour en arriere-plan |

### Utilisation

```dart
Future<List<Country>> fetchCountries() async {
  return await network<List<Country>>(
    request: (request) => request.get("/countries"),
    cacheKey: "app_countries",
    cacheDuration: const Duration(hours: 1),
    cachePolicy: CachePolicy.staleWhileRevalidate,
  ) ?? [];
}
```

### Quand utiliser chaque politique

- **cacheFirst** — Donnees qui changent rarement. Retourne les donnees en cache instantanement, ne recupere depuis le reseau que si le cache est vide.
- **networkFirst** — Donnees qui doivent etre fraiches quand c'est possible. Essaie le reseau d'abord, se rabat sur le cache en cas d'echec.
- **staleWhileRevalidate** — Interface qui necessite une reponse immediate mais doit rester a jour. Retourne les donnees en cache, puis les rafraichit en arriere-plan.
- **cacheOnly** — Mode hors ligne. Lance une erreur si aucune donnee en cache n'existe.

> **Remarque :** Si vous fournissez une `cacheKey` ou `cacheDuration` sans specifier de `cachePolicy`, la politique par defaut est `cacheFirst`.


<div id="retrying-failed-requests"></div>

## Relancer les requetes echouees

Relancez automatiquement les requetes qui echouent.

### Relance basique

```dart
Future fetchUsers() async {
  return await network(
    request: (request) => request.get("/users"),
    retry: 3,
  );
}
```

### Relance avec delai

```dart
Future fetchUsers() async {
  return await network(
    request: (request) => request.get("/users"),
    retry: 3,
    retryDelay: Duration(seconds: 2),
  );
}
```

### Relance conditionnelle

```dart
Future fetchUsers() async {
  return await network(
    request: (request) => request.get("/users"),
    retry: 3,
    retryIf: (DioException dioException) {
      // Relancer uniquement sur les erreurs serveur
      return dioException.response?.statusCode == 500;
    },
  );
}
```

### Relance au niveau du service

```dart
apiService.setRetry(3);
apiService.setRetryDelay(Duration(seconds: 2));
apiService.setRetryIf((dioException) => dioException.response?.statusCode == 500);
```


<div id="connectivity-checks"></div>

## Verification de la connectivite

Echouez rapidement lorsque l'appareil est hors ligne au lieu d'attendre un delai d'expiration.

### Au niveau du service

```dart
class ApiService extends NyApiService {
  ApiService() : super(decoders: modelDecoders);

  @override
  bool get checkConnectivityBeforeRequest => true;
  ...
}
```

### Par requete

```dart
await network(
  request: (request) => request.get("/users"),
  checkConnectivity: true,
);
```

### Dynamique

```dart
apiService.setCheckConnectivityBeforeRequest(true);
```

Lorsque c'est active et que l'appareil est hors ligne :
- La politique `networkFirst` se rabat sur le cache
- Les autres politiques lancent immediatement `DioExceptionType.connectionError`


<div id="cancel-tokens"></div>

## Jetons d'annulation

Gerez et annulez les requetes en attente.

```dart
// Creer un jeton d'annulation gere
final token = apiService.createCancelToken();
await apiService.get('/endpoint', cancelToken: token);

// Annuler toutes les requetes en attente (ex. a la deconnexion)
apiService.cancelAllRequests('User logged out');

// Verifier le nombre de requetes actives
int count = apiService.activeRequestCount;

// Nettoyer un jeton specifique quand c'est termine
apiService.removeCancelToken(token);
```


<div id="setting-auth-headers"></div>

## Definir les en-tetes d'authentification

Remplacez `setAuthHeaders` pour attacher des en-tetes d'authentification a chaque requete. Cette methode est appelee avant chaque requete lorsque `shouldSetAuthHeaders` est `true` (la valeur par defaut).

```dart
class ApiService extends NyApiService {
  ...

  @override
  Future<RequestHeaders> setAuthHeaders(RequestHeaders headers) async {
    String? myAuthToken = Auth.data(field: 'token');
    if (myAuthToken != null) {
      headers.addBearerToken(myAuthToken);
    }
    return headers;
  }
}
```

### Desactiver les en-tetes d'authentification

Pour les points d'acces publics qui n'ont pas besoin d'authentification :

```dart
// Par requete
await network(
  request: (request) => request.get("/public-endpoint"),
  shouldSetAuthHeaders: false,
);

// Au niveau du service
apiService.setShouldSetAuthHeaders(false);
```

**Voir aussi :** [Authentification](/docs/7.x/authentication) pour les details sur l'authentification des utilisateurs et le stockage des jetons.


<div id="refreshing-tokens"></div>

## Rafraichir les jetons

Remplacez `shouldRefreshToken` et `refreshToken` pour gerer l'expiration des jetons. Ils sont appeles avant chaque requete.

```dart
class ApiService extends NyApiService {
  ...

  @override
  Future<bool> shouldRefreshToken() async {
    // Verifier si le jeton doit etre rafraichi
    return false;
  }

  @override
  Future<void> refreshToken(Dio dio) async {
    // Utiliser la nouvelle instance Dio (sans intercepteurs) pour rafraichir le jeton
    dynamic response = (await dio.post("https://example.com/refresh-token")).data;

    // Sauvegarder le nouveau jeton dans le stockage
    await Auth.set((data) {
      data['token'] = response['token'];
      return data;
    });
  }
}
```

Le parametre `dio` dans `refreshToken` est une nouvelle instance Dio, separee de l'instance principale du service, pour eviter les boucles d'intercepteurs.


<div id="singleton-api-service"></div>

## Service API singleton

Par defaut, le helper `api` cree une nouvelle instance a chaque fois. Pour utiliser un singleton, passez une instance au lieu d'une factory dans `config/decoders.dart` :

```dart
final Map<Type, dynamic> apiDecoders = {
  ApiService: () => ApiService(), // Nouvelle instance a chaque fois

  ApiService: ApiService(), // Singleton — toujours la meme instance
};
```


<div id="advanced-configuration"></div>

## Configuration avancee

### Initialisation Dio personnalisee

```dart
class ApiService extends NyApiService {
  ApiService() : super(
    decoders: modelDecoders,
    initDio: (Dio dio) {
      dio.options.validateStatus = (status) => status! < 500;
      return dio;
    },
  );
}
```

### Acceder a l'instance Dio

```dart
Dio dioInstance = apiService.dio;

Response response = await dioInstance.request(
  '/custom-endpoint',
  options: Options(method: 'OPTIONS'),
);
```

### Aide a la pagination

```dart
apiService.setPagination(
  1,
  paramPage: 'page',
  paramPerPage: 'per_page',
  perPage: '20',
);
```

### Callbacks d'evenements

```dart
apiService.onSuccess((response, data) {
  print('Success: ${response.statusCode}');
});

apiService.onError((dioException) {
  print('Error: ${dioException.message}');
});
```

### Proprietes remplacables

| Propriete | Type | Par defaut | Description |
|----------|------|---------|-------------|
| `baseUrl` | `String` | `""` | URL de base pour toutes les requetes |
| `interceptors` | `Map<Type, Interceptor>` | `{}` | Intercepteurs Dio |
| `decoders` | `Map<Type, dynamic>?` | `{}` | Decodeurs de modeles pour la transformation JSON |
| `shouldSetAuthHeaders` | `bool` | `true` | Si `setAuthHeaders` doit etre appele avant les requetes |
| `retry` | `int` | `0` | Tentatives de relance par defaut |
| `retryDelay` | `Duration` | `1 second` | Delai par defaut entre les relances |
| `checkConnectivityBeforeRequest` | `bool` | `false` | Verifier la connectivite avant les requetes |
