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

A powerful static analysis tool for Flutter that identifies performance issues, best practice violations, and optimization opportunities in your widget tree. Includes const detection, nesting analysis [...]

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
0
points
81
downloads

Publisher

unverified uploader

Weekly Downloads

A powerful static analysis tool for Flutter that identifies performance issues, best practice violations, and optimization opportunities in your widget tree. Includes const detection, nesting analysis, performance checks, and actionable suggestions.

Repository (GitHub)
View/report issues

Documentation

Documentation

Funding

Consider supporting this project:

github.com
www.buymeacoffee.com

License

unknown (license)

Dependencies

flutter

More

Packages that depend on flutter_ui_guard