reflection_factory

pub package Null Safety Codecov Dart CI GitHub Tag New Commits Last Commits Pull Requests Code size License

reflection_factory allows Dart reflection with an easy approach, even for third-party classes, using code generation portable for all Dart platforms.

Usage

To enable/generate reflection for some class/type, you can use two approaches:

  • @EnableReflection():

    Annotation that indicates that a specific class/type will have reflection.

  • @ReflectionBridge([User, Profile]):

    Annotation that indicates through a bridge class that the types (User and Profile) will have reflection.

@EnableReflection

The annotations @EnableReflection is used above your class/type that you want to have reflection enabled.

File: some_source_file.dart:

import 'package:reflection_factory/reflection_factory.dart';

// Add a reference to the code generated by:
// $> dart run build_runner build
part 'some_source_file.reflection.g.dart';

// Indicates that reflection for class `User` will be generated/enabled:
@EnableReflection()
class User {
  String? email;

  String pass;

  User(this.email, this.pass);

  User.empty() : this(null,'') ;

  bool get hasEmail => email != null;

  bool checkPassword(String pass) {
    return this.pass == pass;
  }
}

void main() {
  var user = User('joe@mail.com', '123');

  // The generated reflection:
  var userReflection = user.reflection;

  var fieldEmail = userReflection.field('email')!;
  print('email: ${fieldEmail.get()}');

  var methodCheckPassword = userReflection.method('checkPassword')!;

  var passOk1 = methodCheckPassword.invoke(['wrong']); // false
  print('pass("wrong"): $passOk1');

  var passOk2 = methodCheckPassword.invoke(['123']); // true
  print('pass("123"): $passOk2');

  // Using the generated `toJson` extension method:
  print('User JSON:');
  print(user.toJson());

  // Using the generated `toJsonEncoded` extension method:
  print('User JSON encoded:');
  print(user.toJsonEncoded());
  
  // Accessing reflection through class:
  var userReflection2 = User$reflection();

  // Creating an `User` instance from default or empty constructor:
  var user2 = userReflection2.createInstance()!;

  user2.email = 'smith@mail.com';
  user2.pass = 'abc';

  print('User 2 JSON:');
  print(user2.toJson());
  
}

OUTPUT:

email: joe@mail.com
pass("wrong"): false
pass("123"): true
User JSON:
{email: joe@mail.com, hasEmail: true, pass: 123}
User JSON encoded:
{"email":"joe@mail.com","hasEmail":true,"pass":"123"}
User 2 JSON:
{email: smith@mail.com, hasEmail: true, pass: abc}

@ReflectionBridge

The annotations @ReflectionBridge is used above a bridge class, and indicates that third-party types will have reflection generated.

File: some_source_file.dart:


import 'package:reflection_factory/reflection_factory.dart';

// Class `User` is from a third-party package:
import 'package:some_api/user.dart';

// Add a reference to the code generated by:
// $> dart run build_runner build
part 'some_source_file.reflection.g.dart';

// Indicates that reflection for class `User` will be generated/enabled
// through a bridge class:
@ReflectionBridge([User])
class UserReflectionBridge {}

void main() {
  var user = User('joe@mail.com', '123');

  // The generated reflection through bridge class:
  var userReflection = UserReflectionBridge().reflection(user);

  var fieldEmail = userReflection.field('email')!;
  print('email: ${fieldEmail.get()}');

  print('User JSON encoded:');
  print(user.toJsonEncoded());
}

OUTPUT:

email: joe@mail.com
User JSON encoded:
{"email":"joe@mail.com","pass":"123","hasEmail":true}

@ClassProxy

Here's an example to create a class proxy that can intercept calls to its methods:

proxies.dart:

import 'package:reflection_factory/reflection_factory.dart';
import 'package:mime/mime.dart';

part 'proxies.reflection.g.dart';

@ClassProxy('MimeTypeResolver')
class MimeTypeResolverProxy implements ClassProxyListener {
  
  @override
  Object? onCall(instance, String methodName, Map<String, dynamic> parameters,
          TypeReflection? returnType) {
    var call = '$instance -> $methodName( $parameters ) -> $returnType';
    calls.add(call);
    print('CALL>> $call');
    return call;
  }

}

One common use case is to have a proxy to a class without need to import its package in the code, having only the class structure methods in the proxy:

proxies_detached.dart:

import 'package:reflection_factory/reflection_factory.dart';
        
part 'proxies_detached.reflection.g.dart';

@ClassProxy('MimeTypeResolver', libraryPath: 'package:mime/mime.dart')
class MimeTypeResolverProxy implements ClassProxyListener {
  
  @override
  Object? onCall(instance, String methodName, Map<String, dynamic> parameters,
          TypeReflection? returnType) {
    var call = '$instance -> $methodName( $parameters ) -> $returnType';
    calls.add(call);
    print('CALL>> $call');
    return call;
  }
  
}

Using the proxy:

import 'proxies_detached.dart';

void main() {
  var proxy = MimeTypeResolverProxy();

  var proxyResult = proxy.lookup('path/foo'); // `onCall` returned value.
}

part directive

The generated code will be in a separated file referenced by a part directive in your code file:

file user_file.dart:

import 'package:reflection_factory/reflection_factory.dart';

// The reference to the generated reflection code:
part 'user_file.reflection.g.dart';

@EnableReflection()
class User {
  String name;
  String? email;

  User(this.name, this.email);
  
  User.empty() : this('','') ;
}

You can also use a subdirectory to have the generated reflection files:

file user_file.dart:

import 'package:reflection_factory/reflection_factory.dart';

// The reference to the generated reflection code inside a subdirectory:
part 'reflection/user_file.g.dart';

@EnableReflection()
class User {
  //...
}

The builder will automatically detect the part directive and identify if the generated code needs to be in a subdirectory or not, but always using the parent file name (user_file.dart) in the generated file name (user_file.reflection.g.dart or reflection/user_file.g.dart).

Dependencies

You need to add 2 dependencies in your project:

File: pubspec.yaml

dependencies:
  reflection_factory: ^1.2.10

dev_dependencies:
  build_runner: ^2.2.0

Building/Generating Code

To generate the reflection code just run build_runner in your Dart project:

$> dart run build_runner build

Options

You can configure the builder declaring a build.yaml file in your project:

File: build.yaml

targets:
  $default:
    builders:
      reflection_factory:
        options:
          verbose: true
          sequential: true
          timeout: 2 sec
  • Options:
    • verbose: If true builds the reflection code in verbose mode (default: false).
    • sequential: If true will process the BuildSteps sequentially. (default: true).
    • timeout: The sequential BuildStep timeout (default: 30 sec).

Source

The official source code is hosted @ GitHub:

Features and bugs

Please file feature requests and bugs at the issue tracker.

Contribution

Any help from the open-source community is always welcome and needed:

  • Found an issue?
    • Please fill a bug report with details.
  • Wish a feature?
    • Open a feature request with use cases.
  • Are you using and liking the project?
    • Promote the project: create an article, do a post or make a donation.
  • Are you a developer?
    • Fix a bug and send a pull request.
    • Implement a new feature.
    • Improve the Unit Tests.
  • Have you already helped in any way?
    • Many thanks from me, the contributors and everybody that uses this project!

If you donate 1 hour of your time, you can contribute a lot, because others will do the same, just be part and start with your 1 hour.

Author

Graciliano M. Passos: gmpassos@GitHub.

License

Apache License - Version 2.0

Libraries

builder
Library for reflection_factory's builder.
inspector
Library for reflection factory inspection.
reflection_factory
Library for reflection factory.