Librairies clés du projet
--------------------------
Cette section détaille les librairies essentielles qui nécessitent la génération de code.
Dio - Client HTTP
~~~~~~~~~~~~~~~~~
**Qu'est-ce que c'est ?**
Dio est un client HTTP puissant pour Dart qui gère les requêtes réseau vers l'API GeoNature.
**Pourquoi l'utiliser ?**
.. container:: architecture-overview
.. container:: layer-card data
**✅ Avantages**
• **Intercepteurs** : Gestion automatique des tokens, logs
• **Gestion d'erreurs** : Retry automatique, timeout
• **Upload/Download** : Progress, annulation
• **Offline** : Cache et synchronisation différée
**Exemple concret dans le projet :**
.. code-block:: dart
:linenos:
:caption: lib/data/datasource/geonature_api_client.dart
@riverpod
Dio geoNatureApiClient(GeoNatureApiClientRef ref) {
final dio = Dio(BaseOptions(
baseUrl: 'https://api.geonature.fr',
connectTimeout: Duration(seconds: 10),
receiveTimeout: Duration(seconds: 30),
));
// Intercepteur pour les tokens
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) async {
final token = await storage.getToken();
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
handler.next(options);
},
onError: (error, handler) {
if (error.response?.statusCode == 401) {
// Token expiré, rediriger vers login
ref.read(authViewModelProvider.notifier).logout();
}
handler.next(error);
},
));
return dio;
}
**Utilisation pratique :**
.. code-block:: dart
final apiClient = ref.read(geoNatureApiClientProvider);
// GET - Récupérer les observations
final response = await apiClient.get('/monitoring/observations');
final observations = response.data;
// POST - Créer une observation
await apiClient.post('/monitoring/observations', data: {
'date': observation.date.toIso8601String(),
'species_id': observation.speciesId,
'coordinates': [observation.longitude, observation.latitude],
});
// Upload avec progress
await apiClient.post('/upload', data: FormData.fromMap({
'file': await MultipartFile.fromFile(imagePath),
}), onSendProgress: (sent, total) {
print('Progress: ${(sent / total * 100).toStringAsFixed(1)}%');
});
Freezed - Modèles immutables
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Qu'est-ce que c'est ?**
Freezed est une librairie qui génère automatiquement des classes **immutables** avec toutes les méthodes utiles (copyWith, toString, equality, hashCode, JSON serialization).
**Pourquoi l'utiliser ?**
.. container:: architecture-overview
.. container:: layer-card domain
**✅ Avantages**
• **Immutabilité** : Objets non modifiables
• **Type safety** : Détection d'erreurs à la compilation
• **Moins de boilerplate** : Code généré automatiquement
• **JSON support** : Sérialisation automatique
**Exemple concret dans le projet :**
.. code-block:: dart
:linenos:
:caption: lib/domain/model/observation.dart
@freezed
class Observation with _$Observation {
const factory Observation({
required String id,
required DateTime date,
required double latitude,
required double longitude,
String? species,
String? comment,
}) = _Observation;
factory Observation.fromJson(Map
json) =>
_$ObservationFromJson(json);
}
**Code généré automatiquement :**
.. code-block:: dart
// Dans observation.freezed.dart (généré)
extension ObservationMethods on Observation {
// Copie avec modifications
Observation copyWith({
String? id,
DateTime? date,
String? species,
// ... autres champs
});
// Conversion JSON
Map toJson();
// Égalité et hashCode automatiques
@override
bool operator ==(Object other);
@override
int get hashCode;
}
**Utilisation pratique :**
.. code-block:: dart
// Créer une observation
final obs = Observation(
id: '123',
date: DateTime.now(),
latitude: 45.0,
longitude: 5.0,
);
// Modifier (crée une nouvelle instance)
final modifiedObs = obs.copyWith(
species: 'Salamandre tachetée',
comment: 'Observée sous un rocher',
);
// JSON
final json = obs.toJson(); // Map
final fromJson = Observation.fromJson(json); // Observation
Drift - Base de données SQLite
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Qu'est-ce que c'est ?**
Drift est un ORM (Object-Relational Mapping) pour SQLite qui génère du code type-safe pour les requêtes de base de données.
**Pourquoi l'utiliser ?**
.. container:: architecture-overview
.. container:: layer-card data
**✅ Avantages**
• **Type safety** : Requêtes vérifiées à la compilation
• **Performance** : Requêtes compilées et optimisées
• **Offline-first** : SQLite embarqué dans l'app
• **Migration** : Gestion automatique des changements de schéma
**Exemple concret dans le projet :**
.. code-block:: dart
:linenos:
:caption: lib/data/database/tables/observations_table.dart
class Observations extends Table {
TextColumn get id => text()();
DateTimeColumn get date => dateTime()();
RealColumn get latitude => real()();
RealColumn get longitude => real()();
TextColumn get species => text().nullable()();
TextColumn get comment => text().nullable()();
BoolColumn get isSynced => boolean().withDefault(const Constant(false))();
@override
Set get primaryKey => {id};
}
**DAO généré automatiquement :**
.. code-block:: dart
:linenos:
:caption: lib/data/database/dao/observations_dao.dart
@DriftAccessor(tables: [Observations])
class ObservationsDao extends DatabaseAccessor
with _$ObservationsDaoMixin {
ObservationsDao(AppDatabase db) : super(db);
// Requêtes générées automatiquement
Future> getAllObservations() =>
select(observations).get();
Future> getUnsyncedObservations() =>
(select(observations)..where((o) => o.isSynced.equals(false))).get();
Future insertObservation(ObservationsCompanion observation) =>
into(observations).insert(observation);
Future markAsSynced(String id) =>
(update(observations)..where((o) => o.id.equals(id)))
.write(ObservationsCompanion(isSynced: Value(true)));
}
**Utilisation pratique :**
.. code-block:: dart
final dao = database.observationsDao;
// Insérer une observation
await dao.insertObservation(
ObservationsCompanion(
id: Value('obs_123'),
date: Value(DateTime.now()),
latitude: Value(45.0),
longitude: Value(5.0),
species: Value('Salamandre tachetée'),
),
);
// Récupérer les observations non synchronisées
final unsynced = await dao.getUnsyncedObservations();
// Marquer comme synchronisée
await dao.markAsSynced('obs_123');
Riverpod - State Management
~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Qu'est-ce que c'est ?**
Riverpod est une librairie de gestion d'état qui utilise la génération de code pour créer des providers type-safe et performants.
**Pourquoi l'utiliser ?**
.. container:: architecture-overview
.. container:: layer-card presentation
**✅ Avantages**
• **Type safety** : Providers typés automatiquement
• **Performance** : Recalcul uniquement si nécessaire
• **Testabilité** : Facile à mocker et tester
• **Developer Experience** : Auto-complétion parfaite
**Exemple concret dans le projet :**
.. code-block:: dart
:linenos:
:caption: lib/presentation/viewmodel/observations_viewmodel.dart
@riverpod
class ObservationsViewModel extends _$ObservationsViewModel {
@override
Future> build() async {
// Récupérer les observations depuis le repository
final repository = ref.read(observationsRepositoryProvider);
return repository.getObservations();
}
Future addObservation(Observation observation) async {
// Mettre l'état en loading
state = const AsyncLoading();
try {
final repository = ref.read(observationsRepositoryProvider);
await repository.addObservation(observation);
// Recharger la liste
ref.invalidateSelf();
} catch (error) {
state = AsyncError(error, StackTrace.current);
}
}
Future syncObservations() async {
final repository = ref.read(observationsRepositoryProvider);
await repository.syncWithServer();
ref.invalidateSelf();
}
}
**Provider généré automatiquement :**
.. code-block:: dart
// Dans observations_viewmodel.g.dart (généré)
final observationsViewModelProvider =
AsyncNotifierProvider.autoDispose>(
ObservationsViewModel.new,
);
**Utilisation dans un Widget :**
.. code-block:: dart
:linenos:
:caption: lib/presentation/view/observations_list_page.dart
class ObservationsListPage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final observationsAsync = ref.watch(observationsViewModelProvider);
return observationsAsync.when(
loading: () => const CircularProgressIndicator(),
error: (error, stack) => Text('Erreur: $error'),
data: (observations) => ListView.builder(
itemCount: observations.length,
itemBuilder: (context, index) {
final obs = observations[index];
return ListTile(
title: Text(obs.species ?? 'Espèce inconnue'),
subtitle: Text('${obs.date}'),
trailing: IconButton(
icon: Icon(Icons.sync),
onPressed: () {
ref.read(observationsViewModelProvider.notifier)
.syncObservations();
},
),
);
},
),
);
}
}
Pourquoi ces 3 librairies ensemble ?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. admonition:: 🔄 Écosystème cohérent
:class: key-point
**Freezed + Drift + Riverpod** forment un écosystème parfaitement intégré :
1. **Freezed** : Modèles immutables type-safe
2. **Drift** : Persistance locale avec type safety
3. **Riverpod** : State management réactif et performant
**Résultat** : Application robuste, performante et maintenable !
**Flux de données typique :**
.. code-block:: text
User Action → Riverpod Provider → Repository → Drift DAO → SQLite
↓
UI Update ← Riverpod State ← Domain Model ← Freezed Model ← Database
**Génération de code nécessaire :**
.. code-block:: bash
# Cette commande génère TOUT le code nécessaire
make generate_code
# Équivalent à :
flutter packages pub run build_runner build --delete-conflicting-outputs
**Fichiers générés :**
- `*.freezed.dart` - Modèles Freezed
- `*.g.dart` - JSON serialization + Riverpod providers
- `*.drift.dart` - Tables et DAOs Drift
.. raw:: html
.. raw:: html
Contact
=======
**Repository** : https://github.com/RNF-SI/gn_mobile_monitoring
----
*Document créé le 6 novembre 2025*
*Version 1.0*