data_class_plugin 0.2.2 copy "data_class_plugin: ^0.2.2" to clipboard
data_class_plugin: ^0.2.2 copied to clipboard

A tool that uses Dart's Analyzer to generate code on-the-fly.

Data Class Plugin #

CI Workflow Pub Version

Data Class Plugin is a tool that uses the Dart Analysis Server to generate code on-the-fly.

This package is experimental and still under development, thus do not use it for applications in production.

NOTE: The "in_place" mode has been deprecated and no new features will be added. It's recommended to use it only for prototyping and use "file" mode for production level projects.


Table of contents #


How it works #

Data Class Plugin uses the analyzer system and analyzer plugin to get access on the source code, parse it and provide actions based on that.

Installation #

  1. In your project's pubspec.yaml add on dependencies the following

    dependencies:
      data_class_plugin: ^0.2.0
    
  2. Update your analysis_options.yaml (in case you don't have it, just create a new one)

    Minimal analysis_options.yaml

    include: package:lints/recommended.yaml
    
    # You need to register the plugin under analyzer > plugins
    analyzer:
      plugins:
        - data_class_plugin
    
  3. Restart the analysis server

    VSCode

    1. Open the Command Palette
      1. Windows/Linux: Ctrl + Shift + P
      2. MacOS: ⌘ + Shift + P
    2. Type and select "Dart: Restart Analysis Server"

    IntelliJ

    1. Open Find Action
      1. Windows/Linux: Ctrl + Shift + A
      2. MacOS: ⌘ + Shift + A
    2. Type and select "Restart Dart Analysis Server"

Generate the code you want! #

DataClass Annotation #

  1. Create a simple class, annotate it with @DataClass() and provide final public fields for your model.

    @DataClass()
    class User {
       final String id;
       final String username;
    }
    
  2. Place the cursor anywhere inside the User class

  3. Run code actions on your IDE

    VSCode

    1. Windows/Linux: Ctrl + .
    2. MacOS: ⌘ + .

    Intellij

    1. Windows/Linux: Alt + Enter
    2. MacOS: ⌘ + Enter
  4. Select Generate data class

Available methods are:

  1. copyWith

    Generates a new instance of the class with optionally provide new fields values.

    If no value is provided (default), then true is assumed.

    MyClass copyWith(...) { ... }
    
  2. hashAndEquals

    Implements hashCode and equals methods.

    If no value is provided (default), then true is assumed.

    @override
    bool operator ==(Object other) { ... }
    
    @override
    int get hashCode { ... }
    
  3. $toString

    Implements toString method.

    If no value is provided (default), then true is assumed.

    @override
    String toString() { ... }
    
  4. fromJson

    Generates a factory constructor that creates a new instance from a Map.

    If no value is provided (default), then false is assumed.

    factory MyClass.fromJson(Map<dynamic, dynamic> json) { ... }
    
  5. toJson

    Generates a function that coverts this instance to a Map.

    If no value is provided (default), then false is assumed.

    Map<String, dynamic> toJson() { ... }
    

This configuration can be overriden in data_class_plugin_options.yaml, see Configuration.

Union Annotation #

Adding this annotation to a class enables it to create union types.

Available union annotation toggles are:

  1. dataClass

    Toggles code generation for toString, copyWith, equals and hashCode.

    If no value is provided (default), then true is assumed.

  2. toJson

    Toggles code generation for fromJson.

    If no value is provided (default), then true is assumed.

  3. fromJson

    Toggles code generation for toJson.

    If no value is provided (default), then true is assumed.

Enum Annotation #

  1. Create an enumeration with the last field closed by semicolon and annotate it with the @Enum() annotation.

    @Enum()
    enum Category {
       science,
       sports;
    }
    
  2. Place the cursor anywhere inside the Category enum

  3. Run code actions on your IDE

    VSCode

    1. Windows/Linux: Ctrl + .
    2. MacOS: ⌘ + .

    Intellij

    1. Windows/Linux: Alt + Enter
    2. MacOS: ⌘ + Enter
  4. Select Generate enum

Available methods are:

  1. $toString

    Implements toString method.

    If no value is provided (default), then true is assumed.

    @override
    String toString() { ... }
    
  2. fromJson

    Generates a factory constructor that creates a new instance from a Map.

    If no value is provided (default), then false is assumed.

    factory MyClass.fromJson(Map<dynamic, dynamic> json) { ... }
    
  3. toJson

    Generates a function that coverts this instance to a Map.

    If no value is provided (default), then false is assumed.

    Map<String, dynamic> toJson() { ... }
    

This configuration can be overriden in data_class_plugin_options.yaml, see Configuration.

Enums #

Even if you don't use the @Enum() annotation, you can still generate methods in enums.

  1. Create an enumeration with the last field closed by semicolon

    enum Category {
       science,
       sports;
    }
    
  2. Place the cursor anywhere inside the Category enum

  3. Run code actions on your IDE

    VSCode

    1. Windows/Linux: Ctrl + .
    2. MacOS: ⌘ + .

    Intellij

    1. Windows/Linux: Alt + Enter
    2. MacOS: ⌘ + Enter
  4. A list with the following actions will be displayed

    1. Generate constructor
    2. Generate 'fromJson'
    3. Generate 'toJson'
    4. Generate 'toString'

Enums can have an optional single field of primary type to be used in the fromJson or toJson transforms, if not provided then the .name is used as the default json value.

enum Category {
   science(0),
   sports(1);

   final int value;
}

Json Converters #

The plugin exposes a json converter registrant that be used through out the app to register your custom converters. This eliminates the need to annotate every single field with a custom converter like (json_serializable).

By default the plugin provides 3 converters for the following classes: Duration, DateTime, Uri.
In case you want to override the default implementation of these converters you can do it by registering your custom converter with jsonConverterRegistrant.register(cosnt MyCustomConverter()). For more info on how to create and register a converter, see this example

In case you want to provide a custom implementation for a single field that might contain complex logic for parsing/conversion you can use a JsonConverter implementation and annotate the specific field with the implementer class. See example on ClassWithLatLngConverterAnnotation class.

If implementing a JsonConverter is too complex for your case you can use the JsonKey fromJson/toJson functions.

File generation #

In this mode most of the code generation happens on a generated file.

You still need to generate some boilerplate code on the main class via actions, but most of the code now is generated into a different file (like build_runner).

The generated file has the format of base_filename.gen.dart

To start with this mode you need to:

  1. Update data_class_plugin_options.yaml (See more options)
generation_mode: file (default in_place)

# This option is **required** if **generation_mode** is "file"
# Specify which path matches should generate files
# If you update this option, you should re-run the generator
# or if it's for a specific folder/file(s) you are working on, you can update this without restarting
file_generation_paths:
  - "a/glob/here"
  - "an/oth/er/*.dart"

# If you commit the generated files in git
# You can set the line length for the generated code too, so it won't fail in potential CI/CD workflows
generated_file_line_length: 80 (default)
  1. Add a class
@DataClass()
class User {

   String get id;
   String get username;

   @DefaultValue<String>('')
   String get email;
}

Run the code actions like described previously.

The actions will generate for you the constructor, methods and the part directive.

Save the file and run the data_class_plugin CLI to generate dart run data_class_plugin generate <build | watch>

Configuration #

You can customize the generated code produced by Data Class Plugin.

Configuration file

To create a custom configuration you need to add a file named data_class_plugin_options.yaml in the root folder of your project.

Available options

  1. json

    Set the default naming convention for json keys.

    You can also override the default naming convention for the specified directories.

    Supported naming conventions: camelCase, snake_case, kebab-case & PascalCase.

  2. data_class

    Set the default values for the provided methods of the @DataClass annotation, by specifying the directories where they will be enabled or disabled.

  3. enum

    Set the default values for the provided methods of the @Enum annotation, by specifying the directories where they will be enabled or disabled.

Configuration examples

generation_mode: in_place (default) | file

# This option is **required** if **generation_mode** is "file"
# Which path matches should generate files
# If you update this option, you should re-run the genenator
# or if it's for a specific folder/file(s) you are working on, you can update this without restarting
file_generation_paths:
  - "a/glob/here"
  - "an/oth/er/*.dart"

# If you commit the generated files in git
# You can set the line length for the generated code too, so it won't fail in potential CI/CD workflows
generated_file_line_length: 80 (default)

json:
  # Default naming convention for json keys
  key_name_convention: camel_case (default) | snake_case | kebab_case | pascal_case

  # Maps naming conventions to globs
  # You can provide a map of all the conventions you need and then a list with all the globs
  # key_name_conventions glob match takes precedence over key_name_convention
  key_name_conventions:
    <camel_case | snake_case | kebab_case | pascal_case>:
      - "a/glob/here"
      - "another/glob/here"

data_class:
  options_config:
    # For each of the provided methods you can provide a configuration
    # The configuration can be an enabled or disabled field that contains a list of globs
    # Default values for each options
    # copy_with (true), hash_and_equals (true), to_string (true), from_json (false),to_json (false), unmodifiable_collections (true)
    <copy_with | hash_and_equals | to_string | from_json | to_json | unmodifiable_collections>:
      default: boolean
      enabled:
        - "a/glob/here"
        - "another/glob/here"
      disabled:
        - "a/glob/here"
        - "another/glob/here"

union:
  options_config:
    # For each of the provided methods you can provide a configuration
    # The configuration can be an enabled or disabled field that contains a list of globs
    # Default values for each options
    # copy_with (false), hash_and_equals (true), to_string (true)  from_json(false), to_json (false), unmodifiable_collections (true)
    <copy_with | hash_and_equals | to_string | from_json | to_json | unmodifiable_collections>:
      default: boolean
      enabled:
        - "a/glob/here"
        - "another/glob/here"
      disabled:
        - "a/glob/here"
        - "another/glob/here"

enum:
  options_config:
    # For each of the provided methods you can provide a configuration
    # The configuration can be an enabled or disabled field that contains a list of globs
    # Default values for each options
    # to_string (false), from_json(false), to_json (false)
    <to_string | from_json | to_json>:
      default: boolean
      enabled:
        - "a/glob/here"
        - "another/glob/here"
      disabled:
        - "a/glob/here"
        - "another/glob/here"

Notes #

If the generated method doesn't exist it will be placed in the end of the class/enum body (before }), otherwise it will be re-generated to be up-to-date with current snapshot of the code (fields, annotations configuration).

The constructor is always generated at the start of the body (after {) for classes.

class MyClass {
  // constructor will be generated here

  final int a;
}

The constructor is always generated after the semicolon (;) in the values declaration for enums.

enum MyEnum {
  a,
  b,
  c;

  // constructor will be generated here
}

Examples #

You can find a variety of examples in the examples folder and the source code from the Live Demo, as it was presented in the Flutter Greek Community, here.

Development #

In order to see your changes in the plugin you need to modify tools/analyzer_plugin/pubspec.yaml and add the following section:

dependency_overrides:
  data_class_plugin:
    path: /absolute/path/to/root_project

And restart the analysis server (in case that fails run pub_get.sh).

Known Issues #

  1. When using IntelliJ/Android Studio the $toString parameter of the @DataClass annotation is not visible in the Suggestions list. However, you can still use it by typing it.