Run the store with Cloud Firestore¶
dorm_firestore_database connects the generated dORM repositories to Cloud
Firestore. It is a Flutter package: initialize Firebase and configure the
Firestore instance before constructing the engine.
Warning
The engine does not initialize, open, or close FirebaseFirestore. Keep
that lifecycle in the application and pass the configured instance to
Engine.
Add the engine package¶
From a Flutter project, run:
Add the Firebase SDK packages that your application imports directly:
The model source and code-generation packages come from the Quickstart
installation. firebase_core and
cloud_firestore are direct application dependencies because the application
initializes Firebase and passes a FirebaseFirestore instance to Engine.
Initialize Firebase and create the engine¶
Initialize Firebase before reading FirebaseFirestore.instance:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:dorm_firestore_database/dorm_firestore_database.dart';
import 'package:firebase_core/firebase_core.dart';
Future<Dorm<Query, OffsetPageRequest>> createDorm() async {
await Firebase.initializeApp();
final FirebaseFirestore firestore = FirebaseFirestore.instance;
final Engine engine = Engine(firestore);
return Dorm(engine);
}
The platform Firebase configuration must already be available to the Flutter application. If the default app cannot be found, add the platform-specific Firebase configuration for the target platform before running the app.
Store entities in a nested collection¶
By default, an entity named users is stored in the top-level users
collection. To place entity collections below a tenant document, pass an even
segment document path:
The users entity then uses tenants/acme/users. A parentPath identifies a
document, not a collection; tenants is therefore invalid because it has only
one path segment.
Use document IDs as dORM identities¶
Firestore document IDs are the dORM identities for this engine. Automatic
creation reserves an ID with collection.doc(), while explicit creation uses
the supplied non-empty String as the document ID.
The identity is passed to generated fromJson methods from the document
reference. It is not copied into the document body as another field.
final User user = await dorm.users.repository.put(
Creation.auto(
dependency: const UserDependency(),
data: const UserData(name: 'Ada'),
),
);
await dorm.users.repository.put(
Creation.explicit(
dependency: const UserDependency(),
data: const UserData(name: 'Grace'),
identity: 'grace',
),
);
This engine accepts simple String identities. Composite identities are not
supported.
Read, write, and update documents¶
The generated repository methods keep the same names as the other dORM engines:
final User? user = await dorm.users.repository.peek(userId);
final List<User> users = await dorm.users.repository.peekAll(
Filter.text('Ad', field: UserEntity.fields.name),
);
await dorm.users.repository.push(
User(id: userId, name: 'Updated name'),
);
await dorm.users.repository.patch(userId, (current) {
if (current == null) return null;
return current.copyWith(name: 'Patched name');
});
await dorm.users.repository.pop(userId);
push replaces the document at the supplied identity. patch runs the read,
callback, and write through a Firestore transaction. Firestore may retry the
transaction callback when a concurrently read document changes, so the
callback must not depend on a single execution.
Use filters, sorting, and offset pages¶
Firestore queries support the portable dORM filters and sorting operations that the engine maps to the SDK:
final List<User> users = await dorm.users.repository.peekAll(
Filter.value(true, field: UserEntity.fields.active),
QueryOptions(
orderBy: [OrderBy(UserEntity.fields.name)],
limit: 20,
),
);
The current generated repository type accepts OffsetPageRequest. Firestore
does not expose a native offset operation through this engine, so an offset
page reads enough documents to skip the requested prefix and detect
hasNext, then applies the skip in the client:
final Page<User> page = await dorm.users.repository.peekPage(
const Filter.empty(),
OffsetPageRequest(size: 20, offset: 40),
);
Firestore query rules, required indexes, and SDK errors remain visible to the caller.
Follow document and query changes¶
pull and pullAll use Firestore snapshots:
final Stream<User?> userChanges = dorm.users.repository.pull(userId);
final Stream<List<User>> activeUsers = dorm.users.repository.pullAll(
Filter.value(true, field: UserEntity.fields.active),
);
Each stream emits the current read when the listener starts and later emits changes reported by Firestore. The engine does not add polling or a separate event protocol.
Use relationships¶
Foreign fields and generated relation paths use the same repository API as other engines:
Firestore relationships use readable operations as their portable fallback. The engine reads the source models, extracts related IDs, and reads the target documents. A relationship can therefore issue multiple Firestore reads.
Understand writes in batches¶
putAll, pushAll, and popKeys use one Firestore WriteBatch and commit the
batch atomically. The engine does not expose that batch as a public dORM
transaction API.
The Firestore batch limit applies. If one of these operations contains more
than 500 writes, the engine throws before sending the batch instead of splitting
it into non-atomic requests. popAll and purge first read matching
documents and then delete them in a batch; the selection and deletion are not
one public transaction.
Understand the transaction boundary¶
Cloud Firestore provides native transactions, and this engine uses one
internally for patch. The current Dart transaction object reads individual
documents through DocumentReference; it does not provide the collection
query operation used by dORM's peekAll, pagination, and relationship
fallbacks.
For that reason, the engine does not implement TransactionalDorm. Its
internal patch transaction remains separate from a public transaction that
could compose multiple repositories.
Run against the Firestore Emulator¶
For a generated showcase project, run the local infrastructure and the Flutter application separately:
The generated Compose file uses spine3/firebase-emulator:latest with the
fictional project ID dorm-example. Firestore is published on 8080 and
Emulator UI is available on 4000. The generated Firebase options and
useFirestoreEmulator('127.0.0.1', 8080) call are ready for this local setup.
For a manually configured application, or when using this engine outside the showcase generator, use the Firebase CLI flow below.
Start the emulator from the Flutter project that contains your Firebase configuration:
Configure the SDK before constructing the engine:
await Firebase.initializeApp();
final FirebaseFirestore firestore = FirebaseFirestore.instance;
firestore.useFirestoreEmulator('127.0.0.1', 8080);
final Dorm<Query, OffsetPageRequest> dorm = Dorm(Engine(firestore));
Use the emulator when developing rules and queries locally. The Firestore security rules and Firebase Authentication state remain part of the Firebase application configuration; the dORM engine does not provide an authorization layer.
Firestore-specific boundaries¶
The optional FirestoreMigrationAdapter supports ordered migrations. Structural field operations are no-ops and data operations scan documents. The engine does not provide aggregation, collection-group queries, native selector access, index management, or a public transaction object. Firestore batches and transactions
are used internally where the operation mapping requires them.