platform/incubation/reflection/doc/quick_start.md

7.9 KiB

Platform Reflection Quick Start Guide

This guide covers the most common use cases for Platform Reflection to help you get started quickly.

Installation

dependencies:
  platform_reflection: ^0.1.0

Basic Usage

1. Simple Class Reflection

import 'package:platform_reflection/reflection.dart';

// 1. Define your class
@reflectable
class User {
  String name;
  int age;
  
  User(this.name, this.age);
  
  void birthday() => age++;
}

// 2. Register for reflection
void main() {
  // Register class
  Reflector.register(User);
  
  // Register properties
  Reflector.registerProperty(User, 'name', String);
  Reflector.registerProperty(User, 'age', int);
  
  // Register methods
  Reflector.registerMethod(
    User,
    'birthday',
    [],
    true,
  );
  
  // Register constructor
  Reflector.registerConstructor(
    User,
    '',
    parameterTypes: [String, int],
    parameterNames: ['name', 'age'],
    creator: (String name, int age) => User(name, age),
  );
  
  // Use reflection
  final user = reflector.createInstance(
    User,
    positionalArgs: ['John', 30],
  ) as User;
  
  final mirror = reflector.reflect(user);
  print(mirror.getField(const Symbol('name')).reflectee); // John
  mirror.invoke(const Symbol('birthday'), []);
  print(mirror.getField(const Symbol('age')).reflectee); // 31
}

2. Property Access

// Get property value
final mirror = reflector.reflect(instance);
final name = mirror.getField(const Symbol('name')).reflectee as String;

// Set property value
mirror.setField(const Symbol('name'), 'Jane');

// Check if property exists
final metadata = Reflector.getPropertyMetadata(User);
if (metadata?.containsKey('name') ?? false) {
  // Property exists
}

3. Method Invocation

// Invoke method without arguments
mirror.invoke(const Symbol('birthday'), []);

// Invoke method with arguments
final result = mirror.invoke(
  const Symbol('greet'),
  ['Hello'],
).reflectee as String;

// Invoke method with named arguments
final result = mirror.invoke(
  const Symbol('update'),
  [],
  {const Symbol('value'): 42},
).reflectee;

4. Constructor Usage

// Default constructor
final instance = reflector.createInstance(
  User,
  positionalArgs: ['John', 30],
) as User;

// Named constructor
final instance = reflector.createInstance(
  User,
  constructorName: 'guest',
) as User;

// Constructor with named arguments
final instance = reflector.createInstance(
  User,
  positionalArgs: ['John'],
  namedArgs: {const Symbol('age'): 30},
) as User;

5. Type Information

// Get type metadata
final metadata = Reflector.getTypeMetadata(User);

// Check properties
for (var property in metadata.properties.values) {
  print('${property.name}: ${property.type}');
}

// Check methods
for (var method in metadata.methods.values) {
  print('${method.name}(${method.parameterTypes.join(', ')})');
}

6. Error Handling

try {
  // Attempt reflection
  final mirror = reflector.reflect(instance);
  mirror.invoke(const Symbol('method'), []);
} on NotReflectableException catch (e) {
  print('Type not registered: $e');
} on MemberNotFoundException catch (e) {
  print('Member not found: $e');
} on InvalidArgumentsException catch (e) {
  print('Invalid arguments: $e');
} on ReflectionException catch (e) {
  print('Reflection error: $e');
}

Common Patterns

1. Registration Helper

void registerType<T>(Type type) {
  Reflector.register(type);
  
  final scanner = Scanner();
  final metadata = scanner.scanType(type);
  
  // Register properties
  for (var property in metadata.properties.values) {
    Reflector.registerProperty(
      type,
      property.name,
      property.type,
      isWritable: property.isWritable,
    );
  }
  
  // Register methods
  for (var method in metadata.methods.values) {
    Reflector.registerMethod(
      type,
      method.name,
      method.parameterTypes,
      method.returnsVoid,
      parameterNames: method.parameters.map((p) => p.name).toList(),
      isRequired: method.parameters.map((p) => p.isRequired).toList(),
    );
  }
}

2. Property Observer

class PropertyObserver {
  final InstanceMirror mirror;
  final Symbol propertyName;
  final void Function(dynamic oldValue, dynamic newValue) onChange;
  
  PropertyObserver(this.mirror, this.propertyName, this.onChange);
  
  void observe() {
    var lastValue = mirror.getField(propertyName).reflectee;
    
    Timer.periodic(Duration(milliseconds: 100), (_) {
      final currentValue = mirror.getField(propertyName).reflectee;
      if (currentValue != lastValue) {
        onChange(lastValue, currentValue);
        lastValue = currentValue;
      }
    });
  }
}

3. Method Interceptor

class MethodInterceptor {
  final InstanceMirror mirror;
  final Symbol methodName;
  final void Function(List args, Map<Symbol, dynamic> named) beforeInvoke;
  final void Function(dynamic result) afterInvoke;
  
  MethodInterceptor(
    this.mirror,
    this.methodName,
    {this.beforeInvoke = _noOp,
    this.afterInvoke = _noOp});
    
  static void _noOp([dynamic _]) {}
  
  dynamic invoke(List args, [Map<Symbol, dynamic>? named]) {
    beforeInvoke(args, named ?? {});
    final result = mirror.invoke(methodName, args, named).reflectee;
    afterInvoke(result);
    return result;
  }
}

Best Practices

  1. Register Early

    void main() {
      // Register all types at startup
      registerType<User>();
      registerType<Order>();
      registerType<Product>();
    
      // Start application
      runApp();
    }
    
  2. Cache Mirrors

    class UserService {
      final Map<User, InstanceMirror> _mirrors = {};
    
      InstanceMirror getMirror(User user) {
        return _mirrors.putIfAbsent(
          user,
          () => reflector.reflect(user),
        );
      }
    }
    
  3. Handle Errors

    T reflectSafely<T>(Function() operation, T defaultValue) {
      try {
        return operation() as T;
      } on ReflectionException catch (e) {
        print('Reflection failed: $e');
        return defaultValue;
      }
    }
    
  4. Validate Registration

    bool isFullyRegistered(Type type) {
      final metadata = Reflector.getTypeMetadata(type);
      if (metadata == null) return false;
    
      // Check properties
      if (metadata.properties.isEmpty) return false;
    
      // Check methods
      if (metadata.methods.isEmpty) return false;
    
      // Check constructors
      if (metadata.constructors.isEmpty) return false;
    
      return true;
    }
    

Common Issues

  1. Type Not Registered

    // Wrong
    reflector.reflect(unregisteredInstance);
    
    // Right
    Reflector.register(UnregisteredType);
    reflector.reflect(instance);
    
  2. Missing Property/Method Registration

    // Wrong
    Reflector.register(User);
    
    // Right
    Reflector.register(User);
    Reflector.registerProperty(User, 'name', String);
    Reflector.registerMethod(User, 'greet', [String]);
    
  3. Wrong Argument Types

    // Wrong
    mirror.invoke(const Symbol('greet'), [42]);
    
    // Right
    mirror.invoke(const Symbol('greet'), ['Hello']);
    

Performance Tips

  1. Cache Metadata

    final metadata = Reflector.getTypeMetadata(User);
    final properties = metadata.properties;
    final methods = metadata.methods;
    
  2. Reuse Mirrors

    final mirror = reflector.reflect(instance);
    // Reuse mirror for multiple operations
    
  3. Batch Registration

    void registerAll() {
      for (var type in types) {
        registerType(type);
      }
    }
    

Next Steps