Dart's built-in visibility is limited to library-private (_identifier) and public (everything else). This works for small projects, but as codebases grow, you often need finer control:
- π« Prevent accidental imports of internal utilities from parent directories
- π Keep implementation details scoped to feature modules
- π Enforce architectural boundaries within your codebase
more_visibility brings Java-style visibility modifiers and automatic directory-private enforcement to Dart, making your codebase more maintainable and preventing architectural drift.
- π― Directory-private enforcement β Underscore-prefixed directories (
_components,_hooks) are automatically restricted to same-level imports. Zero configuration. - π‘οΈ Java-style annotations β
@mprotectedand@mdefaultfor fine-grained control over symbol visibility - β‘ Real-time feedback β Analysis server plugin catches violations in your IDE as you type
- π§ Works with code generation β Auto-annotates generated files from Freezed, Riverpod, JsonSerializable, etc.
- π¨ Configurable severity β Set rules as errors, warnings, or info based on your needs
Add to your pubspec.yaml:
dependencies:
more_visibility_annotation: ^0.1.0
dev_dependencies:
more_visibility: ^0.1.10In your analysis_options.yaml:
include: package:more_visibility/more_visibility.yamlThat's it! The plugin is now active and will enforce visibility rules.
Simply prefix directories with _ to make them private to their parent directory:
lib/
βββ pages/
β βββ page.dart β
Can import _components/
β βββ _components/
β β βββ button.dart π Private to lib/pages/
β βββ profile/
β βββ profile_page.dart β Cannot import _components/
Example:
// β
lib/pages/page.dart
import '_components/button.dart'; // Same depth - OK
// β lib/pages/profile/profile_page.dart
import '../_components/button.dart'; // Different depth - ERRORNote: Only applies to your application code. Dependencies are excluded to avoid false positives.
Control individual declarations with annotations:
import 'package:more_visibility_annotation/more_visibility_annotation.dart';
// π‘οΈ Protected: accessible from this directory and subdirectories
@mprotected
final sharedConfig = Config();
// π Module-default: accessible only within this directory
@mdefault
final localHelper = Helper();File-level annotations:
@mprotected
library feature_auth;
// All declarations inherit @mprotected
class AuthService { }
final authToken = '';// lib/utils/internal_helper.dart
String formatSecret(String secret) => '***$secret***';
// lib/features/auth/login.dart
import '../../utils/internal_helper.dart'; // β οΈ Unintended coupling
// lib/main.dart
import 'utils/internal_helper.dart'; // β οΈ Internal API exposedProblems:
- No enforcement of architectural boundaries
- Internal utilities leak across module boundaries
- Difficult to refactor without breaking unknown dependents
// lib/utils/_internal/helper.dart (in private directory)
String formatSecret(String secret) => '***$secret***';
// lib/features/auth/login.dart
import '../../utils/_internal/helper.dart'; // β Compile-time error!
// Error: `formatSecret` is in a private directory `/lib/utils/_internal`
// lib/utils/public_api.dart
import '_internal/helper.dart'; // β
Same depth - OK
export '_internal/helper.dart' show allowedFunction;Benefits:
- β Architectural boundaries enforced at compile-time
- β Clear separation between public API and internal implementation
- β Refactoring is safer with explicit visibility scopes
| Topic | Description |
|---|---|
| Visibility Rules | Detailed explanation of all visibility rules |
| Usage Guide | Step-by-step usage instructions and patterns |
| Auto-annotation | Configuring builders for generated code |
analyzer:
errors:
directory_private: warning # Default: error
more_visibility_protected: info # Default: error
more_visibility_module_default: ignore # Disable completelyplugins:
more_visibility:
diagnostics:
directory_private: false # Disable directory-private rule
more_visibility_protected: false # Disable @mprotected checks// ignore_for_file: directory_private
// ignore: more_visibility_protected
import '../protected_api.dart';Works seamlessly with Freezed, Riverpod, JsonSerializable, and other code generators.
build.yaml:
post_process_builders:
more_visibility:auto_annotate:
options:
visibility: mprotected # Default annotation for generated filesGenerated files automatically inherit visibility from their source files:
// user.dart
@mprotected
@freezed
class User with _$User {
// ...
}
// user.freezed.dart (generated)
@mprotected // β Automatically added
part of 'user.dart';
// ...- Dart SDK: 3.10.0 or later
- Flutter SDK: 3.38.0 or later (if using Flutter)
Contributions are welcome! Please feel free to submit issues or pull requests.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
If you find this package useful, please consider giving it a star on GitHub!
Made with β€οΈ for the Dart & Flutter community
Documentation β’ Issues β’ Pub.dev