Skip to content

Commit 4cc1f78

Browse files
authored
add an example use of firestore (#240)
1 parent b98349f commit 4cc1f78

File tree

4 files changed

+258
-6
lines changed

4 files changed

+258
-6
lines changed

pkgs/googleapis_firestore_v1/README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,41 @@ See also:
1616

1717
This package is a generated gRPC client used to access the Firestore API.
1818

19+
## Example
20+
21+
See below for a hello-world example. For a more complete example, including
22+
use of `firestore.listen()`, see
23+
https://github.com/dart-lang/labs/blob/main/pkgs/googleapis_firestore_v1/example/example.dart.
24+
25+
```dart
26+
import 'package:googleapis_firestore_v1/google/firestore/v1/firestore.pbgrpc.dart';
27+
import 'package:grpc/grpc.dart' as grpc;
28+
29+
void main(List<String> args) async {
30+
final projectId = args[0];
31+
32+
// set up a connection
33+
final channel = grpc.ClientChannel(FirestoreClient.defaultHost);
34+
final auth = await grpc.applicationDefaultCredentialsAuthenticator(
35+
FirestoreClient.oauthScopes,
36+
);
37+
final firestore = FirestoreClient(channel, options: auth.toCallOptions);
38+
39+
// make a request
40+
final request = ListCollectionIdsRequest(
41+
parent: 'projects/$projectId/databases/(default)/documents',
42+
);
43+
final result = await firestore.listCollectionIds(request);
44+
print('collectionIds:');
45+
for (var collectionId in result.collectionIds) {
46+
print('- $collectionId');
47+
}
48+
49+
// close the channel
50+
await channel.shutdown();
51+
}
52+
```
53+
1954
## Status: Experimental
2055

2156
**NOTE**: This package is currently experimental and published under the
Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1 @@
11
include: package:lints/recommended.yaml
2-
3-
analyzer:
4-
errors:
5-
# TODO: Remove once google/protobuf.dart/pull/1000 is published.
6-
implementation_imports: ignore
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:io';
7+
8+
import 'package:args/command_runner.dart';
9+
import 'package:googleapis_firestore_v1/google/firestore/v1/common.pb.dart';
10+
import 'package:googleapis_firestore_v1/google/firestore/v1/document.pb.dart';
11+
import 'package:googleapis_firestore_v1/google/firestore/v1/firestore.pbgrpc.dart';
12+
import 'package:grpc/grpc.dart' as grpc;
13+
14+
void main(List<String> args) async {
15+
final commandRunner = createCommandRunner();
16+
final result = await commandRunner.run(args);
17+
exit(result ?? 0);
18+
}
19+
20+
CommandRunner<int> createCommandRunner() {
21+
final runner = CommandRunner<int>(
22+
'firestore',
23+
'A sample app exercising the Firestore APIs',
24+
)..argParser.addOption(
25+
'project',
26+
valueHelp: 'projectId',
27+
help: 'The projectId to use when querying a firestore database.',
28+
);
29+
30+
runner.addCommand(ListCommand());
31+
runner.addCommand(PeekCommand());
32+
runner.addCommand(PokeCommand());
33+
runner.addCommand(ListenCommand());
34+
35+
return runner;
36+
}
37+
38+
class ListCommand extends FirestoreCommand {
39+
@override
40+
final String name = 'list';
41+
42+
@override
43+
final String description = 'List collections for the given project.';
44+
45+
@override
46+
Future<int> runCommand(FirestoreClient firestore) async {
47+
final request = ListCollectionIdsRequest(parent: documentsPath);
48+
print('Listing collections for ${request.parent}…');
49+
final result = await firestore.listCollectionIds(request);
50+
print('');
51+
for (var collectionId in result.collectionIds) {
52+
print('- $collectionId');
53+
}
54+
print('');
55+
print('${result.collectionIds.length} collections.');
56+
return 0;
57+
}
58+
}
59+
60+
class PeekCommand extends FirestoreCommand {
61+
@override
62+
final String name = 'peek';
63+
64+
@override
65+
final String description =
66+
"Print the value of collection 'demo', document 'demo', field 'item'.";
67+
68+
@override
69+
Future<int> runCommand(FirestoreClient firestore) async {
70+
final request = GetDocumentRequest(name: '$documentsPath/demo/demo');
71+
print('reading ${request.name}…');
72+
final document = await firestore.getDocument(request);
73+
final value = document.fields['item'];
74+
if (value == null) {
75+
print("No value for document field 'item'.");
76+
} else {
77+
print("value: '${value.stringValue}'");
78+
}
79+
final updateTime = document.updateTime.toDateTime();
80+
print('updated: $updateTime');
81+
82+
return 0;
83+
}
84+
}
85+
86+
class PokeCommand extends FirestoreCommand {
87+
@override
88+
final String name = 'poke';
89+
90+
@override
91+
final String description =
92+
"Set the value of collection 'demo', document 'demo', field 'item'.";
93+
94+
@override
95+
String get invocation => '${super.invocation} <new value>';
96+
97+
@override
98+
Future<int> runCommand(FirestoreClient firestore) async {
99+
final rest = argResults!.rest;
100+
if (rest.isEmpty) {
101+
print(usage);
102+
return 1;
103+
}
104+
105+
final newValue = rest.first;
106+
print("Updating demo/demo.item to '$newValue'…");
107+
108+
final updatedDocument = Document(
109+
name: '$documentsPath/demo/demo',
110+
fields: [
111+
MapEntry('item', Value(stringValue: newValue)),
112+
],
113+
);
114+
await firestore.updateDocument(
115+
UpdateDocumentRequest(
116+
document: updatedDocument,
117+
updateMask: DocumentMask(fieldPaths: ['item']),
118+
),
119+
);
120+
121+
return 0;
122+
}
123+
}
124+
125+
class ListenCommand extends FirestoreCommand {
126+
@override
127+
final String name = 'listen';
128+
129+
@override
130+
final String description =
131+
"Listen for changes to the 'item' field of document 'demo' in "
132+
"collection 'demo'.";
133+
134+
@override
135+
Future<int> runCommand(FirestoreClient firestore) async {
136+
print('Listening for changes to demo.item field (hit ctrl-c to stop).');
137+
print('');
138+
139+
final StreamController<ListenRequest> requestSink = StreamController();
140+
141+
// Listen for 'ctrl-c' and close the firestore.listen stream.
142+
ProcessSignal.sigint.watch().listen((_) => requestSink.close());
143+
144+
final ListenRequest listenRequest = ListenRequest(
145+
database: databaseId,
146+
addTarget: Target(
147+
documents: Target_DocumentsTarget(
148+
documents: [
149+
'$documentsPath/demo/demo',
150+
],
151+
),
152+
targetId: 1,
153+
),
154+
);
155+
156+
final Stream<ListenResponse> stream = firestore.listen(
157+
requestSink.stream,
158+
// TODO(devoncarew): We add this routing header because our protoc grpc
159+
// generator doesn't yet parse the http proto annotations.
160+
options: grpc.CallOptions(
161+
metadata: {
162+
'x-goog-request-params': 'database=$databaseId',
163+
},
164+
),
165+
);
166+
167+
requestSink.add(listenRequest);
168+
169+
final streamClosed = Completer();
170+
171+
stream.listen(
172+
(event) {
173+
print('==============');
174+
print('documentChange: ${event.documentChange.toString().trim()}');
175+
print('==============');
176+
print('');
177+
},
178+
onError: (e) {
179+
print('\nError listening to document changes: $e');
180+
streamClosed.complete();
181+
},
182+
onDone: () {
183+
print('\nListening stream done.');
184+
streamClosed.complete();
185+
},
186+
);
187+
188+
await streamClosed.future;
189+
190+
return 0;
191+
}
192+
}
193+
194+
abstract class FirestoreCommand extends Command<int> {
195+
String get projectId => globalResults!.option('project')!;
196+
197+
String get databaseId => 'projects/$projectId/databases/(default)';
198+
199+
String get documentsPath => '$databaseId/documents';
200+
201+
@override
202+
Future<int> run() async {
203+
final projectId = globalResults!.option('project');
204+
if (projectId == null) {
205+
print('A --project option is required.');
206+
return 1;
207+
}
208+
209+
final channel = grpc.ClientChannel(FirestoreClient.defaultHost);
210+
final auth = await grpc.applicationDefaultCredentialsAuthenticator(
211+
FirestoreClient.oauthScopes,
212+
);
213+
214+
final firestore = FirestoreClient(channel, options: auth.toCallOptions);
215+
final result = await runCommand(firestore);
216+
await channel.shutdown();
217+
return result;
218+
}
219+
220+
Future<int> runCommand(FirestoreClient firestore);
221+
}

pkgs/googleapis_firestore_v1/pubspec.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ version: 0.1.0-wip
44
repository: https://github.com/dart-lang/labs/tree/main/pkgs/googleapis_firestore_v1
55

66
environment:
7-
sdk: ^3.7.0
7+
sdk: ^3.6.0
88

99
dependencies:
1010
fixnum: ^1.1.0
1111
grpc: ^4.0.0
1212
protobuf: ^4.1.0
1313

1414
dev_dependencies:
15+
args: ^2.7.0
1516
lints: ^6.0.0
1617
path: ^1.9.0
1718
# We use a pinned version to make the gRPC library generation hermetic.

0 commit comments

Comments
 (0)