This package provides an implementation of the Repository pattern with Firestore as the backend.

Features

The Repository is a pattern for Entity persistence. It provides a mixin of operations such as store and get for a single Firestore document.

This package does not provide an implementation that get two or more documents at the same time and maps them to Entity.

CRUD operations on Entity can be easily defined by defining a Repository class with the necessary settings in Usage.

/// some settings...

class UserRepo extends FireRepoAll {
  @override
  FireRepoConfig<User, UserRepoId, UserCollection> get config => userRepoConfig;
}

void main() {
  final user = User("userId", "soraef");
  final repo = UserRepo();

  repo.store(user);
  repo.get(UserRepoId("userId"));
  repo.list(FireListParams(collection: UserCollection(), limit: 10));
  repo.delete(user);

  // query
  repo.query(
    FireQueryParams(
      collection: UserCollection(),
      where: (collection) => collection.where("name", isEqualTo: "soraef"),
    ),
  );

  // query cursor for infinity loading
  // get the entities of limit from the next of afterId.
  repo.queryCursor(
    FireQueryCoursorParams(
      collection: UserCollection(),
      where: (collection) => collection.orderBy("createdAt").limit(10),
    ),
  );
}

Getting started

flutter pub add fire_repo

Usage

Implement Repository for the User class stored in the /users/{userId} document and the Todo class stored in the /users/{userId}/todos/{todoId} document.

UserRepository

Consider the following case of creating a Repository for the User class. In Firestore, we want to store User in the path /users/{userId}.

class User {
  final String id;
  final String name;

  User(this.id, this.name);

  factory User.fromJson(Map<String, dynamic> json) {
    return User(json["id"], json["name"]);
  }

  Map<String, dynamic> get toJson => {
        "id": id,
        "name": name,
      };
}

1.Define UserCollection

Create a UserCollection that extends FireCollection. This class defines that User will be stored under /users in the Firestore.

class UserCollection extends FireCollection {
  UserCollection()
      : super(
          collectionIds: CollectionIds(["users"]),
          documentIds: DocumentIds([]),
        );
}

2. Define UserRepoId

Next, define UserRepoId, which extends FireRepoId. This class is used as the Id when getting User from Repository. Internally, it holds the information that the User object is stored on /users/{userId}.


class UserRepoId extends FireRepoId<UserCollection> {
  UserRepoId(
    String id,
  ) : super(id: id, collection: UserCollection());

  factory UserRepoId.fromEntity(User user) {
    return UserRepoId(user.id);
  }
}

3. Configure FireRepoConfig for UserRepository

Define the settings used by UserRepository as userRepoConfig. This userRepoConfig defines the mapping between User and json and from User to UserRepoId.

final userRepoConfig = FireRepoConfig<User, UserRepoId, UserCollection>(
  fromJson: User.fromJson,
  toJson: (user) => user.toJson,
  getId: UserRepoId.fromEntity,
);

4. Define UserRepository

class UserRepo extends FireRepoAll {
  @override
  FireRepoConfig<User, UserRepoId, UserCollection> get config => userRepoConfig;
}

TodoRepository

Consider the case of creating a Repository for a Todo class that will be stored in the second level of the Firestore, such as /users/{userId}/todos/{todoId}.

class Todo {
  final String id;
  final String userId;
  final String name;

  Todo(this.id, this.userId, this.name);

  factory Todo.fromJson(Map<String, dynamic> json) {
    return Todo(json["id"], json["userId"], json["name"]);
  }

  Map<String, dynamic> get toJson => {
        "id": id,
        "userId": userId,
        "name": name,
      };
}

class TodoCollection extends FireCollection {
  TodoCollection({
    required String userId,
  }) : super(
          collectionIds: CollectionIds(["users", "todos"]),
          documentIds: DocumentIds([userId]),
        );
}

class TodoRepoId extends FireRepoId<TodoCollection> {
  TodoRepoId({
    required String id,
    required String userId,
  }) : super(id: id, collection: TodoCollection(userId: userId));

  factory TodoRepoId.fromEntity(Todo todo) {
    return TodoRepoId(userId: todo.userId, id: todo.id);
  }
}

final todoRepoConfig = FireRepoConfig<Todo, TodoRepoId, TodoCollection>(
  fromJson: Todo.fromJson,
  toJson: (user) => user.toJson,
  getId: TodoRepoId.fromEntity,
);

In this case, we used mixin to implement only FireRepoGet. The TodoGetRepo is a Repository that can be used only with the get method. By using mixin, you can define a Repository that can only perform specific operations.

class TodoGetRepo with FireRepoGet<Todo, TodoRepoId, TodoCollection> {
  @override
  FireRepoConfig<Todo, TodoRepoId, TodoCollection> get config => todoRepoConfig;
}

Libraries

fire_repo