Dart Generated Code
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 calledfoo.proto
results in an output file calledfoo.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 aFoo
from serialized protocol buffer data representing the message.Foo.fromJson(...)
: Creates aFoo
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 theupdates
to it, and marks the copy read-only before returning it.static Foo create()
: Factory function to create a singleFoo
.static PbList<Foo> createRepeated()
: Factory function to create a List implementing a mutable repeated field ofFoo
elements.static Foo getDefault()
: Returns a singleton instance ofFoo
, 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:
- For each underscore in the name, the underscore is removed, and the following letter is capitalized.
- 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()
: Returnstrue
if the field is set.set foo(int value)
: Sets the value of the field. After calling this,hasFoo()
will returntrue
andget foo
will returnvalue
.void clearFoo()
: Clears the value of the field. After calling this,hasFoo()
will returnfalse
andget 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 returnvalue
.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()
: Returnstrue
if the field is set.void clearFoo()
: Clears the value of the field. After calling this,hasFoo()
will returnfalse
andget 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 returntrue
andget bar
will returnvalue
.bool hasBar()
: Returnstrue
if the field is set.void clearBar()
: Clears the value of the field. After calling this,hasBar()
will returnfalse
andget bar
will return the default value.Bar ensureBar()
: Setsbar
to the empty instance ifhasBar()
returnsfalse
, and then returns the value ofbar
. After calling this,hasBar()
will returntrue
.
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 anInt64
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. ReturnsFoo_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 toFoo_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 isFoo_Test.name
. Otherwise, returns the default value.set name(String value)
: Sets the value of the field and sets the oneof case toFoo_Test.name
. After calling this,get name
will returnvalue
andwhichTest()
will returnFoo_Test.name
.void clearName()
: Nothing will be changed if the oneof case is notFoo_Test.name
. Otherwise, clears the value of the field. After calling this,get name
will return the default value andwhichTest()
will returnFoo_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 four values, as well as a static const List<Color>
that contains the
values.
static const List<Color> values = <Color> [
COLOR_UNSPECIFIED,
COLOR_RED,
COLOR_GREEN,
COLOR_BLUE,
];
It will also include the following method:
static Color? valueOf(int value)
: Returns theColor
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 extensions 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.