Code Formatter Package

A powerful Flutter package for splitting large Dart files containing multiple widgets into separate, well-organized files.

pub package License: MIT

Features

  • Widget Extraction: Automatically extracts StatelessWidget and StatefulWidget classes from your code
  • State Class Bundling: Automatically includes the corresponding State class with each StatefulWidget
  • Import Preservation: Preserves all import statements from the original file
  • Smart Naming: Uses widget class names for file naming (PascalCase to snake_case conversion)
  • Flexible Configuration: Customize output directory, file names, and more
  • Detailed Results: Returns comprehensive results with success/failure status and warnings
  • Async Support: Provides both sync and async methods for file operations
  • Error Handling: Proper error types and messages for debugging

Installation

Add this to your pubspec.yaml:

dependencies:
  code_formatter_package: ^1.0.0

Then run:

flutter pub get

Usage

Basic Usage - Format Code from String

import 'package:code_formatter_package/code_formatter_package.dart';

void main() {
  const code = '''
import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: Center(child: Text('Hello World')),
    );
  }
}

class ProfileScreen extends StatefulWidget {
  @override
  State<ProfileScreen> createState() => _ProfileScreenState();
}

class _ProfileScreenState extends State<ProfileScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text('Profile')),
    );
  }
}
''';

  final result = CodeFormatter.formatCode(code);

  if (result.success) {
    print('Successfully extracted ${result.widgets.length} widgets:');
    for (final widget in result.widgets) {
      print('  - ${widget.className} -> ${widget.savedPath}');
    }
  } else {
    print('Error: ${result.errorMessage}');
  }
}

This will create:

  • lib/generated_widgets/home_screen.dart
  • lib/generated_widgets/profile_screen.dart (includes _ProfileScreenState class)

Format Code from File

final result = CodeFormatter.formatFile('lib/screens/all_screens.dart');

if (result.success) {
  print('Extracted ${result.widgets.length} widgets');
} else {
  print('Error: ${result.errorMessage}');
}

Using Configuration Options

final result = CodeFormatter.formatCode(
  myCode,
  config: FormatterConfig(
    outputDir: 'lib/widgets/generated',
    useClassNameAsFileName: true,
    includeFlutterImports: true,
    includeHeaderComment: true,
    overwriteExisting: false,
  ),
);

Custom File Names

final result = CodeFormatter.formatCode(
  myCode,
  config: FormatterConfig(
    customNames: ['home_widget', 'profile_widget'],
    outputDir: 'lib/custom_widgets',
  ),
);

Async File Formatting

final result = await CodeFormatter.formatFileAsync(
  'lib/screens/large_file.dart',
  config: FormatterConfig(outputDir: 'lib/widgets'),
);

Preview Widgets Without Saving

final widgets = CodeFormatter.previewWidgets(myCode);

for (final widget in widgets) {
  print('Found widget: ${widget.className}');
  print('Is StatefulWidget: ${widget.isStateful}');
  if (widget.associatedClasses.isNotEmpty) {
    print('Associated classes: ${widget.associatedClasses.join(", ")}');
  }
}

Flutter App Integration

class MyApp extends StatefulWidget {
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();

    CodeFormatter.initializeFormatter(
      'lib/screens/home.dart',
      config: FormatterConfig(outputDir: 'lib/widgets'),
      onComplete: (result) {
        if (result.success) {
          print('Formatted ${result.widgets.length} widgets');
        } else {
          print('Error: ${result.errorMessage}');
        }
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: HomeScreen());
  }
}

Configuration Options

Option Type Default Description
outputDir String 'lib/generated_widgets' Output directory for generated files
customNames List<String>? null Custom file names for generated widgets
useClassNameAsFileName bool true Use widget class name as file name
includeFlutterImports bool true Add Flutter imports if not present
includeHeaderComment bool true Add header comment to generated files
formatOutput bool true Format the output code
overwriteExisting bool true Overwrite existing files

API Reference

Classes

CodeFormatter

Main class with static methods for formatting code.

Methods:

  • formatCode(String code, {FormatterConfig config}) - Format code from string
  • formatFile(String filePath, {FormatterConfig config}) - Format code from file
  • formatFileAsync(String filePath, {FormatterConfig config}) - Async file formatting
  • initializeFormatter(String filePath, {FormatterConfig config, Function? onComplete}) - Flutter lifecycle integration
  • previewWidgets(String code) - Preview widgets without saving

FormatterResult

Result object returned by formatting methods.

Properties:

  • success - Whether the operation succeeded
  • widgets - List of extracted widgets
  • errorMessage - Error message if failed
  • errorType - Type of error if failed
  • warnings - List of warnings

ExtractedWidget

Represents an extracted widget.

Properties:

  • className - Name of the widget class
  • sourceCode - Full source code including imports
  • savedPath - Path where widget was saved
  • isStateful - Whether it's a StatefulWidget
  • associatedClasses - List of associated class names

FormatterConfig

Configuration options for the formatter.

CodeFormatterException

Exception thrown when formatting fails.

CodeFormatterErrorType

Enum of possible error types:

  • fileNotFound
  • fileWriteError
  • parseError
  • noWidgetsFound
  • directoryCreationError
  • invalidInput
  • unknown

Error Handling

final result = CodeFormatter.formatFile('lib/my_file.dart');

if (!result.success) {
  switch (result.errorType) {
    case CodeFormatterErrorType.fileNotFound:
      print('File not found!');
      break;
    case CodeFormatterErrorType.noWidgetsFound:
      print('No widgets in this file');
      break;
    case CodeFormatterErrorType.parseError:
      print('Could not parse the code');
      break;
    default:
      print('Error: ${result.errorMessage}');
  }
}

// Check for warnings
if (result.warnings.isNotEmpty) {
  print('Warnings:');
  for (final warning in result.warnings) {
    print('  - $warning');
  }
}

Example Output

Input file with multiple widgets:

// lib/screens/home.dart
import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget { ... }
class SettingsScreen extends StatefulWidget { ... }
class _SettingsScreenState extends State<SettingsScreen> { ... }

Output files:

lib/generated_widgets/
├── home_screen.dart      // Contains HomeScreen
└── settings_screen.dart  // Contains SettingsScreen + _SettingsScreenState

Each generated file includes:

  • Header comment with widget name
  • Required imports (Flutter + original imports)
  • Widget class definition
  • Associated State class (for StatefulWidget)

Requirements

  • Dart SDK: >=3.4.4 <4.0.0
  • Flutter: >=3.0.0

License

MIT License - see LICENSE for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Issues

If you find a bug or have a feature request, please open an issue on GitHub.

Libraries

code_formatter_package
A Flutter package for formatting and splitting Dart code into separate widget files.