flutter_aop 0.0.3+4
flutter_aop: ^0.0.3+4 copied to clipboard
Annotation driven proxies to add lightweight AOP style hooks to Flutter code.
flutter_aop #
Annotation driven proxies that bring small, framework free AOP style hooks to Flutter and Dart code.
Mark methods with @Aop and let build_runner generate proxy classes that run your logic before, after, or when an error happens.
Features #
- Simple
@Aopannotation withbefore,after,onError, andtagoptions. - Generated
*.aop.dartfiles expose proxy classes (e.g.LoginServiceAopProxy) you can drop in wherever the original class is used. - Runtime
AopHooksand a globalAopRegistrylet you attach logging, tracing, guards, or analytics without touching the original implementation. - Works with both synchronous and asynchronous (
Future) methods and reports invocation details through anAopContext.
Getting started #
Add flutter_aop to your pubspec.yaml and run flutter pub get.
dependencies:
flutter_aop:
path: ../flutter_aop # or use your hosted location
dev_dependencies:
build_runner: ^2.10.4
Every library that wants AOP support must include the generated part file:
import 'package:flutter_aop/flutter_aop.dart';
part 'login_service.aop.dart';
Usage #
- Annotate the methods that should trigger hooks.
- Declare aspect classes with
@Aspect,@Before,@After, or@OnErrorand include their.aop.dartparts. - Run
dart run build_runner build --delete-conflicting-outputs(orflutter pub run ...) to generate both the.aop.dartfiles and the aggregatedflutter_aop_bootstrap.g.dart. - Import the generated
runFlutterAopBootstrap()(e.g.import 'package:my_app/flutter_aop_bootstrap.g.dart';) and call it once during startup—this registers every proxy/aspect and internally callsensureAllAopInitialized(). - Wrap your concrete instances with
aopWrap(instance)(no need to touch the generated proxy class directly).
import 'package:flutter_aop/flutter_aop.dart';
import 'package:my_app/flutter_aop_bootstrap.g.dart'; // generated by build_runner
part 'login_service.aop.dart';
class LoginService {
@Aop(tag: 'auth', description: 'track login')
Future<void> login(String id, String password) async {
// original implementation
}
}
@Aspect(tag: 'auth')
class LoggingAspect {
const LoggingAspect();
@Before()
void logBefore(AopContext context) =>
print('Entering ${context.methodName} -> ${context.positionalArguments}');
@After()
void logAfter(AopContext context) =>
print('Result for ${context.methodName}: ${context.result}');
@OnError()
void logError(AopContext context) => print('Error: ${context.error}');
}
Future<void> main() async {
runFlutterAopBootstrap(); // call once during startup
final service = aopWrap(LoginService());
await service.login('gmail', '1234');
}
Need global hooks? Register them once anywhere in your app:
AopRegistry.instance.register(
AopHooks(before: (ctx) => debugPrint('[${ctx.annotation.tag}] ${ctx.methodName}')),
tag: 'auth',
);
Using tags keeps large projects organized—you decide which hook handles which annotated method.
Example project #
The example/ directory contains a tiny console-style demo that wires hooks into a LoginService, registers aspects via GetIt/injectable, and prints every lifecycle event.
cd example
flutter pub get
dart run build_runner build --delete-conflicting-outputs
dart run lib/main.dart
Open example/lib/login_service.dart to see the annotated class, the generated example/lib/login_service.aop.dart proxy, and example/lib/flutter_aop_bootstrap.g.dart for the aggregated bootstrap file.
Dependency injection (GetIt/Injectable) #
When using GetIt together with injectable, register wrapped services in a module and simply call the generated bootstrapper before getIt.init():
final getIt = GetIt.instance;
@InjectableInit()
Future<void> configureDependencies() async {
runFlutterAopBootstrap();
getIt.init();
}
@module
abstract class ServiceModule {
@lazySingleton
LoginService loginService() => aopWrap(LoginService());
}
The example app (example/lib/di.dart) shows the full setup, including how to resolve the proxied service from GetIt.
Generated output #
Running build_runner creates *.aop.dart part files next to your source:
lib/
├─ login_service.dart
└─ login_service.aop.dart <-- generated
Every class with at least one @Aop method receives a proxy and registers itself inside AopProxyRegistry. You rarely need the proxy class directly—just call aopWrap(Service()) (or AopProxyRegistry.instance.wrap(Service(), hooks: ...)) to receive the instrumented instance. All generated bootstrap functions are aggregated into flutter_aop_bootstrap.g.dart; call runFlutterAopBootstrap() to execute them once and ensureAllAopInitialized() is invoked automatically at the end.
Tips #
- Hooks for synchronous methods must be synchronous too. If you need async work (e.g. writing to storage), mark the original method
asyncso the proxy can await your hook. positionalArgumentsandnamedArgumentsinsideAopContextgive you the exact values passed to the method.- The optional
descriptionfield is never used by the runtime but is emitted in generated comments—handy when you read the.aop.dartfile.
Running the generator #
dart run build_runner build --delete-conflicting-outputs
Use watch while developing to regenerate proxies/bootstraps when files change:
dart run build_runner watch
Contributing #
Issues and ideas are welcome! File bugs or feature requests in the repository issue tracker. If you send a PR, please include tests (flutter test) and run flutter pub run build_runner build so the generated code stays in sync.