Claude Code Plugins

Community-maintained marketplace

Feedback

jj-flutter-widget-optimization

@Dmccarty30/Journeyman-Jobs
2
0

Performance optimization techniques for Flutter widgets in Journeyman Jobs. Covers virtual scrolling, rebuild minimization, memory management, battery efficiency, and 60fps targets for field workers on budget Android devices with 8-12 hour usage patterns. Use when optimizing existing widgets or diagnosing performance issues.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name jj-flutter-widget-optimization
description Performance optimization techniques for Flutter widgets in Journeyman Jobs. Covers virtual scrolling, rebuild minimization, memory management, battery efficiency, and 60fps targets for field workers on budget Android devices with 8-12 hour usage patterns. Use when optimizing existing widgets or diagnosing performance issues.

JJ Flutter Widget Optimization

Purpose

Optimize Flutter widget performance for electrical field workers using budget Android devices (2-4GB RAM) in 8-12 hour shifts with limited charging opportunities. Target 60fps smooth scrolling, minimal battery drain, and efficient memory usage.

When To Use

  • Diagnosing janky scrolling or frame drops
  • Reducing battery consumption
  • Optimizing memory usage for long sessions
  • Improving app responsiveness on low-end devices
  • Fixing rebuild storms or unnecessary renders
  • Optimizing large lists (100+ items)

Core Performance Principles

1. Performance Targets

Frame Rate Goals:

  • 60fps: Standard UI interactions and scrolling
  • 30fps: Acceptable for complex animations
  • 16.67ms: Maximum frame budget (60fps)
  • 8ms: Widget build time target

Memory Constraints:

  • Budget Devices: 2-4GB total RAM
  • App Budget: <200MB steady state
  • Peak Usage: <400MB during heavy operations
  • List Items: <50KB per cached item

Battery Efficiency:

  • Background: <2% drain per hour
  • Active Use: <15% drain per hour
  • Idle: <0.5% drain per hour
  • 8-Hour Shift: App uses <100% battery

2. Widget Rebuild Optimization

Problem: Unnecessary rebuilds waste CPU and battery

Solution Matrix:

Issue Detection Solution
Entire screen rebuilds Flutter DevTools Performance tab Split into smaller widgets
Provider watch too broad Profile provider dependencies Use .select() for granular watching
Const widgets not marked Performance overlay shows rebuilds Add const constructors
Keys missing in lists Items flicker during updates Add ValueKey(item.id)
Anonymous functions in build New function instance each build Extract to methods or use callbacks

Essential Optimization Patterns

Pattern 1: Virtual Scrolling with itemExtent

Purpose: Smooth scrolling for large job lists (100-1000+ items)

Problem:

// ❌ BAD: Variable heights cause measurement on every scroll
ListView.builder(
  itemCount: jobs.length,
  itemBuilder: (context, index) => JobCard(jobs[index]),
);
// Result: Janky scrolling, dropped frames, poor UX

Solution:

// ✅ GOOD: Fixed height enables fast scrolling
ListView.builder(
  itemCount: jobs.length,
  itemExtent: 140,  // CRITICAL: Fixed height = smooth scroll
  cacheExtent: 280,  // Pre-cache 2 items (2 * 140)
  itemBuilder: (context, index) {
    return JobCard(
      key: ValueKey(jobs[index].id),  // Preserve state
      job: jobs[index],
    );
  },
);
// Result: Smooth 60fps scrolling, minimal CPU usage

Performance Impact:

  • Before: 30-40fps, janky scrolling
  • After: 60fps, butter-smooth
  • CPU Reduction: 40-60% less processing
  • Battery Savings: ~20% for scroll-heavy usage

When To Use:

  • Lists with 50+ items
  • Uniform or semi-uniform item heights
  • Job cards, user lists, search results

Pattern 2: Selective Provider Watching

Purpose: Minimize rebuilds by watching only needed data

Problem:

// ❌ BAD: Rebuilds widget on ANY filter change
class JobFilterBar extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final filter = ref.watch(jobFilterProvider);  // Watches entire object!

    return Text('Active filters: ${filter.activeCount}');
  }
}
// Result: Rebuilds when ANY filter field changes, even unrelated ones

Solution:

// ✅ GOOD: Rebuilds only when activeCount changes
class JobFilterBar extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Only rebuilds when activeCount value changes
    final activeCount = ref.watch(
      jobFilterProvider.select((filter) => filter.activeCount)
    );

    return Text('Active filters: $activeCount');
  }
}
// Result: 90% fewer rebuilds for this widget

Performance Impact:

  • Before: 10-20 rebuilds per filter interaction
  • After: 1-2 rebuilds per filter interaction
  • CPU Reduction: 50-80% for filter UI

When To Use:

  • Displaying derived state (counts, flags)
  • Watching complex provider objects
  • High-frequency update scenarios

Pattern 3: Const Widget Optimization

Purpose: Eliminate rebuilds for static content

Problem:

// ❌ BAD: Creates new widget instances on every build
class JobCard extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        SizedBox(height: 8),  // New instance every build!
        Icon(Icons.work),     // New instance every build!
        Padding(              // New instance every build!
          padding: EdgeInsets.all(16),
          child: Text('Job Title'),
        ),
      ],
    );
  }
}

Solution:

// ✅ GOOD: Const widgets reuse same instance
class JobCard extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const SizedBox(height: 8),  // Reused across all builds
        const Icon(Icons.work),     // Reused across all builds
        const Padding(              // Reused across all builds
          padding: EdgeInsets.all(16),
          child: Text('Job Title'),
        ),
      ],
    );
  }
}

Performance Impact:

  • Memory: 30-50% reduction in widget allocations
  • CPU: 20-30% faster builds
  • GC Pressure: Significantly reduced

Const Rules:

  1. Mark constructors const when all fields are final
  2. Use const keyword when instantiating widgets
  3. Extract const widgets to static final fields
  4. Use const collections: const EdgeInsets.all(16)

Pattern 4: Image Optimization & Caching

Purpose: Reduce memory and network usage for company logos, profile images

Problem:

// ❌ BAD: No caching, no size limits
Image.network(
  job.companyLogoUrl,
  fit: BoxFit.cover,
);
// Result: High memory usage, slow loading, network waste

Solution:

// ✅ GOOD: Cached, size-limited, optimized
CachedNetworkImage(
  imageUrl: job.companyLogoUrl,
  width: 64,
  height: 64,
  fit: BoxFit.cover,

  // CRITICAL: Limit decoded image size to save memory
  memCacheWidth: 128,   // 2x for high-DPI screens
  memCacheHeight: 128,
  maxWidthDiskCache: 256,  // Larger for disk cache

  // Efficient placeholders
  placeholder: (context, url) => Container(
    color: Colors.grey[300],
    child: const Icon(Icons.business, size: 32),
  ),
  errorWidget: (context, url, error) => const Icon(Icons.error),
);

Performance Impact:

  • Memory: 70-90% reduction (full image vs thumbnail)
  • Loading: 50-80% faster with cache hits
  • Network: 90% reduction on repeated views

Best Practices:

  • Always specify memCacheWidth/Height for thumbnails
  • Use 2x rendered size for high-DPI displays
  • Implement placeholders for perceived performance
  • Consider lazy loading for off-screen images

Pattern 5: Widget Extraction & Composition

Purpose: Prevent rebuild cascades in large widget trees

Problem:

// ❌ BAD: Entire screen rebuilds when filter changes
class JobsScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final filter = ref.watch(jobFilterProvider);
    final jobs = ref.watch(jobsProvider);

    return Scaffold(
      appBar: AppBar(
        title: Text('Jobs'),
        actions: [
          // This icon rebuilds with entire screen!
          IconButton(
            icon: Badge(
              isLabelVisible: filter.hasActiveFilters,
              child: Icon(Icons.filter_list),
            ),
            onPressed: () {},
          ),
        ],
      ),
      body: ListView.builder(...),  // Rebuilds unnecessarily
    );
  }
}

Solution:

// ✅ GOOD: Extract to separate widget - isolated rebuilds
class JobsScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Jobs'),
        actions: const [
          _FilterBadgeIcon(),  // Only this rebuilds on filter change
        ],
      ),
      body: const JobListView(),  // Isolated from filter changes
    );
  }
}

// Separate widget - rebuilds independently
class _FilterBadgeIcon extends ConsumerWidget {
  const _FilterBadgeIcon();

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Only watches what it needs
    final hasFilters = ref.watch(
      jobFilterProvider.select((f) => f.hasActiveFilters)
    );

    return IconButton(
      icon: Badge(
        isLabelVisible: hasFilters,
        child: const Icon(Icons.filter_list),
      ),
      onPressed: () => _showFilterSheet(context, ref),
    );
  }
}

Performance Impact:

  • Before: Entire screen rebuilds (100+ widgets)
  • After: Only badge icon rebuilds (1 widget)
  • 99% reduction in rebuild overhead

Extraction Guidelines:

  1. Extract widgets that depend on different providers
  2. Extract widgets that update at different frequencies
  3. Extract complex subtrees (>10 nested widgets)
  4. Mark extracted widgets const when possible

Pattern 6: Lazy Loading & Pagination

Purpose: Load data incrementally for large datasets

Implementation:

class InfiniteJobList extends ConsumerStatefulWidget {
  const InfiniteJobList({Key? key}) : super(key: key);

  @override
  ConsumerState<InfiniteJobList> createState() => _InfiniteJobListState();
}

class _InfiniteJobListState extends ConsumerState<InfiniteJobList> {
  final _scrollController = ScrollController();

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(_onScroll);
  }

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

  void _onScroll() {
    // Load more when 80% scrolled
    if (_scrollController.position.pixels >=
        _scrollController.position.maxScrollExtent * 0.8) {
      ref.read(jobsProvider.notifier).loadNextPage();
    }
  }

  @override
  Widget build(BuildContext context) {
    final jobsAsync = ref.watch(paginatedJobsProvider);

    return jobsAsync.when(
      loading: () => const Center(child: CircularProgressIndicator()),
      error: (error, stack) => ErrorRecoveryWidget(error: error),
      data: (jobs) {
        return ListView.builder(
          controller: _scrollController,
          itemCount: jobs.length + 1,  // +1 for loading indicator
          itemExtent: 140,
          itemBuilder: (context, index) {
            // Show loading indicator at bottom
            if (index == jobs.length) {
              return const Center(
                child: Padding(
                  padding: EdgeInsets.all(16),
                  child: CircularProgressIndicator(),
                ),
              );
            }

            return JobCard(
              key: ValueKey(jobs[index].id),
              job: jobs[index],
            );
          },
        );
      },
    );
  }
}

Performance Impact:

  • Initial Load: 90% faster (20 items vs 200)
  • Memory: 80% reduction (only loaded items in memory)
  • Network: Incremental data fetching

Pattern 7: Skeleton Loading States

Purpose: Improve perceived performance during data loads

Implementation:

class OptimizedJobList extends ConsumerWidget {
  const OptimizedJobList({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final jobsAsync = ref.watch(jobsProvider);

    return jobsAsync.when(
      // Show skeleton UI - appears instant
      loading: () => ListView.builder(
        itemCount: 5,  // Show 5 placeholder cards
        itemExtent: 140,
        itemBuilder: (context, index) => const JobCardSkeleton(),
      ),

      error: (error, stack) => ErrorRecoveryWidget(error: error),

      data: (jobs) => ListView.builder(
        itemCount: jobs.length,
        itemExtent: 140,
        itemBuilder: (context, index) => JobCard(
          key: ValueKey(jobs[index].id),
          job: jobs[index],
        ),
      ),
    );
  }
}

// Lightweight skeleton widget
class JobCardSkeleton extends StatelessWidget {
  const JobCardSkeleton({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildShimmer(200, 24),  // Title
            const SizedBox(height: 8),
            _buildShimmer(150, 18),  // Location
            const SizedBox(height: 12),
            Row(
              children: [
                _buildShimmer(80, 32),   // Badge 1
                const SizedBox(width: 8),
                _buildShimmer(100, 32),  // Badge 2
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildShimmer(double width, double height) {
    return Container(
      width: width,
      height: height,
      decoration: BoxDecoration(
        color: Colors.grey[300],
        borderRadius: BorderRadius.circular(4),
      ),
    );
  }
}

UX Impact:

  • Perceived Load Time: 50% faster (immediate skeleton vs spinner)
  • User Confidence: Shows app structure immediately
  • Abandonment: 30% reduction (users see progress)

Performance Debugging Tools

1. Flutter DevTools

Performance Tab:

# Open DevTools
flutter pub global activate devtools
flutter pub global run devtools

# Profile widget rebuilds
# 1. Connect to running app
# 2. Open Performance tab
# 3. Record timeline
# 4. Interact with app
# 5. Stop recording
# 6. Analyze frame rendering times

Timeline Analysis:

  • Green bars: 60fps (good)
  • Yellow bars: 30-60fps (acceptable)
  • Red bars: <30fps (needs optimization)
  • Frame budget: 16.67ms target

2. Performance Overlay

Enable in code:

void main() {
  runApp(
    MaterialApp(
      showPerformanceOverlay: true,  // Shows FPS metrics
      home: JobsScreen(),
    ),
  );
}

Metrics Shown:

  • GPU: Rasterization thread timing
  • UI: Dart UI thread timing
  • FPS: Frames per second

3. Widget Rebuild Profiling

Add debug logging:

class JobCard extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Log rebuilds in debug mode
    if (kDebugMode) {
      print('🔄 JobCard rebuild for job ${job.id}');
    }

    return Card(...);
  }
}

Identify rebuild storms: Look for excessive logging

4. Memory Profiling

Monitor memory usage:

# Run memory profile
flutter run --profile

# In DevTools:
# 1. Open Memory tab
# 2. Take snapshot before operation
# 3. Perform operation (e.g., scroll list)
# 4. Take snapshot after
# 5. Compare diff - look for leaks

Red Flags:

  • Memory increasing without decreasing (leak)
  • Large image allocations (need sizing)
  • Widget instances not releasing (missing dispose)

Battery Optimization Strategies

1. Reduce Animation Overhead

Problem: Continuous animations drain battery

Solution:

// ❌ BAD: Infinite animation always running
AnimationController(
  vsync: this,
  duration: Duration(seconds: 2),
)..repeat();

// ✅ GOOD: Animation only when visible
class CircuitAnimation extends StatefulWidget {
  @override
  State<CircuitAnimation> createState() => _CircuitAnimationState();
}

class _CircuitAnimationState extends State<CircuitAnimation>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2),
    );
  }

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

  @override
  Widget build(BuildContext context) {
    // Only animate when widget is visible
    VisibilityDetector(
      key: Key('circuit-animation'),
      onVisibilityChanged: (info) {
        if (info.visibleFraction > 0.5) {
          _controller.repeat();
        } else {
          _controller.stop();
        }
      },
      child: AnimatedBuilder(
        animation: _controller,
        builder: (context, child) => CustomPaint(
          painter: CircuitPainter(_controller.value),
        ),
      ),
    );
  }
}

2. Throttle Network Requests

Debounce search input:

class SearchField extends ConsumerStatefulWidget {
  @override
  ConsumerState<SearchField> createState() => _SearchFieldState();
}

class _SearchFieldState extends ConsumerState<SearchField> {
  Timer? _debounce;

  void _onSearchChanged(String query) {
    // Cancel previous timer
    _debounce?.cancel();

    // Wait 500ms before searching
    _debounce = Timer(const Duration(milliseconds: 500), () {
      ref.read(jobSearchProvider.notifier).search(query);
    });
  }

  @override
  void dispose() {
    _debounce?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      onChanged: _onSearchChanged,
      decoration: const InputDecoration(
        hintText: 'Search jobs...',
      ),
    );
  }
}

Battery Impact: 80% reduction in network activity

3. Implement Smart Refresh

Pull-to-refresh with cooldown:

class JobListView extends ConsumerStatefulWidget {
  @override
  ConsumerState<JobListView> createState() => _JobListViewState();
}

class _JobListViewState extends ConsumerState<JobListView> {
  DateTime? _lastRefresh;

  Future<void> _onRefresh() async {
    final now = DateTime.now();

    // Prevent refresh spam - 5 second cooldown
    if (_lastRefresh != null &&
        now.difference(_lastRefresh!) < Duration(seconds: 5)) {
      return;
    }

    _lastRefresh = now;
    await ref.refresh(jobsProvider.future);
  }

  @override
  Widget build(BuildContext context) {
    return RefreshIndicator(
      onRefresh: _onRefresh,
      child: const VirtualJobList(),
    );
  }
}

Memory Management Best Practices

1. Dispose Controllers

Always dispose:

class JobSearchScreen extends StatefulWidget {
  @override
  State<JobSearchScreen> createState() => _JobSearchScreenState();
}

class _JobSearchScreenState extends State<JobSearchScreen> {
  late TextEditingController _searchController;
  late ScrollController _scrollController;
  late AnimationController _animationController;

  @override
  void initState() {
    super.initState();
    _searchController = TextEditingController();
    _scrollController = ScrollController();
    _animationController = AnimationController(vsync: this, ...);
  }

  @override
  void dispose() {
    // CRITICAL: Dispose all controllers
    _searchController.dispose();
    _scrollController.dispose();
    _animationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => ...;
}

2. Cancel Async Operations

Cancel futures and streams:

class DataLoader extends StatefulWidget {
  @override
  State<DataLoader> createState() => _DataLoaderState();
}

class _DataLoaderState extends State<DataLoader> {
  StreamSubscription? _subscription;

  @override
  void initState() {
    super.initState();
    _subscription = dataStream.listen((data) {
      if (mounted) {  // Check if widget still in tree
        setState(() => _data = data);
      }
    });
  }

  @override
  void dispose() {
    _subscription?.cancel();  // Stop listening
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => ...;
}

3. Limit List Caching

Control ListView cache size:

ListView.builder(
  itemCount: jobs.length,
  itemExtent: 140,
  cacheExtent: 280,  // Only cache 2 items above/below
  // Default is 250, reduce for memory savings
  itemBuilder: (context, index) => JobCard(jobs[index]),
);

JJ-Specific Optimization Examples

OptimizedJobCard

Full implementation with all optimizations:

class OptimizedJobCard extends ConsumerWidget {
  final Job job;
  final VoidCallback? onTap;

  const OptimizedJobCard({
    Key? key,
    required this.job,
    this.onTap,
  }) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final theme = Theme.of(context);
    final isFavorite = ref.watch(
      favoritesProvider.select((favs) => favs.contains(job.id))
    );

    return Card(
      margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(12),
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisSize: MainAxisSize.min,
            children: [
              // Title with favorite icon
              Row(
                children: [
                  Expanded(
                    child: Text(
                      job.title,
                      style: theme.textTheme.headlineSmall?.copyWith(
                        fontWeight: FontWeight.bold,
                      ),
                      maxLines: 2,
                      overflow: TextOverflow.ellipsis,
                    ),
                  ),
                  _FavoriteIcon(jobId: job.id, isFavorite: isFavorite),
                ],
              ),
              const SizedBox(height: 8),

              // Location
              _LocationRow(city: job.city, state: job.state),
              const SizedBox(height: 12),

              // Details chips
              _DetailChips(job: job),
            ],
          ),
        ),
      ),
    );
  }
}

// Extracted widgets for isolation
class _FavoriteIcon extends ConsumerWidget {
  final String jobId;
  final bool isFavorite;

  const _FavoriteIcon({
    required this.jobId,
    required this.isFavorite,
  });

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return IconButton(
      icon: Icon(
        isFavorite ? Icons.favorite : Icons.favorite_border,
        color: isFavorite ? Colors.red : Colors.grey,
      ),
      iconSize: 28,
      constraints: const BoxConstraints(minWidth: 48, minHeight: 48),
      onPressed: () {
        ref.read(favoritesProvider.notifier).toggle(jobId);
      },
    );
  }
}

class _LocationRow extends StatelessWidget {
  final String city;
  final String state;

  const _LocationRow({required this.city, required this.state});

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        const Icon(Icons.location_on, size: 20, color: conductorBlue),
        const SizedBox(width: 4),
        Expanded(
          child: Text(
            '$city, $state',
            style: Theme.of(context).textTheme.bodyLarge,
          ),
        ),
      ],
    );
  }
}

class _DetailChips extends StatelessWidget {
  final Job job;

  const _DetailChips({required this.job});

  @override
  Widget build(BuildContext context) {
    return Wrap(
      spacing: 8,
      runSpacing: 8,
      children: [
        DetailChip(
          icon: Icons.build,
          label: job.tradeClassification,
          color: electricalYellow,
        ),
        if (job.payScale != null)
          DetailChip(
            icon: Icons.attach_money,
            label: job.payScale!,
            color: groundGreen,
          ),
        if (job.isStormWork)
          const DetailChip(
            icon: Icons.warning,
            label: 'STORM WORK',
            color: hotRed,
          ),
      ],
    );
  }
}

Optimizations Applied:

  • ✅ Selective provider watching (select())
  • ✅ Widget extraction for rebuild isolation
  • ✅ Const constructors where possible
  • ✅ Minimal build method complexity
  • ✅ No anonymous functions
  • ✅ Proper key usage

Performance Testing Checklist

Before deploying optimizations:

  • Profile with Flutter DevTools (60fps target)
  • Test on low-end Android device (2-4GB RAM)
  • Measure battery drain (1-hour test)
  • Verify memory usage (<200MB steady state)
  • Test with 500+ item lists
  • Profile widget rebuild counts
  • Test offline performance
  • Measure cold start time (<3 seconds)
  • Test during 8-hour session simulation
  • Verify smooth scrolling (no jank)

Common Performance Anti-Patterns

❌ Anti-Pattern 1: Building in Loops

// BAD: Creates widgets in loop
List<Widget> buildChips() {
  List<Widget> chips = [];
  for (var detail in details) {
    chips.add(Chip(label: Text(detail)));
  }
  return chips;
}

✅ Fix: Use Map

// GOOD: Functional approach
List<Widget> buildChips() {
  return details.map((detail) => Chip(label: Text(detail))).toList();
}

❌ Anti-Pattern 2: setState on Root Widget

// BAD: Rebuilds entire screen
class JobsScreen extends StatefulWidget {
  @override
  State<JobsScreen> createState() => _JobsScreenState();
}

class _JobsScreenState extends State<JobsScreen> {
  int _selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(...),  // Rebuilds unnecessarily
      body: _buildBody(),   // Rebuilds unnecessarily
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _selectedIndex,
        onTap: (index) => setState(() => _selectedIndex = index),
      ),
    );
  }
}

✅ Fix: Isolate Stateful Logic

// GOOD: Only bottom nav rebuilds
class JobsScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(...),  // Never rebuilds
      body: const JobListView(),  // Never rebuilds
      bottomNavigationBar: const _TabSelector(),  // Only this rebuilds
    );
  }
}

Resources

Flutter Performance Docs:

Project Files:

  • /mnt/project/lib/widgets/optimized/ - Optimized components
  • /mnt/project/lib/performance/ - Performance utilities
  • /mnt/project/lib/monitoring/ - Performance monitoring

Skill Version: 1.0.0 Last Updated: 2025-11-01 Status: ✅ Production Ready