Back to Posts

Flutter Performance Optimization

14 min read

This guide covers techniques and best practices for optimizing Flutter application performance.

1. Widget Optimization

Const Constructors

// Good
class CustomButton extends StatelessWidget {
  const CustomButton({
    Key? key,
    required this.text,
    required this.onPressed,
  }) : super(key: key);

  final String text;
  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      child: Text(text),
    );
  }
}

// Bad
class CustomButton extends StatelessWidget {
  CustomButton({
    Key? key,
    required this.text,
    required this.onPressed,
  }) : super(key: key);

  final String text;
  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      child: Text(text),
    );
  }
}

Repaint Boundaries

class AnimatedWidget extends StatefulWidget {
  @override
  _AnimatedWidgetState createState() => _AnimatedWidgetState();
}

class _AnimatedWidgetState extends State<AnimatedWidget> with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 1),
    );
    _animation = Tween<double>(begin: 0, end: 1).animate(_controller);
    _controller.repeat();
  }

  @override
  Widget build(BuildContext context) {
    return RepaintBoundary(
      child: AnimatedBuilder(
        animation: _animation,
        builder: (context, child) {
          return Transform.rotate(
            angle: _animation.value * 2 * pi,
            child: child,
          );
        },
        child: const FlutterLogo(size: 100),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

Selective Rebuilds

class CounterWidget extends StatelessWidget {
  const CounterWidget({
    Key? key,
    required this.count,
    required this.onIncrement,
  }) : super(key: key);

  final int count;
  final VoidCallback onIncrement;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $count'),
        ElevatedButton(
          onPressed: onIncrement,
          child: const Text('Increment'),
        ),
      ],
    );
  }
}

// Usage
class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  int _count = 0;
  String _title = 'Counter App';

  void _increment() {
    setState(() {
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(_title),
        CounterWidget(
          count: _count,
          onIncrement: _increment,
        ),
      ],
    );
  }
}

2. State Management Optimization

Efficient State Updates

class CounterProvider extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

// Usage
class CounterWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<CounterProvider>(
      builder: (context, provider, _) {
        return Column(
          children: [
            Text('Count: ${provider.count}'),
            ElevatedButton(
              onPressed: provider.increment,
              child: const Text('Increment'),
            ),
          ],
        );
      },
    );
  }
}

State Persistence

class AppState extends ChangeNotifier {
  final SharedPreferences _prefs;
  int _count = 0;

  AppState(this._prefs) {
    _loadState();
  }

  int get count => _count;

  void increment() {
    _count++;
    _saveState();
    notifyListeners();
  }

  Future<void> _loadState() async {
    _count = _prefs.getInt('count') ?? 0;
    notifyListeners();
  }

  Future<void> _saveState() async {
    await _prefs.setInt('count', _count);
  }
}

// Usage
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final prefs = await SharedPreferences.getInstance();
  runApp(
    ChangeNotifierProvider(
      create: (_) => AppState(prefs),
      child: const MyApp(),
    ),
  );
}

3. Memory Management

Image Optimization

class OptimizedImage extends StatelessWidget {
  final String imageUrl;
  final double? width;
  final double? height;
  final BoxFit fit;

  const OptimizedImage({
    Key? key,
    required this.imageUrl,
    this.width,
    this.height,
    this.fit = BoxFit.cover,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Image.network(
      imageUrl,
      width: width,
      height: height,
      fit: fit,
      cacheWidth: width?.toInt(),
      cacheHeight: height?.toInt(),
      loadingBuilder: (context, child, loadingProgress) {
        if (loadingProgress == null) return child;
        return Center(
          child: CircularProgressIndicator(
            value: loadingProgress.expectedTotalBytes != null
                ? loadingProgress.cumulativeBytesLoaded /
                    loadingProgress.expectedTotalBytes!
                : null,
          ),
        );
      },
    );
  }
}

Resource Cleanup

class ResourceManager extends StatefulWidget {
  final Widget child;

  const ResourceManager({Key? key, required this.child}) : super(key: key);

  @override
  _ResourceManagerState createState() => _ResourceManagerState();
}

class _ResourceManagerState extends State<ResourceManager> {
  final List<StreamSubscription> _subscriptions = [];
  final List<AnimationController> _controllers = [];

  void addSubscription(StreamSubscription subscription) {
    _subscriptions.add(subscription);
  }

  void addController(AnimationController controller) {
    _controllers.add(controller);
  }

  @override
  void dispose() {
    for (var subscription in _subscriptions) {
      subscription.cancel();
    }
    for (var controller in _controllers) {
      controller.dispose();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
}

4. Layout Optimization

Efficient Layouts

class OptimizedLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: [
        SliverAppBar(
          title: const Text('Optimized Layout'),
          floating: true,
        ),
        SliverList(
          delegate: SliverChildBuilderDelegate(
            (context, index) {
              return ListTile(
                title: Text('Item $index'),
              );
            },
            childCount: 1000,
          ),
        ),
      ],
    );
  }
}

Layout Constraints

class ConstrainedLayout extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        return SingleChildScrollView(
          child: ConstrainedBox(
            constraints: BoxConstraints(
              minHeight: constraints.maxHeight,
            ),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                // Header
                Container(
                  height: 100,
                  color: Colors.blue,
                ),
                // Content
                Expanded(
                  child: Container(
                    color: Colors.white,
                  ),
                ),
                // Footer
                Container(
                  height: 100,
                  color: Colors.blue,
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

5. Animation Optimization

Efficient Animations

class OptimizedAnimation extends StatefulWidget {
  @override
  _OptimizedAnimationState createState() => _OptimizedAnimationState();
}

class _OptimizedAnimationState extends State<OptimizedAnimation>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 1),
    );
    _animation = CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    );
    _controller.repeat();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (context, child) {
        return Transform.scale(
          scale: 1.0 + (_animation.value * 0.2),
          child: child,
        );
      },
      child: const FlutterLogo(size: 100),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

Animation Performance

class PerformanceAnimation extends StatefulWidget {
  @override
  _PerformanceAnimationState createState() => _PerformanceAnimationState();
}

class _PerformanceAnimationState extends State<PerformanceAnimation>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 1),
    );
    _animation = Tween<double>(begin: 0, end: 1).animate(_controller);
    _controller.repeat();
  }

  @override
  Widget build(BuildContext context) {
    return RepaintBoundary(
      child: AnimatedBuilder(
        animation: _animation,
        builder: (context, child) {
          return CustomPaint(
            painter: CirclePainter(_animation.value),
            child: child,
          );
        },
        child: const SizedBox(width: 100, height: 100),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

class CirclePainter extends CustomPainter {
  final double progress;

  CirclePainter(this.progress);

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2;

    final center = Offset(size.width / 2, size.height / 2);
    final radius = size.width / 2 * progress;

    canvas.drawCircle(center, radius, paint);
  }

  @override
  bool shouldRepaint(CirclePainter oldDelegate) {
    return progress != oldDelegate.progress;
  }
}

Performance Tools

Flutter DevTools

void main() {
  // Enable performance overlay
  debugPaintSizeEnabled = true;
  debugPaintBaselinesEnabled = true;
  debugPaintPointersEnabled = true;
  debugPaintLayerBordersEnabled = true;
  debugRepaintRainbowEnabled = true;

  runApp(const MyApp());
}

Dart Observatory

void main() {
  // Enable observatory
  debugPrint('Observatory URL: ${Observatory.defaultUri}');
  runApp(const MyApp());
}

Best Practices

  1. Widget Optimization

    • Use const constructors
    • Implement repaint boundaries
    • Minimize rebuilds
    • Use proper widget types
  2. State Management

    • Choose efficient patterns
    • Minimize state updates
    • Use proper persistence
    • Clean up resources
  3. Memory Management

    • Optimize images
    • Clean up resources
    • Use proper caching
    • Monitor memory usage
  4. Layout Optimization

    • Use efficient layouts
    • Follow constraints
    • Minimize nesting
    • Use proper widgets
  5. Animation Optimization

    • Use efficient animations
    • Monitor performance
    • Clean up controllers
    • Use proper curves

Conclusion

Remember these key points:

  1. Optimize widgets
  2. Manage state efficiently
  3. Handle memory properly
  4. Optimize layouts
  5. Use efficient animations

By following these practices, you can:

  • Improve app performance
  • Reduce memory usage
  • Enhance user experience
  • Optimize resource usage

Keep optimizing your Flutter applications!