blob: 0010ef69d8f8e74910279b343eac1824452c0494 [file] [view]
---
title: Dart gRPC
sidebar_position: 11
id: dart
license: |
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
---
Fory can generate Dart gRPC service companions for schemas that define services.
The generated code uses normal `package:grpc` clients, service bases, method
descriptors, call options, deadlines, cancellations, and status codes, while
request and response objects are serialized with Fory instead of protobuf.
Use this mode when both RPC peers are generated from the same Fory IDL, protobuf
IDL, or FlatBuffers IDL and both sides expect Fory-encoded message bodies. Use
normal protobuf gRPC generation for APIs that must be consumed by generic
protobuf clients, reflection tools, or components that expect protobuf message
bytes.
## Add Dependencies
The `fory` package does not add gRPC dependencies. Add `grpc` (and the
`build_runner` dev dependency that generates the Fory serializer code) in the
application that compiles or runs generated service companions:
```yaml
dependencies:
fory: ^1.6.1
grpc: ^4.0.0
dev_dependencies:
build_runner: ^2.4.0
```
The same dependencies cover both client and server applications.
## Define a Service
Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers
`rpc_service` definitions. A Fory IDL service looks like this:
```protobuf
package demo.greeter;
message HelloRequest {
string name = 1;
}
message HelloReply {
string reply = 1;
}
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
```
Generate Dart model and gRPC companion code with `--grpc`:
```bash
foryc service.fdl --dart_out=./lib/generated --grpc
```
Then run `build_runner` once to emit the Fory serializer part file for the
generated models (this step is required before the code can run):
```bash
dart run build_runner build
```
For this schema, the Dart generator emits (the model file and module are named
from the package leaf, `greeter`):
| File | Purpose |
| ------------------------------------------- | ---------------------------------------------------- |
| `demo/greeter/greeter.dart` | Fory model types and the schema module |
| `demo/greeter/greeter.fory.dart` | Serializers and registration (built by build_runner) |
| `demo/greeter/greeter_grpc.dart` | gRPC client, service base, and method descriptors |
| `GreeterForyModule` in `greeter.dart` | Fory registration module for generated types |
| `GreeterServiceBase` in `greeter_grpc.dart` | Base class for server implementations |
| `GreeterClient` in `greeter_grpc.dart` | Client stub for gRPC calls |
The generated client and service base obtain a ready `Fory` automatically and
register the schema's types on first use, so no manual registration step is
required. To share a custom `Fory` (for example one configured with extra
modules), call `GreeterForyModule.install(yourFory)` once before the first RPC;
this is optional.
## Implement a Server
Extend the generated `GreeterServiceBase` and host it with grpc-dart's `Server`:
```dart
import 'dart:io';
import 'package:grpc/grpc.dart';
import 'demo/greeter/greeter.dart';
import 'demo/greeter/greeter_grpc.dart';
class GreeterService extends GreeterServiceBase {
@override
Future<HelloReply> sayHello(ServiceCall call, HelloRequest request) async {
final reply = HelloReply()..reply = 'Hello, ${request.name}';
return reply;
}
}
Future<void> main() async {
final server = Server.create(services: [GreeterService()]);
await server.serve(address: InternetAddress.loopbackIPv4, port: 50051);
}
```
## Create a Client
Use the generated client with a `ClientChannel`:
```dart
import 'package:grpc/grpc.dart';
import 'demo/greeter/greeter.dart';
import 'demo/greeter/greeter_grpc.dart';
Future<void> main() async {
final channel = ClientChannel(
'localhost',
port: 50051,
options: const ChannelOptions(
credentials: ChannelCredentials.insecure(),
),
);
final client = GreeterClient(channel);
final reply = await client.sayHello(HelloRequest()..name = 'Fory');
print(reply.reply);
await channel.shutdown();
}
```
## Streaming RPCs
Fory service definitions can use the same gRPC streaming shapes:
```protobuf
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
rpc Chat (stream HelloRequest) returns (stream HelloReply);
}
```
Generated Dart methods follow grpc-dart conventions. Single responses return a
`ResponseFuture<R>` (client-streaming adapts the call with `.single`); streaming
responses return a `ResponseStream<R>`. On the server, single requests arrive as
the message type and streaming requests as a `Stream`; the method returns a
`Future` for single responses and a `Stream` for streaming responses:
| IDL shape | Client method | Server method (override) |
| ----------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------ |
| `rpc A (Req) returns (Res)` | `ResponseFuture<Res> a(Req request, {CallOptions?})` | `Future<Res> a(ServiceCall call, Req request)` |
| `rpc A (Req) returns (stream Res)` | `ResponseStream<Res> a(Req request, {CallOptions?})` | `Stream<Res> a(ServiceCall call, Req request)` |
| `rpc A (stream Req) returns (Res)` | `ResponseFuture<Res> a(Stream<Req> request, {...})` | `Future<Res> a(ServiceCall call, Stream<Req> request)` |
| `rpc A (stream Req) returns (stream Res)` | `ResponseStream<Res> a(Stream<Req> request, {...})` | `Stream<Res> a(ServiceCall call, Stream<Req> request)` |
Server implementations use the generated streaming method shapes directly:
```dart
class GreeterService extends GreeterServiceBase {
@override
Stream<HelloReply> lotsOfReplies(
ServiceCall call,
HelloRequest request,
) async* {
for (final greeting in ['Hello, ${request.name}', 'Welcome, ${request.name}']) {
yield HelloReply()..reply = greeting;
}
}
@override
Future<HelloReply> lotsOfGreetings(
ServiceCall call,
Stream<HelloRequest> request,
) async {
final names = <String>[];
await for (final message in request) {
names.add(message.name);
}
return HelloReply()..reply = names.join(', ');
}
@override
Stream<HelloReply> chat(
ServiceCall call,
Stream<HelloRequest> request,
) async* {
await for (final message in request) {
yield HelloReply()..reply = 'Hello, ${message.name}';
}
}
}
```
Generated clients return the standard grpc-dart call objects:
```dart
// Server streaming.
await for (final reply in client.lotsOfReplies(HelloRequest()..name = 'Fory')) {
print(reply.reply);
}
// Client streaming.
final summary = await client.lotsOfGreetings(
Stream.fromIterable([
HelloRequest()..name = 'Ada',
HelloRequest()..name = 'Grace',
]),
);
print(summary.reply);
// Bidirectional streaming.
await for (final reply in client.chat(
Stream.fromIterable([HelloRequest()..name = 'Fory']),
)) {
print(reply.reply);
}
```
The generated descriptors preserve the exact IDL service and method names for
the gRPC path, while the Dart methods use camelCase names.
## Generated Module Names
Dart model files and schema modules are named after the package's last segment,
not the gRPC service name. (When a schema has no package, the source file stem is
used instead.)
| Schema input (package) | Model file | Schema module |
| ------------------------------- | ------------------- | ----------------------- |
| `service.fdl` (`demo.greeter`) | `greeter.dart` | `GreeterForyModule` |
| `api.fdl` (`demo.order_events`) | `order_events.dart` | `OrderEventsForyModule` |
| `greeter.fdl` (`demo.greeter`) | `greeter.dart` | `GreeterForyModule` |
A gRPC service named `Greeter` still generates the companion
`<stem>_grpc.dart` with `GreeterClient` and `GreeterServiceBase`; it does not
change the schema module name. If several schema files use the same package
leaf, place them in distinct output directories or choose package/file names that
produce distinct Dart model files.
## Operations
The generated service code only replaces request and response serialization.
All normal gRPC operational features still belong to your gRPC stack:
- Deadlines and cancellations
- TLS and authentication
- Name resolution and load balancing
- Client and server interceptors
- Status codes and metadata
- Channel lifecycle management
## Troubleshooting
### Missing `package:grpc` Types
Add `grpc` to your application dependencies. Generated Fory service files import
grpc-dart APIs, but `fory` intentionally does not depend on gRPC.
### Generated Code References a Missing `.fory.dart` Part
Run `dart run build_runner build` after generating
or regenerating the Dart sources. The serializer part file is produced by
`build_runner`, not by `foryc`.
### Protobuf Clients Cannot Decode the Service
Fory gRPC companions do not use protobuf wire encoding for messages. Use a
Fory-generated client for Fory-generated services, or expose a separate protobuf
service endpoint for generic protobuf clients.