Grpc with Dart (Quick Start 9 Steps)

Batuhan Sahin
Codimis
Published in
5 min readAug 17, 2022

We are all working with services. Are you tired of working with Rest? Also, do you not want to use any other language for server-side programming? What if I say there is a much faster tech we have? If you are excited, you are in the right post, I will mention about google’s framework.

Let’s Start with What is Grpc?

gRPC is a modern open-source high-performance Remote Procedure Call (RPC) framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking, and authentication. It is also applicable in the last mile of distributed computing to connect devices, mobile applications, and browsers to backend services (Web site is here).

Supported languages

Each gRPC language/platform has links to the following pages and more:

  • Quick start
  • Tutorials
  • API reference

Select a language to visit its website:

Today we make some practice with using the quick start dart document .

1- Let’s start with download our example

Download the repo as a zip file and unzip it, or clone the repo:

git clone https://github.com/batttuh/Grpc-Dart-Example.git

2- Download package dependencies:

  • $ dart pub get

3- Let’s start with our dependencies when you click the pubspec.yaml you will see protobuf.

What is Protobuf?

Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data — think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.

4- Start with looking at proto3 syntax, you can visit the website via this link.

5- In the protos file you will see that helloworld.proto. This proto file includes our messages(that means our requests response messages) and we are using messages to build our service.

// The request message containing the user’s name.message HelloRequest {string name = 1;}// The response message containing the greetingsmessage HelloReply {string message = 1;}

6- We can write our service, it seems very easy.

// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
// Sends another greeting
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}

SayHello takes HelloRequest and returns HelloReply, it is like request and response, am I right? You can even return an empty response too. Like using:

message Empty{}
service Greeter{
rpc SayHello (HelloRequest) returns (Empty) {}
}

Or you can change it, take an empty message and return Hello Request. It is so basic.

7- If you change anything in the Proto file. You need to use protoc command because we need to recompile our updated proto file. You need to first download protocol buffers for using protoc command.

If you did not understand how to download it you can use this video.

protoc --dart_out=grpc:lib/src/generated -Iprotos protos/helloworld.proto

8- We need to start with the server first, Open bin/server.dart

class GreeterService extends GreeterServiceBase {
@override
Future<HelloReply> sayHello(ServiceCall call, HelloRequest request) async {
return HelloReply()..message = 'Hello, ${request.name}!';
}

@override
Future<HelloReply> sayHelloAgain(ServiceCall call, HelloRequest request) async {
return HelloReply()..message = 'Hello again, ${request.name}!';
}
}

In there we take GreeterServiceBase which comes from our protoc file when we compile it, we create files. You do not need to look at these files because it is so complicated. You just need to override our service functions which are coming fromGreeterServiceBase you can easily make this because our class alerts it.

9- Then we are starting with the Client Side we control our functions and we can use these service functions here.

Future<void> main(List<String> args) async {
final channel = ClientChannel(
'localhost',
port: 50051,
options: const ChannelOptions(credentials: ChannelCredentials.insecure()),
);
final stub = GreeterClient(channel);

final name = args.isNotEmpty ? args[0] : 'world';

try {
var response = await stub.sayHello(HelloRequest()..name = name);
print('Greeter client received: ${response.message}');
response = await stub.sayHelloAgain(HelloRequest()..name = name);
print('Greeter client received: ${response.message}');
} catch (e) {
print('Caught error: $e');
}
await channel.shutdown();
}

If you are using any other server which is running on your PC, not on your mobile phone you just need to change “localhost” to “10.0.2.2”. This syntax may be complex. We can change a little bit. Let’s take connection in another function.

ClientChannel createClient() {return ClientChannel('localhost',port: 50051,options: ChannelOptions(credentials: ChannelCredentials.insecure(),codecRegistry:CodecRegistry(codecs: const [GzipCodec(), IdentityCodec()]),),);}

Connect it in the main function and use another variable to make ClientConnection because when we finished using the channel we need to shut down it.

Future<void> main(List<String> args) async {final channel = createClient();final stub = GreeterClient(channel);final name = args.isNotEmpty ? args[0] : 'world';try {final response = await stub.sayHello(HelloRequest()..name = name,options: CallOptions(compression: const GzipCodec()),);print('Greeter client received: ${response.message}');} catch (e) {print('Caught error: $e');}await channel.shutdown();}

Full codes in here:

import 'package:grpc/grpc.dart';import 'package:helloworld/src/generated/helloworld.pbgrpc.dart';import 'server.dart';ClientChannel createClient() {return ClientChannel('localhost',port: 50051,options: ChannelOptions(credentials: ChannelCredentials.insecure(),codecRegistry:CodecRegistry(codecs: const [GzipCodec(), IdentityCodec()]),),);}Future<void> main(List<String> args) async {final channel = createClient();final stub = GreeterClient(channel);
//It is for taken value in the command linefinal name = args.isNotEmpty ? args[0] : 'world';
try {var response = await stub.sayHello(HelloRequest()..name = name,options: CallOptions(compression: const GzipCodec()),);print('Greeter client received: ${response.message}');} catch (e) {print('Caught error: $e');}await channel.shutdown();}

Now we can use another function which is sayHelloAgain.


response=await stub.sayHelloAgain(HelloRequest(name: 'Batuhan'));
print('Greeter client received: ${response.message}');

You can easily use this but there is a different thing in here we do not use CallOptions, what is this CallOptions?

CallOptions can specify static metadata, set the timeout, and configure per-RPC metadata providers. The metadata providers are invoked in order for every RPC and can modify the outgoing metadata (including metadata provided by previous providers).

THAT’S IT CONGRATULATIONS. YOU BUİLD CLİENT AND SERVER SERVİCE

TO RUN THE APP

  1. Run the server:
  • $ dart bin/server.dart

2. From another terminal, run the client. Add a name as a command-line argument:

  • $ dart bin/client.dart Alice
Greeter client received: Hello, Alice!
Greeter client received: Hello again, Batuhan!

--

--