Reflectable Flutter - Listing widgets in your project
Bài đăng này đã không được cập nhật trong 2 năm
Reflectable
Reference
https://marketsplash.com/tutorials/dart/dart-metadata/
Metadata
https://dart.dev/language/metadata
https://api.flutter.dev/flutter/meta/meta-library.html
dart: Mirror - Status: Unstable
https://api.dart.dev/stable/3.1.3/dart-mirrors/dart-mirrors-library.html
Use Cases For Metadata
Metadata finds its application in various scenarios:
- Code Documentation: By annotating code with metadata, developers can provide richer context, aiding in code understanding and documentation generation.
 - Tooling and Libraries: Tools and libraries can leverage metadata to automate tasks, provide warnings, or offer suggestions.
 - Code Generation: Metadata can be used to generate boilerplate code, reducing manual effort and potential errors.
 
Description
Listing all the widgets in a Flutter project is like a document that helps developers quickly identify existing widgets within the current project. This can aid in reusing widgets, reducing development time.
Dependencies:
in pubspec.yaml
dependencies: 
//https://pub.dev/packages/reflectable
reflectable: ^4.0.5
dev_dependencies: 
//support generate code
//https://pub.dev/packages/build_runner
 build_runner: ^2.4.6
Config:
Create annotation type:
import 'package:reflectable/reflectable.dart';
class SampleWidget extends Reflectable {
  const SampleWidget() : super(invokingCapability, typingCapability, reflectedTypeCapability);
}
const sampleWidget = SampleWidget();
Create widget is marked by SampeWidget annotation
import 'package:flutter/material.dart';
import 'package:flutter_reflectable/sample_annotation.dart';
class GreenBoxWidget extends StatelessWidget {
  const GreenBoxWidget({Key? key, this.width, this.height}) : super(key: key);
  final double? width;
  final double? height;
  
  Widget build(BuildContext context) {
    return Container(
      color: Colors.green,
      width: width,
      height: height,
    );
  }
}
class SampleGreenBoxWidget extends StatelessWidget {
  const SampleGreenBoxWidget({super.key});
  
  Widget build(BuildContext context) {
    return const GreenBoxWidget(
      height: 100,
      width: 100,
    );
  }
}
Config to generate
In main.dart
///*important
///need import 'package:flutter_reflectable/widgets/widget.dart';
import 'package:flutter_reflectable/widgets/widget.dart';
import 'main.reflectable.dart'; 
main.reflectable.dart: this file will be generated
In widgets/widget.dart
///*important
///need import this file to able generate code
export 'red_box_widget.dart';
export 'green_box_widget.dart';
We need to import this file to generate can detect what annotation need to generate
Run script:
dart run build_runner build
Config show UI
void main() {
	//this [initializeReflectable] required to call reflectable
  initializeReflectable();
  runApp(const MyApp());
}
class SampleListWidget extends StatelessWidget {
  const SampleListWidget({Key? key}) : super(key: key);
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Widget by sample annotation')),
      body: Column(
        children: sampleWidget.annotatedClasses.map((e) => e.newInstance('', []) as Widget).toList(),
      ),
    );
  }
}
//main value of this article
sampleWidget.annotatedClasses.map((e) => e.newInstance('', []) as Widget).toList(),
sampleWidget.annotatedClasses: get all Type were marked with @sampleWidget
e.newInstance('', []) : get an instance of each type by call default constructor (it have no param arguments)
The result

Apply with StoryBook
Introduce about story book and import it
https://pub.dev/packages/storybook_flutter
Define Story book name
const String defaultName = 'unknown'; 
mixin StoryBookName on Widget {
  String get name;
}
Need to define StoryBookName because in current time can not pass value to Annotation type
///This code not work for generate code
///This is current limit in Reflectable of Dart
class SampleWidget extends Reflectable {
  const SampleWidget(this.name) : super(invokingCapability, typingCapability, reflectedTypeCapability);
  
  final String name;
}
Add mixin for SampeWidget
class SampleGreenBoxWidget extends StatelessWidget with StoryBookName {
  const SampleGreenBoxWidget({super.key});
  
  Widget build(BuildContext context) {
    return const GreenBoxWidget(
      height: 100,
      width: 100,
    );
  }
  
  String get name => 'box/green-box';
}
Widget without mixin
class DefaultNameBox extends StatelessWidget {
  const DefaultNameBox({Key? key, this.width, this.height}) : super(key: key);
  final double? width;
  final double? height;
  
  Widget build(BuildContext context) {
    return Container(
      color: Colors.grey,
      width: width,
      height: height,
      child: const Text(defaultName),
    );
  }
}
class SampleDefaultNameBoxWidget extends StatelessWidget {
  const SampleDefaultNameBoxWidget({super.key});
  
  Widget build(BuildContext context) {
    return const DefaultNameBox(
      height: 100,
      width: 100,
    );
  }
}
**
💡 remember add export to widget.dart
export 'default_name_box_widget.dart';**
Set up story book widget
class ReflectableStoryBookWidget extends StatelessWidget {
  const ReflectableStoryBookWidget({Key? key}) : super(key: key);
  
  Widget build(BuildContext context) {
    return Storybook(
      plugins: initializePlugins(
        initialDeviceFrameData: (
          isFrameVisible: true,
          device: Devices.ios.iPad,
          orientation: Orientation.portrait,
        ),
      ),
      stories: sampleWidget.annotatedClasses
          .where((classMirror) => !classMirror.isAbstract)
          .map((e) => e.newInstance('', []))
          .whereType<Widget>()
          .map((e) => Story(name: e is StoryBookName ? e.name : defaultName, builder: (_) => e))
          .toList(),
    );
  }
}
💡 remember re run generate script
The result

Conclusion
All rights reserved