firestore_odm 2.1.0 copy "firestore_odm: ^2.1.0" to clipboard
firestore_odm: ^2.1.0 copied to clipboard

Type-safe Firestore ODM with code generation support. Generate type-safe Firestore operations with annotations.

Firestore ODM for Dart/Flutter #

Stop fighting with Firestore queries. Start building amazing apps.

Transform your Firestore development experience with type-safe, intuitive database operations that feel natural and productive.

pub package GitHub

πŸ“– Complete Documentation #

πŸ“š Read the Full Documentation - Comprehensive guides, examples, and API reference

πŸš€ Why Firestore ODM? #

If you've worked with Flutter and Firestore, you know the pain:

  • No Type Safety - String-based field paths that break at runtime, not compile time
  • Manual Serialization - Converting DocumentSnapshot to models and back is tedious and error-prone
  • Complex Queries - Writing nested logical queries is difficult and hard to read
  • Runtime Errors - Typos in field names cause crashes in production
  • Incomplete Solutions - Other ODMs are often incomplete or not actively maintained

We built Firestore ODM to solve these problems with:

  • βœ… Complete type safety throughout your entire data layer
  • βœ… Lightning-fast code generation using callables and Dart extensions
  • βœ… Minimal generated code that doesn't bloat your project
  • βœ… Model reusability across collections and subcollections
  • βœ… Revolutionary features like Smart Builder pagination and streaming aggregations
  • βœ… Zero runtime overhead - all magic happens at compile time

πŸ”₯ Before vs After #

Type Safety Revolution #

// ❌ Standard cloud_firestore - Runtime errors waiting to happen
DocumentSnapshot doc = await FirebaseFirestore.instance
  .collection('users')
  .doc('user123')
  .get();

Map<String, dynamic>? data = doc.data() as Map<String, dynamic>?;
String name = data?['name']; // Runtime error if field doesn't exist
int age = data?['profile']['age']; // Nested access is fragile
// βœ… Firestore ODM - Compile-time safety
User? user = await db.users('user123').get();
String name = user.name; // IDE autocomplete, compile-time checking
int age = user.profile.age; // Type-safe nested access

Smart Query Building #

// ❌ Standard - String-based field paths, typos cause runtime errors
final result = await FirebaseFirestore.instance
  .collection('users')
  .where('isActive', isEqualTo: true)
  .where('profile.followers', isGreaterThan: 100)
  .where('age', isLessThan: 30)
  .get();
// βœ… ODM - Type-safe query builder with IDE support
final result = await db.users
  .where(($) => $.and(
    $.isActive(isEqualTo: true),
    $.profile.followers(isGreaterThan: 100),
    $.age(isLessThan: 30),
  ))
  .get();

Intelligent Updates #

// ❌ Standard - Manual map construction, error-prone
await userDoc.update({
  'profile.followers': FieldValue.increment(1),
  'tags': FieldValue.arrayUnion(['verified']),
  'lastLogin': FieldValue.serverTimestamp(),
});
// βœ… ODM - Three smart update strategies

// 1. Patch - Explicit atomic operations
await userDoc.patch(($) => [
  $.profile.followers.increment(1),
  $.tags.add('verified'),
  $.lastLogin.serverTimestamp(),
]);

// 2. IncrementalModify - Smart diff with atomic operations
await userDoc.incrementalModify((user) => user.copyWith(
  age: user.age + 1,              // Auto-detects -> FieldValue.increment(1)
  tags: [...user.tags, 'expert'], // Auto-detects -> FieldValue.arrayUnion()
  lastLogin: FirestoreODM.serverTimestamp, // Server timestamp support
));

⚑ Key Features #

πŸ›‘οΈ Complete Type Safety #

  • No Map<String, dynamic> anywhere in your code
  • Compile-time field validation - typos become build errors, not runtime crashes
  • IDE autocomplete for all database operations
  • Strong typing for nested objects and complex data structures

πŸš€ Lightning Fast Code Generation #

  • Highly optimized generated code using callables and Dart extensions
  • Minimal output - smart generation without bloating your project
  • Model reusability - same model works in collections and subcollections
  • Zero runtime overhead - all magic happens at compile time

🧠 Revolutionary Pagination #

Our Smart Builder eliminates the most common Firestore pagination bugs:

// Get first page with ordering
final page1 = await db.users
  .orderBy(($) => ($.followers(descending: true), $.name()))
  .limit(10)
  .get();

// Get next page with perfect type-safety - zero inconsistency risk
// The same orderBy ensures cursor consistency automatically
final page2 = await db.users
  .orderBy(($) => ($.followers(descending: true), $.name()))
  .startAfterObject(page1.last) // Auto-extracts cursor values
  .limit(10)
  .get();

πŸ“Š Streaming Aggregations (Unique Feature!) #

Real-time aggregation subscriptions that Firestore doesn't support natively:

// Live statistics that update in real-time
db.users
  .where(($) => $.isActive(isEqualTo: true))
  .aggregate(($) => (
    count: $.count(),
    averageAge: $.age.average(),
    totalFollowers: $.profile.followers.sum(),
  ))
  .stream
  .listen((stats) {
    print('Live: ${stats.count} users, avg age ${stats.averageAge}');
  });

🏦 Smart Transactions #

Automatic deferred writes handle Firestore's read-before-write rule:

await db.runTransaction((tx) async {
  // All reads happen first automatically
  final sender = await tx.users('user1').get();
  final receiver = await tx.users('user2').get();
  
  // Writes are automatically deferred until the end
  await tx.users('user1').patch(($) => [$.balance.increment(-100)]);
  await tx.users('user2').patch(($) => [$.balance.increment(100)]);
});

⚑ Atomic Batch Operations #

Perform multiple writes atomically with two convenient approaches:

// Automatic management - simple and clean
await db.runBatch((batch) {
  batch.users.insert(newUser);
  batch.posts.update(existingPost);
  batch.users('user_id').posts.insert(userPost);
  batch.users('old_user').delete();
});

// Manual management - fine-grained control
final batch = db.batch();
batch.users.insert(user1);
batch.users.insert(user2);
batch.posts.update(post);
await batch.commit();

πŸ”— Flexible Data Modeling #

Support for multiple modeling approaches:

  • freezed (recommended) - Robust immutable classes
  • json_serializable - Plain Dart classes with full control
  • fast_immutable_collections - High-performance IList, IMap, ISet

πŸ—οΈ Schema-Based Architecture #

  • Multiple ODM instances for different app modules
  • Compile-time validation of collection paths and relationships
  • Automatic subcollection detection and type-safe access
  • Clean separation of database concerns

πŸš€ Quick Start #

1. Installation #

dependencies:
  cloud_firestore: ^4.0.0
  firestore_odm: ^2.0.0
  freezed_annotation: ^2.0.0

dev_dependencies:
  build_runner: ^2.0.0
  firestore_odm_builder: ^2.0.0
  freezed: ^2.0.0
  json_serializable: ^6.0.0

2. Define Your Model #

// lib/models/user.dart
import 'package:firestore_odm_annotation/firestore_odm_annotation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'user.freezed.dart';
part 'user.g.dart';

@freezed
class User with _$User {
  const factory User({
    @DocumentIdField() required String id,
    required String name,
    required String email,
    required int age,
    DateTime? lastLogin,
  }) = _User;

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}

3. Define Your Schema #

// lib/schema.dart
import 'package:firestore_odm_annotation/firestore_odm_annotation.dart';
import 'models/user.dart';

part 'schema.odm.dart';

@Schema()
@Collection<User>("users")
final appSchema = _$AppSchema;

4. Generate Code #

dart run build_runner build --delete-conflicting-outputs

5. Start Using #

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firestore_odm/firestore_odm.dart';
import 'schema.dart';

final firestore = FirebaseFirestore.instance;
final db = FirestoreODM(appSchema, firestore: firestore);

// Create a user
await db.users.insert(User(
  id: 'jane',
  name: 'Jane Smith',
  email: 'jane@example.com',
  age: 28,
));

// Get a user
final user = await db.users('jane').get();
print(user?.name); // "Jane Smith"

// Type-safe queries
final youngUsers = await db.users
  .where(($) => $.age(isLessThan: 30))
  .orderBy(($) => $.name())
  .get();

🌟 Advanced Features #

Subcollections with Model Reusability #

@Schema()
@Collection<User>("users")
@Collection<Post>("posts")
@Collection<Post>("users/*/posts") // Same Post model, different location
final appSchema = _$AppSchema;

// Access user's posts
final userPosts = db.users('jane').posts;
await userPosts.insert(Post(id: 'post1', title: 'Hello World!'));

Bulk Operations #

// Update all premium users
await db.users
  .where(($) => $.isPremium(isEqualTo: true))
  .patch(($) => [$.points.increment(100)]);

// Delete inactive users
await db.users
  .where(($) => $.status(isEqualTo: 'inactive'))
  .delete();

Server Timestamps #

// Using patch
await userDoc.patch(($) => [$.lastLogin.serverTimestamp()]);

// Using modify
await userDoc.modify((user) => user.copyWith(
  lastLogin: FirestoreODM.serverTimestamp,
));

πŸ“Š Performance & Technical Excellence #

Optimized Code Generation #

  • Callables and Dart extensions for maximum performance
  • Minimal generated code - no project bloat
  • Compile-time optimizations - zero runtime overhead
  • Smart caching and efficient build processes

Advanced Query Capabilities #

  • Complex logical operations with and() and or()
  • Array operations - arrayContains, arrayContainsAny, whereIn
  • Range queries with proper ordering constraints
  • Nested field access with full type safety

Real-world Ready #

  • Transaction support with automatic deferred writes
  • Streaming subscriptions for real-time updates
  • Error handling with meaningful compile-time messages
  • Testing support with fake_cloud_firestore integration

πŸ§ͺ Testing #

Perfect integration with fake_cloud_firestore:

import 'package:fake_cloud_firestore/fake_cloud_firestore.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  test('user operations work correctly', () async {
    final firestore = FakeFirebaseFirestore();
    final db = FirestoreODM(appSchema, firestore: firestore);

    await db.users.insert(User(id: 'test', name: 'Test User', email: 'test@example.com', age: 25));

    final user = await db.users('test').get();
    expect(user?.name, 'Test User');
  });
}

πŸ“ˆ Comparison with Standard Firestore #

Feature Standard cloud_firestore Firestore ODM
Type Safety ❌ Map<String, dynamic> everywhere βœ… Strong types throughout
Query Building ❌ String-based, error-prone βœ… Type-safe with IDE support
Data Updates ❌ Manual map construction βœ… Three smart update strategies
Aggregations ❌ Basic count only βœ… Comprehensive + streaming
Pagination ❌ Manual, inconsistency risks βœ… Smart Builder, zero risk
Transactions ❌ Manual read-before-write βœ… Automatic deferred writes
Code Generation ❌ None βœ… Highly optimized, minimal output
Model Reusability ❌ N/A βœ… Same model, multiple collections
Runtime Errors ❌ Common βœ… Eliminated at compile-time
Developer Experience ❌ Frustrating βœ… Productive and enjoyable

🀝 Contributing #

We love contributions! See our Contributing Guide for details.

πŸ“„ License #

MIT License - see LICENSE file for details.


Ready to transform your Firestore experience?

πŸ”— Get Started Now | πŸ“š Full Documentation | πŸ› Report Issues

Build type-safe, maintainable Flutter apps with the power of Firestore ODM! πŸš€

4
likes
0
points
122
downloads

Publisher

verified publishersylphx.com

Weekly Downloads

Type-safe Firestore ODM with code generation support. Generate type-safe Firestore operations with annotations.

Repository (GitHub)
View/report issues

Topics

#firestore #odm #database #flutter #code-generation

Documentation

Documentation

License

unknown (license)

Dependencies

cloud_firestore, cloud_firestore_platform_interface, firestore_odm_annotation, flutter, meta

More

Packages that depend on firestore_odm