Dart Generated Code

Describes what Dart code the protocol buffer compiler generates for any given protocol definition.

Any differences between proto2 and proto3 generated code are highlighted - note that these differences are in the generated code as described in this document, not the base API, which are the same in both versions. You should read the proto2 language guide and/or the proto3 language guide before reading this document.

Compiler Invocation

The protocol buffer compiler requires a plugin to generate Dart code. Installing it following the instructions provides a protoc-gen-dart binary which protoc uses when invoked with the --dart_out command-line flag. The --dart_out flag tells the compiler where to write the Dart source files. For a .proto file input, the compiler produces among others a .pb.dart file.

The name of the .pb.dart file is computed by taking the name of the .proto file and making two changes:

  • The extension (.proto) is replaced with .pb.dart. For example, a file called foo.proto results in an output file called foo.pb.dart.
  • The proto path (specified with the --proto_path or -I command-line flag) is replaced with the output path (specified with the --dart_out flag).

For example, when you invoke the compiler as follows:

protoc --proto_path=src --dart_out=build/gen src/foo.proto src/bar/baz.proto

the compiler will read the files src/foo.proto and src/bar/baz.proto. It produces: build/gen/foo.pb.dart and build/gen/bar/baz.pb.dart. The compiler automatically creates the directory build/gen/bar if necessary, but it will not create build or build/gen; they must already exist.

Messages

Given a simple message declaration:

message Foo {}

The protocol buffer compiler generates a class called Foo, which extends the class GeneratedMessage.

The class GeneratedMessage defines methods that let you check, manipulate, read, or write the entire message. In addition to these methods, the Foo class defines the following methods and constructors:

  • Foo(): Default constructor. Creates an instance where all singular fields are unset and repeated fields are empty.
  • Foo.fromBuffer(...): Creates a Foo from serialized protocol buffer data representing the message.
  • Foo.fromJson(...): Creates a Foo from a JSON string encoding the message.
  • Foo clone(): Creates a deep clone of the fields in the message.
  • Foo copyWith(void Function(Foo) updates): Makes a writable copy of this message, applies the updates to it, and marks the copy read-only before returning it.
  • static Foo create(): Factory function to create a single Foo.
  • static PbList<Foo> createRepeated(): Factory function to create a List implementing a mutable repeated field of Foo elements.
  • static Foo getDefault(): Returns a singleton instance of Foo, which is identical to a newly-constructed instance of Foo (so all singular fields are unset and all repeated fields are empty).

Nested Types

A message can be declared inside another message. For example:

message Foo {
  message Bar {
  }
}

In this case, the compiler generates two classes: Foo and Foo_Bar.

Fields

In addition to the methods described in the previous section, the protocol buffer compiler generates accessor methods for each field defined within the message in the .proto file.

Note that the generated names always use camel-case naming, even if the field name in the .proto file uses lower-case with underscores (as it should). The case-conversion works as follows:

  1. For each underscore in the name, the underscore is removed, and the following letter is capitalized.
  2. If the name will have a prefix attached (e.g. "has"), the first letter is capitalized. Otherwise, it is lower-cased.

Thus, for the field foo_bar_baz, the getter becomes get fooBarBaz and a method prefixed with has would be hasFooBarBaz.

Singular Primitive Fields (proto2)

For any of these field definitions:

optional int32 foo = 1;
required int32 foo = 1;

The compiler will generate the following accessor methods in the message class:

  • int get foo: Returns the current value of the field. If the field is not set, returns the default value.
  • bool hasFoo(): Returns true if the field is set.
  • set foo(int value): Sets the value of the field. After calling this, hasFoo() will return true and get foo will return value.
  • void clearFoo(): Clears the value of the field. After calling this, hasFoo() will return false and get foo will return the default value.

For other simple field types, the corresponding Dart type is chosen according to the scalar value types table. For message and enum types, the value type is replaced with the message or enum class.

Singular Primitive Fields (proto3)

For this field definition:

int32 foo = 1;

The compiler will generate the following accessor methods in the message class:

  • int get foo: Returns the current value of the field. If the field is not set, returns the default value.
  • set foo(int value): Sets the value of the field. After calling this, get foo will return value.
  • void clearFoo(): Clears the value of the field. After calling this,get foo will return the default value.

NOTE: Due to a quirk in the Dart proto3 implementation, the following methods are generated even if the optional modifier, used to request presence semantics, isn’t in the proto definition.

  • bool hasFoo(): Returns true if the field is set.
  • void clearFoo(): Clears the value of the field. After calling this, hasFoo() will return false and get foo will return the default value.

Singular Message Fields

Given the message type:

message Bar {}

For a message with a Bar field:

// proto2
message Baz {
  optional Bar bar = 1;
  // The generated code is the same result if required instead of optional.
}

// proto3
message Baz {
  Bar bar = 1;
}

The compiler will generate the following accessor methods in the message class:

  • Bar get bar: Returns the current value of the field. If the field is not set, returns the default value.
  • set bar(Bar value): Sets the value of the field. After calling this, hasBar() will return true and get bar will return value.
  • bool hasBar(): Returns true if the field is set.
  • void clearBar(): Clears the value of the field. After calling this, hasBar() will return false and get bar will return the default value.
  • Bar ensureBar(): Sets bar to the empty instance if hasBar() returns false, and then returns the value of bar. After calling this, hasBar() will return true.

Repeated Fields

For this field definition:

repeated int32 foo = 1;

The compiler will generate:

  • List<int> get foo: Returns the list backing the field. If the field is not set, returns an empty list. Modifications to the list are reflected in the field.

Int64 Fields

For this field definition:

// proto2
optional int64 bar = 1;

// proto3
int64 bar = 1;

The compiler will generate:

  • Int64 get bar: Returns an Int64 object containing the field value.

Note that Int64 is not built into the Dart core libraries. To work with these objects, you may need to import the Dart fixnum library:

import 'package:fixnum/fixnum.dart';

Map Fields

Given a map field definition like this:

map<int32, int32> map_field = 1;

The compiler will generate the following getter:

  • Map<int, int> get mapField: Returns the Dart map backing the field. If the field is not set, returns an empty map. Modifications to the map are reflected in the field.

Any

Given an Any field like this:

import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  google.protobuf.Any details = 2;
}

In our generated code, the getter for the details field returns an instance of com.google.protobuf.Any. This provides the following special methods to pack and unpack the Any’s values:

    /// Unpacks the message in [value] into [instance].
    ///
    /// Throws a [InvalidProtocolBufferException] if [typeUrl] does not correspond
    /// to the type of [instance].
    ///
    /// A typical usage would be `any.unpackInto(new Message())`.
    ///
    /// Returns [instance].
    T unpackInto<T extends GeneratedMessage>(T instance,
        {ExtensionRegistry extensionRegistry = ExtensionRegistry.EMPTY});

    /// Returns `true` if the encoded message matches the type of [instance].
    ///
    /// Can be used with a default instance:
    /// `any.canUnpackInto(Message.getDefault())`
    bool canUnpackInto(GeneratedMessage instance);

    /// Creates a new [Any] encoding [message].
    ///
    /// The [typeUrl] will be [typeUrlPrefix]/`fullName` where `fullName` is
    /// the fully qualified name of the type of [message].
    static Any pack(GeneratedMessage message,
        {String typeUrlPrefix = 'type.googleapis.com'});

Oneof

Given a oneof definition like this:

message Foo {
  oneof test {
    string name = 1;
    SubMessage sub_message = 2;
  }
}

The compiler will generate the following Dart enum type:

 enum Foo_Test { name, subMessage, notSet }

In addition, it will generate these methods:

  • Foo_Test whichTest(): Returns the enum indicating which field is set. Returns Foo_Test.notSet if none of them is set.
  • void clearTest(): Clears the value of the oneof field which is currently set (if any), and sets the oneof case to Foo_Test.notSet.

For each field inside the oneof definition the regular field accessor methods are generated. For instance for name:

  • String get name: Returns the current value of the field if the oneof case is Foo_Test.name. Otherwise, returns the default value.
  • set name(String value): Sets the value of the field and sets the oneof case to Foo_Test.name. After calling this, get name will return value and whichTest() will return Foo_Test.name.
  • void clearName(): Nothing will be changed if the oneof case is not Foo_Test.name. Otherwise, clears the value of the field. After calling this, get name will return the default value and whichTest() will return Foo_Test.notSet.

Enumerations

Given an enum definition like:

enum Color {
  COLOR_UNSPECIFIED = 0;
  COLOR_RED = 1;
  COLOR_GREEN = 2;
  COLOR_BLUE = 3;
}

The protocol buffer compiler will generate a class called Color, which extends the ProtobufEnum class. The class will include a static const Color for each of the three values defined as well as a static const List<Color> containing all the three non-unspecified values. It will also include the following method:

  • static Color? valueOf(int value): Returns the Color corresponding to the given numeric value.

Each value will have the following properties:

  • name: The enum’s name, as specified in the .proto file.
  • value: The enum’s integer value, as specified in the .proto file.

Note that the .proto language allows multiple enum symbols to have the same numeric value. Symbols with the same numeric value are synonyms. For example:

enum Foo {
  BAR = 0;
  BAZ = 0;
}

In this case, BAZ is a synonym for BAR and will be defined like so:

static const Foo BAZ = BAR;

An enum can be defined nested within a message type. For instance, given an enum definition like:

message Bar {
  enum Color {
    COLOR_UNSPECIFIED = 0;
    COLOR_RED = 1;
    COLOR_GREEN = 2;
    COLOR_BLUE = 3;
  }
}

The protocol buffer compiler will generate a class called Bar, which extends GeneratedMessage, and a class called Bar_Color, which extends ProtobufEnum.

Extensions (proto2 only)

Given a file foo_test.proto including a message with an extension range and a top-level extension definition:

message Foo {
  extensions 100 to 199;
}

extend Foo {
  optional int32 bar = 101;
}

The protocol buffer compiler will generate, in addition to the Foo class, a class Foo_test which will contain a static Extension for each extension field in the file along with a method for registering all the exensions in an ExtensionRegistry :

  • static final Extension bar
  • static void registerAllExtensions(ExtensionRegistry registry) : Registers all the defined extensions in the given registry.

The extension accessors of Foo can be used as follows:

Foo foo = Foo();
foo.setExtension(Foo_test.bar, 1);
assert(foo.hasExtension(Foo_test.bar));
assert(foo.getExtension(Foo_test.bar)) == 1);

Extensions can also be declared nested inside of another message:

message Baz {
  extend Foo {
    optional int32 bar = 124;
  }
}

In this case, the extension bar is instead declared as a static member of the Baz class.

When parsing a message that might have extensions, you must provide an ExtensionRegistry in which you have registered any extensions that you want to be able to parse. Otherwise, those extensions will just be treated like unknown fields. For example:

ExtensionRegistry registry = ExtensionRegistry();
registry.add(Baz.bar);
Foo foo = Foo.fromBuffer(input, registry);

If you already have a parsed message with unknown fields, you can use reparseMessage on an ExtensionRegistry to reparse the message. If the set of unknown fields contains extensions that are present in the registry, these extensions are parsed and removed from the unknown field set. Extensions already present in the message are preserved.

Foo foo = Foo.fromBuffer(input);
ExtensionRegistry registry = ExtensionRegistry();
registry.add(Baz.bar);
Foo reparsed = registry.reparseMessage(foo);

Be aware that this method to retrieve extensions is more expensive overall. Where possible we recommend using ExtensionRegistry with all the needed extensions when doing GeneratedMessage.fromBuffer.

Services

Given a service definition:

service Foo {
  rpc Bar(FooRequest) returns(FooResponse);
}

The protocol buffer compiler can be invoked with the `grpc` option (e.g. --dart_out=grpc:output_folder), in which case it will generate code to support gRPC. See the gRPC Dart Quickstart guide for more details.