Mobile Development 13 min read

AspectD – A Dart AOP Framework for Flutter

AspectD is a compile‑time Aspect‑Oriented Programming framework for Dart that traverses Flutter’s dill intermediate files to inject, call, or execute advice code, allowing non‑intrusive instrumentation, logging, and UI event injection without modifying source, and integrates into the build pipeline via a toolchain patch.

Xianyu Technology
Xianyu Technology
Xianyu Technology
AspectD – A Dart AOP Framework for Flutter

With the rapid growth of the Flutter framework, many projects adopt it for rebuilding or creating new products. While Flutter offers high development efficiency, excellent performance, and good cross‑platform support, it also suffers from missing or incomplete plugins, basic capabilities, and underlying framework features.

In a real‑world scenario of automated recording and playback, developers had to modify the Dart layer of the Flutter framework, which introduced invasive changes. To reduce maintenance cost and avoid framework intrusion, the team turned to Aspect‑Oriented Programming (AOP).

This article introduces AspectD , an AOP framework for Dart developed by the Xianyu technical team.

Why AOP for Dart?

In iOS, Objective‑C provides a powerful runtime that makes runtime AOP easy. In Android, Java allows both compile‑time (AspectJ) and runtime (Spring AOP) approaches. Dart, however, has very limited reflection – it only supports introspection, not modification – and Flutter disables reflection for size and robustness reasons. Therefore, AspectD implements a compile‑time AOP solution.

Design Overview

PointCut describes where (call or execute), which library, class, and method the AOP logic should be applied to. Its data structure includes source information, target object, function name, stub ID, positional and named parameters.

class PointCut {
  final Map
sourceInfos;
  final Object target;
  final String function;
  final String stubId;
  final List
positionalParams;
  final Map
namedParams;
}

The @pragma('vm:entry-point') annotation is required to prevent tree‑shaking from discarding the injected code during AOT compilation.

Advice methods receive a PointCut instance, can call pointcut.proceed() to invoke the original logic, and may return a result.

Future
getCurTime(PointCut pointcut) async {
  // custom logic
  var result = await pointcut.proceed();
  return result;
}

Aspect classes are marked with @Aspect and contain annotated advice methods. Removing the @Aspect annotation disables the whole AOP block.

@Aspect()
@pragma('vm:entry-point')
class ExecuteDemo {
  @Execute('package:app/calculator.dart', 'Calculator', '-getCurTime')
  @pragma('vm:entry-point')
  Future
getCurTime(PointCut pointcut) async { ... }
}

Dill Transformation

Flutter’s compiled output is a dill (Dart Intermediate Language) file. AspectD traverses the dill AST, identifies target methods using the metadata from @Aspect , and injects or replaces code accordingly. A simplified visitor example:

@override
MethodInvocation visitMethodInvocation(MethodInvocation node) {
  // locate the target procedure
  // generate a unique key
  // retrieve AspectD info
  // apply Call/Execute transformation
  return transformedNode;
}

This approach enables fine‑grained manipulation of compiled Dart code without altering source files.

Supported Syntax

AspectD provides three unified AOP primitives:

Call : intercept a method call point.

Execute : intercept the execution point of a method.

Inject : insert custom statements at a specific line number inside a method (useful for cases where Call/Execute cannot access private members).

Example of a Call advice:

@Aspect()
@pragma('vm:entry-point')
class CallDemo {
  @Call('package:app/calculator.dart', 'Calculator', '-getCurTime')
  @pragma('vm:entry-point')
  Future
getCurTime(PointCut pointcut) async {
    print('Aspectd:KWLM02');
    var result = await pointcut.proceed();
    return result;
  }
}

Example of an Inject advice that adds logic after a specific line in GestureDetector.build :

@Aspect()
@pragma('vm:entry-point')
class InjectDemo {
  @Inject('package:flutter/src/widgets/gesture_detector.dart',
          'GestureDetector', '-build', lineNum: 452)
  @pragma('vm:entry-point')
  static void onTapBuild() {
    Object instance; // Aspectd Ignore
    Object context;  // Aspectd Ignore
    print(instance);
    print(context);
    print('Aspectd:KWLM25');
  }
}

Build‑Process Integration

Standard Flutter tools do not support compiling both the original project and AspectD code into a single dill file. By applying a patch to the Flutter toolchain (similar to AspectJ’s Ajc), AspectD can be integrated into the build pipeline, enabling AOP in both debug and release modes.

Practical Experience

Using AspectD, the team eliminated all invasive modifications to the Flutter framework while achieving the same functionality, supporting hundreds of script‑based recording‑playback and automated regression tests.

Typical use cases include performance instrumentation, enhanced logging, deterministic random‑number generation for replay, and complex UI event injection.

Conclusion

AspectD offers a compile‑time AOP solution for Flutter/Dart, leveraging dill transformation to provide powerful, non‑intrusive code weaving. The framework is open‑source on GitHub and welcomes contributions.

dartflutterAspectDcode generationaopDill
Xianyu Technology
Written by

Xianyu Technology

Official account of the Xianyu technology team

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.