
Gad Ntenta
Développeur Full Stack
Performance dans Flutter
FlutterPerformanceOptimization
Optimisez les performances de votre application Flutter avec ces techniques avancées.
Performance dans Flutter
Introduction
La performance est un aspect crucial du développement d'applications Flutter. Dans cet article, nous allons explorer les différentes techniques et meilleures pratiques pour optimiser les performances de vos applications.
Optimisations de Base
1. Utilisation de const
// Mauvais
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Text('Hello'),
color: Colors.blue,
padding: EdgeInsets.all(16),
);
}
}
// Bon
class MyWidget extends StatelessWidget {
const MyWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Container(
child: Text('Hello'),
color: Colors.blue,
padding: EdgeInsets.all(16),
);
}
}
2. Éviter les Rebuilds Inutiles
// Mauvais
class MyWidget extends StatelessWidget {
final String title;
final int count;
MyWidget({required this.title, required this.count});
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(title),
Text('Count: $count'),
// Ce widget est reconstruit même si count ne change pas
ExpensiveWidget(),
],
);
}
}
// Bon
class MyWidget extends StatelessWidget {
final String title;
final int count;
const MyWidget({required this.title, required this.count});
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(title),
Text('Count: $count'),
// Ce widget n'est reconstruit que si count change
const ExpensiveWidget(),
],
);
}
}
Optimisations Avancées
1. Utilisation de ListView.builder
// Mauvais
ListView(
children: items.map((item) => ItemWidget(item: item)).toList(),
)
// Bon
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ItemWidget(item: items[index]),
)
2. Gestion de la Mémoire
class ImageCacheManager {
static final Map<String, Uint8List> _cache = {};
static Future<Uint8List> getImage(String url) async {
if (_cache.containsKey(url)) {
return _cache[url]!;
}
final response = await http.get(Uri.parse(url));
final bytes = response.bodyBytes;
_cache[url] = bytes;
return bytes;
}
static void clearCache() {
_cache.clear();
}
}
class CachedImage extends StatelessWidget {
final String url;
const CachedImage({required this.url});
@override
Widget build(BuildContext context) {
return FutureBuilder<Uint8List>(
future: ImageCacheManager.getImage(url),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Image.memory(snapshot.data!);
}
return CircularProgressIndicator();
},
);
}
}
Optimisations de Rendu
1. Utilisation de RepaintBoundary
class AnimatedWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RepaintBoundary(
child: Container(
child: CustomPaint(
painter: MyPainter(),
),
),
);
}
}
2. Optimisation des Images
class OptimizedImage extends StatelessWidget {
final String url;
final double width;
final double height;
const OptimizedImage({
required this.url,
required this.width,
required this.height,
});
@override
Widget build(BuildContext context) {
return Image.network(
url,
width: width,
height: height,
cacheWidth: (width * MediaQuery.of(context).devicePixelRatio).toInt(),
cacheHeight: (height * MediaQuery.of(context).devicePixelRatio).toInt(),
fit: BoxFit.cover,
);
}
}
Optimisations de Navigation
1. Gestion du Cache des Routes
class OptimizedNavigator extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Navigator(
onGenerateRoute: (settings) {
return PageRouteBuilder(
settings: settings,
pageBuilder: (context, animation, secondaryAnimation) {
return _buildPage(settings.name!);
},
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(opacity: animation, child: child);
},
);
},
);
}
Widget _buildPage(String routeName) {
switch (routeName) {
case '/home':
return const HomePage();
case '/profile':
return const ProfilePage();
default:
return const NotFoundPage();
}
}
}
2. Optimisation des Transitions
class OptimizedPageRoute extends PageRouteBuilder {
final Widget page;
OptimizedPageRoute({required this.page})
: super(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
transitionDuration: Duration(milliseconds: 300),
);
}
Optimisations de Données
1. Pagination Efficace
class PaginatedList extends StatefulWidget {
@override
_PaginatedListState createState() => _PaginatedListState();
}
class _PaginatedListState extends State<PaginatedList> {
final ScrollController _controller = ScrollController();
final List<Item> _items = [];
bool _isLoading = false;
int _page = 1;
@override
void initState() {
super.initState();
_loadMoreItems();
_controller.addListener(_onScroll);
}
void _onScroll() {
if (_controller.position.pixels == _controller.position.maxScrollExtent) {
_loadMoreItems();
}
}
Future<void> _loadMoreItems() async {
if (_isLoading) return;
setState(() {
_isLoading = true;
});
final newItems = await fetchItems(_page);
setState(() {
_items.addAll(newItems);
_page++;
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
return ListView.builder(
controller: _controller,
itemCount: _items.length + 1,
itemBuilder: (context, index) {
if (index == _items.length) {
return _isLoading
? Center(child: CircularProgressIndicator())
: SizedBox();
}
return ItemWidget(item: _items[index]);
},
);
}
}
2. Mise en Cache des Données
class DataCache {
static final Map<String, dynamic> _cache = {};
static final Map<String, DateTime> _timestamps = {};
static const Duration _cacheDuration = Duration(minutes: 5);
static Future<T> getData<T>(String key, Future<T> Function() fetchData) async {
if (_isValid(key)) {
return _cache[key] as T;
}
final data = await fetchData();
_cache[key] = data;
_timestamps[key] = DateTime.now();
return data;
}
static bool _isValid(String key) {
if (!_cache.containsKey(key)) return false;
final timestamp = _timestamps[key];
if (timestamp == null) return false;
return DateTime.now().difference(timestamp) < _cacheDuration;
}
static void clearCache() {
_cache.clear();
_timestamps.clear();
}
}
Outils de Profilage
1. Utilisation de DevTools
void main() {
runApp(
MaterialApp(
home: ProfilingWrapper(
child: MyApp(),
),
),
);
}
class ProfilingWrapper extends StatelessWidget {
final Widget child;
const ProfilingWrapper({Key? key, required this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
return RepaintBoundary(
child: child,
);
}
}
2. Mesure des Performances
class PerformanceMonitor {
static final Map<String, List<int>> _measurements = {};
static void startMeasurement(String key) {
_measurements[key] = [DateTime.now().millisecondsSinceEpoch];
}
static void endMeasurement(String key) {
if (_measurements.containsKey(key)) {
_measurements[key]!.add(DateTime.now().millisecondsSinceEpoch);
_logMeasurement(key);
}
}
static void _logMeasurement(String key) {
final measurements = _measurements[key]!;
final duration = measurements[1] - measurements[0];
print('Performance: $key took ${duration}ms');
}
}
Conclusion
L'optimisation des performances dans Flutter est un processus continu qui nécessite une attention particulière aux détails. En suivant ces bonnes pratiques et en utilisant les outils appropriés, vous pouvez créer des applications Flutter performantes et réactives.
N'oubliez pas de :
- Profiler régulièrement votre application
- Optimiser les reconstructions de widgets
- Gérer efficacement la mémoire
- Utiliser les outils de développement
- Tester sur différents appareils
Avec ces connaissances, vous êtes prêt à créer des applications Flutter performantes !