Protocol Buffers with Dart
Approach 1
Starting with a protobuf and generating a Dart Class
Create a .proto file that defines the structure of your data model using the Protocol Buffers schema definition language.
This file should have a .proto extension and contain the message definitions, fields, and other relevant data structures that make up your data model.
It's essential to ensure that the data types you use in the .proto file are compatible with the programming language you intend to use for generating the code.
For instance, if you're using Dart, make sure to use data types that can be mapped to Dart's data types.
This file describes the structure of your data in a language-neutral format and serves as the source of truth for generating code in different programming languages.
In this example, we will use a sample .proto file named model.proto, which defines the data model for a workout exercise using BlazePods hardware device.
// This is a sample proto file, present at lib/proto/model.proto
syntax = "proto3";
message GExerciseModel
{
string exercise_name = 2;
GLEDLogic led_logic = 3;
int32 blazepods = 4;
int32 duration = 5;
optional int32 sets = 6;
optional int32 timeout = 7;
optional int32 interval_between_sets = 8;
GInteraction interaction = 9;
optional string active_color = 10;
string id = 11;
optional bool count = 12;
optional bool missing_count = 13;
optional double distance = 14;
}
enum GInteraction {
G_INTERACTION_TAP_SPECIFIC_COLOR = 0;
G_INTERACTION_TAP_SEQUENCE = 1;
G_INTERACTION_DISTANCE_CHANGE = 2;
}
enum GLEDLogic {
G_L_E_D_LOGIC_RANDOM = 0;
G_L_E_D_LOGIC_ALL_AT_ONCE = 1;
G_L_E_D_LOGIC_FOCUS = 2;
G_L_E_D_LOGIC_HOME_BASE = 3;
G_L_E_D_LOGIC_SEQUENCE = 4;
}
- The following table describes the fields in the proto file:
Field | Type | Description |
---|---|---|
exercise_name | string | The name of the exercise. |
led_logic | enum | The LED logic for the exercise. |
blazepods | int32 | The number of BlazePods used in the exercise. |
duration | int32 | The duration of the exercise, in seconds. |
sets | int32 (optional) | The number of sets in the exercise. |
timeout | int32 (optional) | The timeout period for the exercise, in seconds. |
interval_between_sets | int32 (optional) | The interval between sets, in seconds. |
interaction | enum | The type of interaction required during the exercise. |
active_color | string (optional) | The active color for the exercise. |
id | string | The unique identifier for the exercise. |
count | bool (optional) | Whether to count the exercise. |
missing_count | bool (optional) | Whether the exercise counts as missing. |
distance | double (optional) | The distance covered in the exercise, in meters. |
Enum | Values |
---|---|
GInteraction | G_INTERACTION_TAP_SPECIFIC_COLOR, G_INTERACTION_TAP_SEQUENCE, G_INTERACTION_DISTANCE_CHANGE |
GLEDLogic | G_L_E_D_LOGIC_RANDOM, G_L_E_D_LOGIC_ALL_AT_ONCE, G_L_E_D_LOGIC_FOCUS, G_L_E_D_LOGIC_HOME_BASE, G_L_E_D_LOGIC_SEQUENCE |
1. Dependencies Required
dependencies:
protobuf: ^2.1.0
dart pub global activate protoc_plugin
2. Generate Dart Class from Proto File
cd lib/models &&
protoc --dart_out=. *.proto
After this, you'll see 4 files created in your path , lib/models/
You can use this to create dart objects
3. Converting the proto generated class to a user-defined Dart class to a Dart object
Convert the Proto-generated Dart class to a user-defined Dart class that matches your data model. The Protocol Buffers compiler generates a Dart class that represents your data model based on the .proto file you created in step 2. However, this generated class may not always be suitable for your specific use case, and you may need to convert it to a user-defined Dart class.
- This example demonstrates how to convert a user-defined Dart class called ExerciseModel to a Protocol Buffers generated class called GExerciseModel.
- We define a function called convertToProtobuf that takes an instance of ExerciseModel and returns an instance of GExerciseModel by setting the values of the GExerciseModel fields based on the ExerciseModel fields.
- We create instances of ExerciseModel and GExerciseModel and use the convertToProtobuf function to convert between them.
- We print the byte array and JSON representation of the GExerciseModel instance.
import 'package:starting_with_protobuf/models/exercise_model.dart';
import 'package:starting_with_protobuf/models/model.pbserver.dart';
void main(List<String> args) {
var object = GExerciseModel(
exerciseName: 'testExercise',
ledLogic: GLEDLogic.G_L_E_D_LOGIC_ALL_AT_ONCE,
sets: 4,
blazepods: 5,
interaction: GInteraction.G_INTERACTION_TAP_SEQUENCE,
count: true,
duration: 10,
timeout: 10,
activeColor: '#123456',
);
// Converting object to Uint8List
print(object.writeToBuffer());
// Converting object to JSON
print(object.toProto3Json());
// Suppose you have a UI Class
var uiObject = const ExerciseModel(
id: '5',
exerciseName: 'test1',
blazepods: 5,
duration: 10,
interaction: Interaction.distanceChange,
ledLogic: LEDLogic.homeBase,
);
// We can write write a converter function to do this
var protoBufObject = convertToProtobuf(uiObject);
print(protoBufObject.writeToBuffer());
print(protoBufObject.toProto3Json());
}
/// Convert ExerciseModel to GExerciseModel
GExerciseModel convertToProtobuf(ExerciseModel model) {
var object = GExerciseModel();
object.id = model.id;
object.exerciseName = model.exerciseName;
object.blazepods = model.blazepods;
object.duration = model.duration;
object.interaction = GInteraction.values[model.interaction.index];
object.ledLogic = GLEDLogic.values[model.ledLogic.index];
object.activeColor = model.activeColor ?? '';
object.sets = model.sets ?? 0;
object.timeout = model.timeout ?? 0;
object.intervalBetweenSets = model.intervalBetweenSets ?? 0;
object.count = model.count ?? false;
object.missingCount = model.missingCount ?? false;
object.distance = model.distance ?? 0;
return object;
}
Approach 2
Starting with a Dart Class and generating a protobuf
Protocol Buffers (Protobuf) is a free and open-source cross-platform data format used to serialize structured data.
- This approach demonstrates how to use Protocol Buffers to serialize structured data in Dart by starting with a Dart class and generating a Protocol Buffers schema.
- We will use the protobuf package and proto_generator and proto_annotations dependencies to generate Protocol Buffers schema from the Dart class.
- We will then use the generated Protocol Buffers schema to serialize and deserialize data.
Step 1: Add Dependencies
In the first step, we need to add the following dependencies to our project's pubspec.yaml file:
dependencies:
protobuf: ^2.1.0
proto_generator: ^4.0.0-dev.12 # Use this version specifically
proto_annotations: ^3.0.0-dev.3 # Use this version specifically
dev_dependencies:
build_runner: ^2.3.3
These dependencies are required for generating the Protocol Buffers schema and handling the serialization and deserialization of data.
Step 2: Define Dart Class
- In the second step, we will define a Dart class that we want to serialize and deserialize using Protocol Buffers.
- We will also add proto_annotations to annotate the class and its fields.
import 'package:proto_annotations/proto_annotations.dart';
// Required as a import for 'person.g.dart';
import 'src/grpc/model.pb.dart';
//! Don't forget to add the following line in your dart file, it is essential for the code generation to work.
part 'person.g.dart';
@proto
enum Gender {
male,
female
}
@proto
class Person {
@ProtoField(2)
String name;
@ProtoField(3)
int age;
@ProtoField(4)
List<String> hobbies;
@ProtoField(5)
Gender gender;
Person({
required this.name,
required this.age,
required this.hobbies,
required this.gender,
});
}
Step 3: Generate Protocol Buffers Schema
- In the third step, we will generate the Protocol Buffers schema from the Dart class using the proto_generator and build_runner dependencies.
- We will also use the protoc command-line tool to generate the schema.
dart run build_runner build
mkdir -p lib/src/grpc
protoc --dart_out=grpc:lib/src/grpc -Ilib/proto ./lib/proto/model.proto
We first run the build_runner command to generate the necessary files. Then, we use the protoc command-line tool to generate the Protocol Buffers schema from the Dart class. The generated schema files will be saved in the lib/src/grpc directory.
Outputs: (Generated Files)
-> lib/proto/model.proto
syntax = "proto3";
message GPerson
{
string name = 2;
int32 age = 3;
repeated string hobbies = 4;
GGender gender = 5;
}
enum GGender {
G_GENDER_MALE = 0;
G_GENDER_FEMALE = 1;
}
-> lib/src/grpc/*.dart Now use the generated proto class to serialize and deserialize data.
Person obj = Person(
name: "Rohan",
age: 20,
hobbies: ["Badminton", "Table-Tennis"],
gender: Gender.male,
);
// Convert UI class to protoModel
final protoModel = obj.toProto();
// Generating Byte Array from protoModel
var byteArr = protoModel.writeToBuffer();
// Generating protoModel from Byte Array
var newProtoModel = GPerson.fromBuffer(byteArr);
// Converting protoModel to UI class
var newObj = newProtoModel.toPerson();
print(newObj.name);
print(newObj.age);
print(byteArr);