background
When Flutter development is used, there are sometimes problems with many image resources. According to regulations, image resources must be configured in pubspec.yaml file to be used properly. If there are more than 50 image resources, do you need to configure them one by one? Obviously not!
Yaml allows Flutter to configure the image resource folder path directly in pubspec.yaml. There is no need to specify each image resource path.
Different developments take a different approach, but here I use the Dart annotation, which is a one-line annotation to configure the resource file.
For example, we have the following resource files:
We only need to configure one line of comment:
@ImagePathSet('assets/'.'ImagePathTest')
void main() => runApp(MyApp());
Copy the code
Then run a command:
flutter packages pub run build_runner build
Copy the code
Dart generates a.dart file with the following contents:
class ImagePathTest {
ImagePathTest._();
static const BANNER = 'assets/image/banner.png';
static const PLAY_STOP = 'assets/image/play_stop.png';
static const SAVE_BUTTON = 'assets/image/save_button.png';
static const MINE_HEADER_IMAGE = 'assets/image/test/mine_header_image.png';
static const VERIFY = 'assets/image/verify.png';
static const VERIFY_ERROR = 'assets/image/verify_error.png';
}
Copy the code
At the same time, automatically configure our resource files in the pubspec.yaml folder:
assets:
- assets/image/banner.png
- assets/image/play_stop.png
- assets/image/save_button.png
- assets/image/test/mine_header_image.png
- assets/image/verify.png
- assets/image/verify_error.png
Copy the code
When used, you can directly reference image resources in code form:
Image.asset(ImagePathTest.xxx)
Copy the code
That is to prevent hand error on business, and can improve efficiency ~ the following focus on our development ideas.
Generate a resource configuration file using a one-line annotation
For details on Dart annotation use, see this article on Flutter annotation handling and code generation, as well as the official note on source_gen
As a quick note, the source_gen documentation reads:
source_gen is based on the build package and exposes options for using your Generator in a Builder. Part of the document is omitted…… In order to get the Builder used with build_runner it must be configured in a build.yaml file.
Translated into Chinese:
Source_gen is based on the Build package and provides exposed options for using your own Generator in a Builder. . To be able to use Builder and build_runner together, a build.yaml file must be configured.
So to use the Dart annotation, we need to do a few things:
- Dependency annotation library
source_gen
- Dependency build runtime
build_runner
- Create an annotation code generator
Generator
- create
Build
- create
build.yaml
file - Introduce relevant annotations where they are needed
- Run the build command to build
Let’s go one at a time.
Dependency annotation librarysource_gen
There’s nothing to say about this, but you must rely on this library for Dart annotations:
dependencies:
source_gen: ^ 0.9.4 + 5
Copy the code
For the version, see source_gen here
Dependency build runtimebuild_runner
Dev_dependencies = dev_dependencies
dev_dependencies:
build_runner: ^ 1.7.1
Copy the code
The build_Runner version is available here
Pub run build_runner
build
watch
serve
test
The first construction method is generally required in FLUTTER. The four commands above can be supplemented with commands such as –delete-conflicting-outputs. For details, see build_runner here
Create an annotation code generatorGenerator
Literally translated as generator, the official description is:
A tool to generate Dart code based on a Dart library source.
A tool for generating Dart code based on the source code of the Dart library. The class diagram is as follows:
Both classes are abstract classes, usually create a class inherited from GeneratorForAnnoration and implement the abstract method, in the abstract method we need to complete the development of logical functions; Here we need to create a picture resource file configuration function, which has the following two main points:
- Requires the consumer to specify the resource folder path
- The consumer is required to specify the name of the resource configuration class to be generated
So let’s create an instance class that contains these two information:
class ImagePathSet{
/// Resource folder path
final String pathName;
/// Name of the resource configuration class to be generated
final String newClassName;
const ImagePathSet(this.pathName, this.newClassName);
}
Copy the code
Eventually the first reference we use will look like this:
@ImagePathSet('assets/'.'ImagePathTest')
Copy the code
The important thing to note here is that the constructor of this class must be const. With the final annotation class we need to use, we create the generator:
class ImagePathGenerator extends GeneratorForAnnotation<ImagePathSet> {
@override
generateForAnnotatedElement(Element element, ConstantReader annotation, BuildStep buildStep) {
return null; }}Copy the code
In generateForAnnotatedElement () method, we can complete the logical part of our development, it involves three parameters:
-
Element: This is the detail of the annotated class/method, such as the modified part of the code:
/// path_test.dart @ImagePathSet('assets/'.'ImagePathTest') class PathTest{} Copy the code
Some element information is as follows:
element.name /// PathTest element.displayName /// PathTest element.toString() /// class PathTest element.enclosingElement /// /example/lib/path_test.dart element.kind /// CLASS element.metadata /// [@ImagePathSet ImagePathSet(String pathName, String newClassName)] Copy the code
-
The two most commonly used methods are:
read(String field)
peek(String field)
Both read the given annotation parameter information, and the former returns NULL if a FormatException is not read or thrown. Note that these two methods return a ConstantReader type. If you want to get the value of a specific annotation element, you need to call the corresponding xxxValue method. XXX indicates the specific type.
String pathName= annotation.peek('pathName').stringValue Copy the code
If we do not know the type of the annotation parameter, we can use isXxx to determine whether it is the corresponding type, for example:
annotation.peek('pathName').isString ///true annotation.peek('pathName').isInt ///false Copy the code
-
Dart does not support reflection. We did not find a way to do this.
- InputId: contains information entered during build
The complete generator code for generating resource files is as follows:
import 'dart:io';
import 'package:analyzer/dart/element/element.dart';
import 'package:image_path_helper/image_path_set.dart';
import 'package:source_gen/source_gen.dart';
import 'package:build/build.dart';
class ImagePathGenerator extends GeneratorForAnnotation<ImagePathSet> {
String _codeContent = ' ';
String _pubspecContent = ' ';
@override
generateForAnnotatedElement(Element element, ConstantReader annotation, BuildStep buildStep) {
final explanation = '// **************************************************************************\n'
'// If there are new files that need to be updated, it is recommended to run the clean command: \n'
'// flutter packages pub run build_runner clean \n'
'// \n'
'// Then run the following command to regenerate the corresponding file: \n'
'// flutter packages pub run build_runner build --delete-conflicting-outputs \n'
'/ / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *';
var pubspecFile = File('pubspec.yaml');
for (String imageName in pubspecFile.readAsLinesSync()) {
if (imageName.trim() == 'assets:') continue;
if (imageName.trim().toUpperCase().endsWith('.PNG')) continue;
if (imageName.trim().toUpperCase().endsWith('.JPEG')) continue;
if (imageName.trim().toUpperCase().endsWith('.SVG')) continue;
if (imageName.trim().toUpperCase().endsWith('.JPG')) continue;
_pubspecContent = "$_pubspecContent\n$imageName";
}
_pubspecContent = '${_pubspecContent.trim()}\n\n assets:'; Var imagePath = annotation. Peek ('pathName').stringValue;
if(! imagePath.endsWith('/')) {
imagePath = '$imagePath/'; Var newClassName = annotation.peek('newClassName').stringValue; HandleFile (imagePath); / / / add images path to pubspec. The yaml file pubspecFile. WriteAsString (_pubspecContent); /// return the generated code filereturn '$explanation\n\n'
'class $newClassName{\n'
' $newClassName._(); \n'
' $_codeContent\n'
'} ';
}
void handleFile(String path) {
var directory = Directory(path);
if (directory == null) {
throw '$path is not a directory.';
}
for (var file in directory.listSync()) {
var type = file.statSync().type;
if (type == FileSystemEntityType.directory) {
handleFile('${file.path}/');
} else if (type == FileSystemEntityType.file) {
var filePath = file.path;
var keyName = filePath.trim().toUpperCase();
if(! keyName.endsWith('.PNG') &&
!keyName.endsWith('.JPEG') &&
!keyName.endsWith('.SVG') &&
!keyName.endsWith('.JPG')) continue;
var key = keyName
.replaceAll(RegExp(path.toUpperCase()), ' ')
.replaceAll(RegExp('.PNG'), ' ')
.replaceAll(RegExp('.JPEG'), ' ')
.replaceAll(RegExp('.SVG'), ' ')
.replaceAll(RegExp('.JPG'), ' ');
_codeContent = '$_codeContent\n\t\t\t\tstatic const $key = \'$filePath\'; '; // use \t symbol instead of space. _pubspecContent ='$_pubspecContent\n - $filePath'; }}}}Copy the code
Create a Build
The main purpose of a Build is to get the generator to execute. Here we create a Build like this:
Builder imagePathBuilder(BuilderOptions options) =>
LibraryBuilder(ImagePathGenerator());
Copy the code
The main referenced packages are:
import 'package:build/build.dart';
Copy the code
Create the build.yaml file
Here our build.yaml file is configured as follows:
builders:
image_builder:
import: 'package:image_path_helper/builder.dart'
builder_factories: ['imagePathBuilder']
build_extensions: { '.dart': ['.g.dart'] }
auto_apply: root_package
build_to: source
Copy the code
The build.yaml configuration information is eventually read by the BuildConfig class in build_config.dart. For parameter descriptions, the official description build_config is recommended here.
A complete build.yaml structure is shown below:
build.yaml
BuildConfig
build.yaml
BuildConfig
BuildConfig
key | value | default |
---|---|---|
targets |
Map<String, BuildTarget> |
A single target should have the same package name |
builders |
Map<String, BuilderDefinition> |
/ |
post_process_builders |
Map<String, PostProcessBuilderDefinition> |
/ |
global_options |
Map<String, GlobalBuilderOptions> |
/ |
The four key pieces of information correspond to the four root nodes in the build.yaml file, of which the builders node is the most commonly used.
Builders that
Builders configure the configuration information for all the Builders in your package in the format Map
. For example, we have a Builder that looks like this:
/// builder.dart
Builder imagePathBuilder(BuilderOptions options) =>
LibraryBuilder(ImagePathGenerator());
Copy the code
We can configure this in the build.yaml file:
builders:
image_builder:
import: 'package:image_path_helper/builder.dart'
builder_factories: ['imagePathBuilder']
build_extensions: { '.dart': ['.g.dart'] }
auto_apply: root_package
build_to: source
Copy the code
Image_builder corresponds to the String part of Map
, and the BuilderDefinition information following: corresponds to the structure diagram above. Let’s break down the information for each parameter in BuilderDefinition:
parameter | The parameter types | instructions |
---|---|---|
builder_factories |
List<String> |
Mandatory parameter, returnedBuilder A list of method names, such as the one aboveBuilder Method is calledimagePathBuilder Written,['imagePathBuilder'] |
import |
String |
Mandatory parameter, importBuilder Package path, in the format ofPackage: uri The string |
build_extensions |
Map<String, List<String>> |
Mandatory parameter, mapping from input extension to output extension. For example: for example, the format of the file where the annotation is used is.dart To specify that the output file format can be specified by.dart Converted to.g.dart 或 .zip And so forth |
auto_apply |
String |
This parameter is optional. The default value isnone , corresponding to the source codeAutoApply Enumerated class, with four optional configurations:
none : Do not apply this generator unless you configure it manuallydependents : Applying this Builder to a package depends directly on the package that exposes the Builderall_packages : Applies this builder to all packages in the pass dependency diagramroot_package : Apply this generator only to top-level packagesDo you feel confused? It doesn’t matter, explained separately below ~~~ |
required_inputs |
List |
Optional argument, used to adjust the build order, specifying one or a series of file extensions to run after any Builder that might produce this type of output |
runs_before |
List<BuilderKey> |
An optional parameter to adjust build sequentially, which is the opposite of the one above, to run before the specified Builder
BuilderKey : indicates atarget The identity symbol, mainly by the correspondingBuilder Package name and method name, for exampleimage_path_helper|imagePathBuilder |
applies_builders |
List<BuilderKey> |
Optional parameter,Builder The list of keys, which is the identity tag, followsbuilder_factories The parameter configuration should be one-to-one |
is_optional |
bool |
This parameter is optional. Default valuefalse To specify whether running can be delayedBuilder In general, no configuration is required |
build_to |
String |
This parameter is optional. The default value iscache , mainly for theBuildTo Two arguments to an enumeration class:
cache : Output goes into the hidden build cache and will not be publishedsource : The output goes into the source tree next to its main inputIf you need to compile files that can be seen in your own source code, set this parameter to source If the specified generator returnsnull If no file generation is required, set this parameter tocache |
defaults |
TargetBuilderConfigDefaults |
Optional parameter: The user is not in itbuilders [Here refers totargets Under the nodebuilders Don’t get confused! 】 Section to apply when specifying the corresponding key |
Details about the auto_apply parameter:
Annotations package
- When we will
auto_apply
Set todependents
When:- if
Annotations package
Is directly dependent onsub_package02
On, then only insub_package02
On normal use of annotations, thoughPackage
Package depends on thesub_package02
, but the annotation still cannot be used properly
- if
- When we will
auto_apply
Set toall_packages
When:- if
Annotations package
Is directly dependent onsub_package02
On, then insub_package02
和Package
Annotations can be used normally
- if
- When we will
auto_apply
Set toroot_package
When:- if
Annotations package
Is directly dependent onsub_package02
On, then only inPackage
On normal use of annotations, althoughsub_package02
Do depend on, but just don’t give use
- if
- Therefore, if
Annotations package
Is directly dependent onPackage
When I do, I don’t careauto_apply
Set up thedependents
,all_packages
Or is itroot_package
When, in fact, are normal use!
As for the other three parameters of build.yaml, to be honest, I have to skip them here because I don’t use them much at present and only understand some of them.
Introduce relevant annotations where needed & run build commands to build
Once this is done, we need to reference the annotations, for example on the main() method:
@ImagePathSet('assets/'.'ImagePathTest')
void main() => runApp(MyApp());
Copy the code
After referencing the annotations, we then execute the following command on the Terminal command line to complete the compilation:
flutter packages pub run build_runner build
Copy the code
Dart file is configured on the main method of the main.dart file. The final generated file is main.g.art. Dart you can refer to the runBuilder method and the Expected_outputs method under run_Builder. dart.
Note: If you need to rebuild it is recommended to clean it first:
flutter packages pub run build_runner clean
Copy the code
In addition, it is recommended to execute the following command at build time:
flutter packages pub run build_runner build --delete-conflicting-outputs
Copy the code
At this point, a complete annotation with one line of code + one line command to complete the image file configuration function is done ~~~ ~
Conclusion moment
To make annotations in Flutter, you only need to follow certain steps and follow your own logic to develop the relevant features easily. The main process steps are summarized as follows:
- Rely on
source_gen
和build_runner
库 - Annotation class creation and generator creation
Generator
- create
Builder
- Create and configure
build.yaml
file - Reference the created annotations and run the commands to do so
Tips: This article source location: image_path_helper