Dart How to setup devcontainer for gRPC

eye-catch Dart and Flutter

gRPC makes our development easier to communicate with an application that is written in a different language. I tried to use gRPC in Golang before and posted the following two articles.

I implemented the same feature in Dart language. So I share the knowledge in this post with you.

You can clone my GitHub repository if you want to try it yourself.

Sponsored links

Set up Dockerfile

We have to set up Dockerfile to make it work in devcontainer. I followed the official page Quick Start. We need to install protocol buffer compiler.

# Install the compiler for protocol buffer
RUN curl -LO "https://github.com/protocolbuffers/protobuf/releases/download/v22.3/protoc-22.3-linux-x86_64.zip" && \
    unzip protoc-22.3-linux-x86_64.zip -d /usr/local && \
    rm protoc-22.3-linux-x86_64.zip

Then, I added the following for Dart.

# Install for Dart
RUN sudo apt update && \
    sudo apt install apt-transport-https && \
    wget -qO- https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo gpg --dearmor -o /usr/share/keyrings/dart.gpg && \
    echo 'deb [signed-by=/usr/share/keyrings/dart.gpg arch=amd64] https://storage.googleapis.com/download.dartlang.org/linux/debian stable main' | sudo tee /etc/apt/sources.list.d/dart_stable.list && \
    sudo apt update && \
    sudo apt install dart && \
    # Install proto for Dart
    dart pub global activate protoc_plugin && \
    # ===== NOT WORK !!! ====
    export PATH="$PATH:$HOME/.pub-cache/bin"

The proto files are already created for Golang; thus, I tried to generate gRPC files, but it didn’t work.

$ protoc --dart_out=./internal/proto -Iprotos ../../proto/middle.proto
protos: warning: directory does not exist.
../../proto/middle.proto: File does not reside within any path specified using --proto_path (or -I).  You must specify a --proto_path which encompasses this file.  Note that the proto_path must be an exact prefix of the .proto file names -- protoc is too dumb to figure out when two paths (e.g. absolute and relative) are equivalent (it's harder than you think).

$ protoc --dart_out=./internal/proto --proto_path=../../proto -Iprotos ./middle.proto     
protos: warning: directory does not exist.
Could not make proto path relative: ./middle.proto: No such file or directory

$ protoc --dart_out=./internal/proto --proto_path=../../proto -Iprotos middle.proto 
protos: warning: directory does not exist.
protoc-gen-dart: program not found or is not executable
Please specify a program using absolute path or make sure the program is available in your PATH system variable
--dart_out: protoc-gen-dart: Plugin failed with status code 1.

It seems that $HOME path can’t be used in Dockerfile for the vscode user. I tried echo $HOME but it shows $HOME as a string. The following way works neither.

# NOT WORK!!
RUN export PATH="$PATH:/home/vscode/.pub-cache/bin"

Therefore, PATH needs to be set with ENV.

# Install for Dart
RUN sudo apt update && \
    sudo apt install apt-transport-https && \
    wget -qO- https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo gpg --dearmor -o /usr/share/keyrings/dart.gpg && \
    echo 'deb [signed-by=/usr/share/keyrings/dart.gpg arch=amd64] https://storage.googleapis.com/download.dartlang.org/linux/debian stable main' | sudo tee /etc/apt/sources.list.d/dart_stable.list && \
    sudo apt update && \
    sudo apt install dart && \
    # Install proto for Dart
    dart pub global activate protoc_plugin

ENV PATH="$PATH:/home/vscode/.pub-cache/bin"

In this way, the path is correctly added to $PATH.

This is the first part that I took some time to find out.

Sponsored links

protoc command for Dart

Once the devcontainer is established, let’s define generate command in Makefile to make the protoc command simple. We need to set the output path. It must be created in advance. Then, we can set the relative path to the directory where proto files exist with --proto_path or -I. At last, give the proto file names.

generate:
    @protoc --dart_out=grpc:./lib/proto \
     -I../../proto \
    middle.proto \
    types_def.proto

If you want to make it clearer, use --proto_path.

generate2:
    @protoc --dart_out=grpc:./lib/proto \
    --proto_path=../../proto \
    middle.proto \
    types_def.proto

Note that grpc: needs to be added to --dart_out option. Otherwise, GeneratedService class extended by our defined service class can’t be used.

We can generate code from the defined proto files with the following command.

make generate

We have to regenerate the code whenever we update proto files. This Makefile definition makes our development easier.

Define timestamp in protobuf

Timestamp is often needed but timestamp is not built-in type in protocol buffer. We need to import another proto file as you can see in the following example.

// proto file
syntax = "proto3";

import "google/protobuf/timestamp.proto";

option go_package = "api-test/grpc/apitest";

service Middle {
   ...
}

However, the following error is shown without modifying our make generate command.

Target of URI hasn't been generated: 'google/protobuf/timestamp.pbjson.dart'.
Try running the generator that will generate the file referenced by the URI.darturi_has_not_been_generated

The necessary file doesn’t exist in our directory. Add the same path to the generate command at the end to solve this problem.

generate2:
    @protoc --dart_out=grpc:./lib/proto \
    --proto_path=../../proto \
    middle.proto \
    types_def.proto \
    google/protobuf/timestamp.proto 

Then, google/protobuf/ directory is newly created in proto directory and the corresponding files are generated there.

Add necessary packages to pubspec.yaml

Let’s add the necessary packages. Execute the following command.

dart pub add grpc protobuf fixnum

grpc and protobuf are of course needed. fixnum is used in the generated code to define for example Int64. Since a normal Dart int can’t be assigned to it, we need to add the package.

Install VSCode extensions

Let’s add useful vscode extensions.

"customizations": {
  "vscode": {
    "extensions": [
      "zxh404.vscode-proto3",
      "ms-vscode.makefile-tools",
      "xaver.clang-format",
      "dart-code.dart-code"
    ]
  }
}

I installed clang-format in Dockerfile too.

# To format proto file with vscode-proto3 extension
RUN apt update && apt install clang-format --yes

clang-format is used in vscode-proto3 to format the code. Go to my repository if you want to see the complete Dockerfile.

Go to further

The next step is to define the first function in protocol buffer and implement it.

Comments

Copied title and URL