Gestion avancée des listes : actualisation et chargement progressif
Le chargement intégral d'un jeu de données depuis une API entraîne rapidement des problèmes de consommation mémoire, de latence réseau et de fluidité d'interface. Pour garantir une expérience utilisateur optimale sur OpenHarmony avec Flutter, il est indispensable d'adopter une stratégie de pagination couplée à des gestes standards : le balayage vers le bas pour actualiser les données et le défilement continu pour récupérer les éléments suivants.
Cette architecture repose sur l'utilisation du paquet easy_refresh, qui offre une abstraction robuste des mécanismes de défilement, de la détection des gestes et des indicateurs de chargement.
1. Configuration des dépendances
Déclarez la bibliothèque dans votre fichier pubspec.yaml. Il est conseillé de figer la version pour éviter les ruptures de compatibilité avec certaines versions du SDK Flutter.
dependencies:
flutter:
sdk: flutter
dio: ^5.9.0
provider: ^6.1.5+1
easy_refresh: 3.3.0
Synchronisez ensuite les paquets via la commande flutter pub get.
2. Adaptation de la couche réseau
L'API distante doit accepter des paramètres de pagination. Nous remplaçons la requête unique par une méthode acceptant un décalage (skip) et une taille de lot (count).
Fichier : lib/services/data_repository.dart
Future<List<Article>> retrieveArticles({int skip = 0, int count = 15}) async {
try {
final httpResponse = await _httpClient.get(
'$_endpoint/articles',
queryParameters: {
'_skip': skip,
'_count': count,
},
);
// Désérialisation JSON et mapping vers le modèle métier
return (httpResponse.data as List)
.map((json) => Article.fromJson(json))
.toList();
} catch (err) {
throw Exception('Échec de la récupération des données : $err');
}
}
3. Orchestration de l'état avec Provider
Le gestionnaire d'état doit distinguer clairement une actualisation complète d'un chargement incrémental. Nous utilisons des indicateurs pour suivre la position actuelle dans le flux et la disponibilité des données restantes.
Fichier : lib/viewmodels/article_viewmodel.dart
class ArticleViewModel extends ChangeNotifier {
final DataRepository _repository;
List<Article> _items = [];
bool _canLoadMore = true;
int _currentOffset = 0;
static const int _pageSize = 15;
List<Article> get items => List.unmodifiable(_items);
bool get hasMoreData => _canLoadMore;
ArticleViewModel(this._repository);
/// Réinitialise la collection et récupère le premier lot
Future<void> pullToRefresh() async {
_currentOffset = 0;
_canLoadMore = true;
notifyListeners();
try {
final freshData = await _repository.retrieveArticles(skip: 0, count: _pageSize);
_items = freshData;
_currentOffset = freshData.length;
if (freshData.length < _pageSize) {
_canLoadMore = false;
}
} catch (e) {
// Journalisation ou gestion d'erreur centralisée
} finally {
notifyListeners();
}
}
/// Ajoute les éléments suivants à la liste existante
Future<void> fetchNextBatch() async {
if (!_canLoadMore) return;
try {
final nextBatch = await _repository.retrieveArticles(
skip: _currentOffset,
count: _pageSize,
);
if (nextBatch.isEmpty) {
_canLoadMore = false;
} else {
_items.addAll(nextBatch);
_currentOffset += nextBatch.length;
if (nextBatch.length < _pageSize) {
_canLoadMore = false;
}
}
} catch (e) {
// Gestion d'erreur
}
notifyListeners();
}
}
4. Construction de l'interface interactive
L'intégration visuelle consiste à encapsuler le composant de liste dans le widget EasyRefresh. Le contrôleur permet de synchroniser manuellement la fin des animations avec la résolution des opérations asynchrones, évitant ainsi les indicateurs bloqués.
Fichier : lib/screens/article_feed_screen.dart
class _ArticleFeedScreenState extends State<ArticleFeedScreen> {
late final EasyRefreshController _refreshCtrl;
@override
void initState() {
super.initState();
_refreshCtrl = EasyRefreshController(
controlFinishRefresh: true,
controlFinishLoad: true,
);
}
@override
void dispose() {
_refreshCtrl.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flux d\'articles')),
body: Consumer<ArticleViewModel>(
builder: (ctx, vm, _) {
return EasyRefresh(
controller: _refreshCtrl,
header: const ClassicHeader(),
footer: const ClassicFooter(),
onRefresh: () async {
await vm.pullToRefresh();
if (!mounted) return;
_refreshCtrl.finishRefresh();
_refreshCtrl.resetFooter();
},
onLoad: () async {
if (!vm.hasMoreData) {
_refreshCtrl.finishLoad(IndicatorResult.noMore);
return;
}
await vm.fetchNextBatch();
if (!mounted) return;
_refreshCtrl.finishLoad(
vm.hasMoreData ? IndicatorResult.success : IndicatorResult.noMore,
);
},
child: ListView.separated(
itemCount: vm.items.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) {
final article = vm.items[index];
return ListTile(
title: Text(article.title),
subtitle: Text(article.summary),
);
},
),
);
},
),
);
}
}
Avantages architecturaux par rapport aux widgets natifs
Les composants standards de Flutter, tels que RefreshIndicator, se limitent au geste de rafraîchissement vertical. Implémenter manuellement la détection de fin de défilement via un ScrollController introduit fréquemment des problèmes de requêtes dupliquées, de rebonds visuels ou de fuites de mémoire lors du démontage des widgets. La solution présentée délègue la détection des gestes et la machine à états des indicateurs à une bibliothèque éprouvée, garantissant un comportement natif et prévisible sur les terminaux OpenHarmony.
La séparation stricte entre la logique de pagination dans le ViewModel et la couche visuelle permet également de tester unitairement les scénarios de chargement sans dépendre du rendu graphique. Cette structure constitue une base stable pour intégrer des mécanismes avancés tels que la mise en cache locale, la reprise sur erreur ou la gestion des états hors ligne.