flutter_ui_guard 0.0.2 copy "flutter_ui_guard: ^0.0.2" to clipboard
flutter_ui_guard: ^0.0.2 copied to clipboard

A Flutter static analysis tool that detects UI performance issues, deep widget nesting, and missing best practices in widget trees.

example/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_ui_guard/flutter_ui_guard.dart';

void main() {
  runApp(const FlutterGuardDemoApp());
}

class FlutterGuardDemoApp extends StatelessWidget {
  const FlutterGuardDemoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Guard Demo',
      theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  GuardReport? _currentReport;
  String _selectedExample = 'Basic Example';

  final Map<String, Widget> _examples = {
    'Basic Example': const BasicExampleWidget(),
    'Deep Nesting': const DeepNestingWidget(),
    'Heavy Build': const HeavyBuildWidget(),
    'Container Issues': const ContainerIssuesWidget(),
    'Good Practices': const GoodPracticesWidget(),
  };

  void _analyzeWidget(String exampleName) {
    final widget = _examples[exampleName]!;
    final report = FlutterGuard.analyze(
      rootWidget: widget,
      config: const GuardConfig(verbose: false),
    );

    setState(() {
      _currentReport = report;
      _selectedExample = exampleName;
    });
  }

  @override
  void initState() {
    super.initState();
    _analyzeWidget('Basic Example');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        backgroundColor: Colors.white,
        title: const Text('đŸ›Ąī¸ Flutter Guard Demo'),
        elevation: 2,
      ),
      body: LayoutBuilder(
        builder: (context, constraints) {
          final isWideScreen = constraints.maxWidth > 800;

          if (isWideScreen) {
            return _buildWideLayout();
          } else {
            return _buildNarrowLayout();
          }
        },
      ),
    );
  }

  Widget _buildWideLayout() {
    return Row(
      children: [
        // Left sidebar - Example selector
        Container(
          width: 250,
          decoration: BoxDecoration(
            color: Colors.grey[100],
            border: Border(right: BorderSide(color: Colors.grey[300]!)),
          ),
          child: _buildSidebar(),
        ),

        // Main content area
        Expanded(child: _buildMainContent()),
      ],
    );
  }

  Widget _buildNarrowLayout() {
    return Column(
      children: [
        // Example selector dropdown
        Container(
          padding: const EdgeInsets.all(8),
          color: Colors.grey[100],
          child: DropdownButton<String>(
            value: _selectedExample,
            isExpanded: true,
            items: _examples.keys.map((name) {
              return DropdownMenuItem(value: name, child: Text(name));
            }).toList(),
            onChanged: (value) {
              if (value != null) _analyzeWidget(value);
            },
          ),
        ),

        // Main content
        Expanded(child: _buildMainContent()),
      ],
    );
  }

  Widget _buildSidebar() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Container(
          padding: const EdgeInsets.all(16),
          color: Colors.blue[50],
          child: const Text(
            'Select Example',
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
        ),
        Expanded(
          child: ListView(
            children: _examples.keys.map((name) {
              final isSelected = name == _selectedExample;
              return ListTile(
                selected: isSelected,
                selectedTileColor: Colors.blue[100],
                title: Text(name),
                trailing: isSelected
                    ? const Icon(Icons.check, color: Colors.blue)
                    : null,
                onTap: () => _analyzeWidget(name),
              );
            }).toList(),
          ),
        ),
      ],
    );
  }

  Widget _buildMainContent() {
    return Column(
      children: [
        // Widget preview
        Expanded(
          flex: 2,
          child: Container(
            decoration: BoxDecoration(
              border: Border(bottom: BorderSide(color: Colors.grey[300]!)),
            ),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Container(
                  padding: const EdgeInsets.all(16),
                  color: Colors.green[50],
                  child: Row(
                    children: [
                      const Icon(Icons.preview, color: Colors.green),
                      const SizedBox(width: 8),
                      Expanded(
                        child: Text(
                          'Preview: $_selectedExample',
                          style: const TextStyle(
                            fontSize: 16,
                            fontWeight: FontWeight.bold,
                          ),
                          overflow: TextOverflow.ellipsis,
                        ),
                      ),
                    ],
                  ),
                ),
                Expanded(
                  child: Center(
                    child: SingleChildScrollView(
                      padding: const EdgeInsets.all(24),
                      child: _examples[_selectedExample],
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),

        // Analysis report
        Expanded(
          flex: 3,
          child: Container(
            color: Colors.grey[50],
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Container(
                  padding: const EdgeInsets.all(16),
                  color: Colors.orange[50],
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Row(
                        children: [
                          Icon(Icons.analytics, color: Colors.orange),
                          SizedBox(width: 8),
                          Expanded(
                            child: Text(
                              'Analysis Report',
                              style: TextStyle(
                                fontSize: 16,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                          ),
                        ],
                      ),
                      if (_currentReport != null) ...[
                        const SizedBox(height: 8),
                        Wrap(
                          spacing: 8,
                          runSpacing: 8,
                          children: [
                            _buildStatChip(
                              'Widgets',
                              _currentReport!.totalWidgets.toString(),
                              Colors.blue,
                            ),
                            _buildStatChip(
                              'Issues',
                              _currentReport!.issues.length.toString(),
                              _currentReport!.issues.isEmpty
                                  ? Colors.green
                                  : Colors.red,
                            ),
                            _buildStatChip(
                              'Depth',
                              _currentReport!.maxDepth.toString(),
                              Colors.purple,
                            ),
                          ],
                        ),
                      ],
                    ],
                  ),
                ),
                Expanded(
                  child: _currentReport == null
                      ? const Center(child: CircularProgressIndicator())
                      : _buildReportView(_currentReport!),
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }

  Widget _buildStatChip(String label, String value, Color color) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
      decoration: BoxDecoration(
        color: color.withValues(alpha: 0.1),
        borderRadius: BorderRadius.circular(16),
        border: Border.all(color: color.withValues(alpha: 0.3)),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Text(
            label,
            style: TextStyle(
              fontSize: 12,
              color: color.withValues(alpha: .7),
              fontWeight: FontWeight.w500,
            ),
          ),
          const SizedBox(width: 4),
          Text(
            value,
            style: TextStyle(
              fontSize: 14,
              color: color.withValues(alpha: .9),
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildReportView(GuardReport report) {
    if (report.issues.isEmpty) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(Icons.check_circle, size: 64, color: Colors.green[400]),
            const SizedBox(height: 16),
            const Text(
              '✅ No Issues Found!',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            Text(
              'Your widget tree looks great!',
              style: TextStyle(fontSize: 16, color: Colors.grey[600]),
            ),
          ],
        ),
      );
    }

    return ListView.builder(
      padding: const EdgeInsets.all(16),
      itemCount: report.issues.length,
      itemBuilder: (context, index) {
        final issue = report.issues[index];
        return _buildIssueCard(issue);
      },
    );
  }

  Widget _buildIssueCard(GuardIssue issue) {
    final severityData = _getSeverityData(issue.severity);

    return Card(
      margin: const EdgeInsets.only(bottom: 12),
      elevation: 2,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Container(
                  padding: const EdgeInsets.all(8),
                  decoration: BoxDecoration(
                    color: severityData['color'].withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Text(
                    severityData['icon'],
                    style: const TextStyle(fontSize: 20),
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        issue.widgetType,
                        style: const TextStyle(
                          fontWeight: FontWeight.bold,
                          fontSize: 16,
                        ),
                      ),
                      const SizedBox(height: 4),
                      Text(
                        issue.message,
                        style: TextStyle(color: Colors.grey[700], fontSize: 14),
                      ),
                    ],
                  ),
                ),
                Container(
                  padding: const EdgeInsets.symmetric(
                    horizontal: 8,
                    vertical: 4,
                  ),
                  decoration: BoxDecoration(
                    color: severityData['color'].withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(12),
                    border: Border.all(
                      color: severityData['color'].withValues(alpha: 0.3),
                    ),
                  ),
                  child: Text(
                    severityData['label'],
                    style: TextStyle(
                      color: severityData['color'],
                      fontSize: 12,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
              ],
            ),
            if (issue.suggestion != null) ...[
              const SizedBox(height: 12),
              Container(
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: Colors.blue[50],
                  borderRadius: BorderRadius.circular(8),
                  border: Border.all(color: Colors.blue[200]!),
                ),
                child: Row(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Icon(Icons.lightbulb, color: Colors.orange, size: 20),
                    const SizedBox(width: 8),
                    Expanded(
                      child: Text(
                        issue.suggestion!,
                        style: TextStyle(color: Colors.blue[900], fontSize: 13),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ],
        ),
      ),
    );
  }

  Map<String, dynamic> _getSeverityData(IssueSeverity severity) {
    switch (severity) {
      case IssueSeverity.info:
        return {'icon': 'â„šī¸', 'label': 'INFO', 'color': Colors.blue};
      case IssueSeverity.warning:
        return {'icon': 'âš ī¸', 'label': 'WARNING', 'color': Colors.orange};
      case IssueSeverity.error:
        return {'icon': '❌', 'label': 'ERROR', 'color': Colors.red};
      case IssueSeverity.critical:
        return {'icon': '🔴', 'label': 'CRITICAL', 'color': Colors.purple};
    }
  }
}

// Example Widgets
class BasicExampleWidget extends StatelessWidget {
  const BasicExampleWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Text('Hello Flutter Guard'), // Missing const
        const SizedBox(height: 8),
        const Text('This is const'), // Good!
        const SizedBox(height: 8),
        Text('Another text'), // Missing const
      ],
    );
  }
}

class DeepNestingWidget extends StatelessWidget {
  const DeepNestingWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Column(
          children: [
            Column(
              children: [
                Column(
                  children: [
                    Column(
                      children: [
                        Column(
                          children: [
                            Column(
                              children: [
                                Container(
                                  padding: const EdgeInsets.all(8),
                                  color: Colors.red[100],
                                  child: const Text('Too Deep!'),
                                ),
                              ],
                            ),
                          ],
                        ),
                      ],
                    ),
                  ],
                ),
              ],
            ),
          ],
        ),
      ],
    );
  }
}

class HeavyBuildWidget extends StatelessWidget {
  const HeavyBuildWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Wrap(
      spacing: 8,
      runSpacing: 8,
      children: List.generate(
        12,
        (i) => Container(
          padding: const EdgeInsets.all(12),
          decoration: BoxDecoration(
            color: Colors.blue[100],
            borderRadius: BorderRadius.circular(8),
          ),
          child: Text('Item $i'),
        ),
      ),
    );
  }
}

class ContainerIssuesWidget extends StatelessWidget {
  const ContainerIssuesWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Container(), // Empty container
        const SizedBox(height: 8),
        Container(
          width: 100,
          height: 50,
          color: Colors.green[200],
        ), // Should use SizedBox
        const SizedBox(height: 8),
        Opacity(
          opacity: 0.5,
          child: Container(
            padding: const EdgeInsets.all(16),
            color: Colors.orange[200],
            child: const Text('Expensive Opacity'),
          ),
        ),
      ],
    );
  }
}

class GoodPracticesWidget extends StatelessWidget {
  const GoodPracticesWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: const [
        Text('All const!'), // Still missing const here for demo
        SizedBox(height: 8),
        Icon(Icons.check_circle, color: Colors.green),
        SizedBox(height: 8),
        Padding(padding: EdgeInsets.all(16), child: Text('Good practices')),
      ],
    );
  }
}
5
likes
145
points
81
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter static analysis tool that detects UI performance issues, deep widget nesting, and missing best practices in widget trees.

Repository (GitHub)
View/report issues
Contributing

Documentation

Documentation
API reference

Funding

Consider supporting this project:

github.com
buymeacoffee.com

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flutter_ui_guard