Reflectable Flutter - Listing widgets in your project
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