<!--
 * Copyright (C) 2023 WYATT GROUP
 * Please see the AUTHORS file for details.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
-->

# CRUD BloC

<p align="left">
  <a href="https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_analysis"><img src="https://img.shields.io/badge/Style-Wyatt%20Analysis-blue.svg?style=flat-square" alt="Style: Wyatt Analysis" /></a>
  <img src="https://img.shields.io/badge/SDK-Flutter-blue?style=flat-square" alt="SDK: Flutter" />
</p>

CRUD Bloc Pattern utilities for Flutter.

This package defines a set of classes that can be used to implement the CRUD Bloc Pattern.

* Data Source
* Repository
* Use Case
  + Create
  + Get
  + Get All
  + Update
  + Update All
  + Delete
  + Delete All
  + Search
* Bloc
  + Standard (C R U D), you have to choose the responsiblity of the bloc for each use case. For example, you can have a cubit that only handles the creation of an entity, and another cubit that only handles the deletion of an entity. Each cubit can only have one operation responsibility of each type, for example you can't have a cubit that handles get and get all.
  + Advanced, you can set every use case to be handled by the bloc. This is useful if you want to have a single bloc that handles all the operations of an entity.

## Usage

Create your entity using Wyatt Architecture and Equatable

```dart
import 'package:equatable/equatable.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';

class User extends Entity with EquatableMixin {
  final String? id;

  final String name;
  final String email;
  final String phone;

  const User({
    required this.name,
    required this.email,
    required this.phone,
    this.id,
  });

  @override
  List<Object?> get props => [id, name, email, phone];
}
```

Then, you can create your model for this entity with Freezed, JsonSerializable or any other library.

```dart
import 'package:crud_bloc_example/user_entity.dart';
import 'package:json_annotation/json_annotation.dart';

part 'user_model.g.dart';

@JsonSerializable()
class UserModel extends User {
  UserModel({
    super.id,
    required super.name,
    required super.email,
    required super.phone,
  });

  factory UserModel.fromJson(Map<String, dynamic> json) =>
      _$UserModelFromJson(json);

  Map<String, dynamic> toJson() => _$UserModelToJson(this);

  @override
  String toString() =>
      'UserModel(id: $id, name: $name, email: $email, phone: $phone)';
}
```

> This exemple uses JsonSerializable to generate the model, but you can use any other library. Or you can even create your own model without any library.

Then you have to create a **CRUD** Cubit. Each Crud cubit can only handle one operation of each type. For example, you can't have a cubit that handles get and get all. (See the `CrudAdvancedCubit` for this use case).

You also have to create a `modelIdentifier` that will be used to identify your model. This is used to identify the model in the state of the cubit. For example, if you have a list of users, you can use the id of the user to identify it in the state. **It's up to you to choose how you want to identify your model.**

```dart
import 'package:wyatt_crud_bloc/wyatt_crud_bloc.dart';

/// A [CrudCubit] for [User].
class UserCubit extends CrudCubit<User> {
  final CrudRepository<User> crudRepository;

  UserCubit(this.crudRepository);

  @override
  DefaultCreate<User>? get createOperation => Create(crudRepository);

  @override
  DefaultDelete? get deleteOperation => Delete(crudRepository);

  @override
  DefaultRead? get readOperation => GetAll(crudRepository);

  @override
  DefaultUpdate? get updateOperation => Update(crudRepository);

  @override
  ModelIdentifier<User> get modelIdentifier => ModelIdentifier(
        getIdentifier: (user) => user.id ?? '',
      );
}
```

> In this example, the `modelIdentifier` is the id of the user. But you can use any other property of the user to identify it. But make sure that the property you use is unique.

> In this example, the `CrudCubit` handles `Create`, `Delete`, `GetAll` and `Update` operations. But you can choose which operation you want to handle. For example, you can have a cubit that only handles the creation of an entity, and another cubit that only handles the deletion of an entity.

Then you can configure the data source and the repository.

### In Memory

The base implementation of the data source is the `CrudInMemoryDataSourceImpl`. This data source stores the data in memory. This is useful for testing.

```dart
final CrudDataSource crudDataSource = CrudDataSourceInMemoryImpl(
  id: (operation, json) => json['id'] as String?,
);
```

> The `id` parameter is used to get the id of the raw data. You can use the `operation` parameter to get the operation that is being performed. This is useful if you want to have different ids for each operation. For example, you can have a different id for the creation and the update of an entity.

### Firestore

You can use the `CrudFirestoreDataSourceImpl` to store your data in Firestore.

Make sure to add the `wyatt_crud_bloc_firestore` package to your dependencies.

```dart
final CrudDataSource crudDataSource = CrudDataSourceFirestoreImpl(
  'users',
  id: (_, json) => json['id'] as String,
  fromFirestore: (document, snapshot) => document.data() ?? {},
  toFirestore: (object, options) => object,
);
```

> The `id` parameter is used to get the id of the raw data. You can use the `operation` parameter to get the operation that is being performed. This is useful if you want to have different ids for each operation. For example, you can have a different id for the creation and the update of an entity.

> The `fromFirestore` and `toFirestore` parameters are used to convert the data from and to Firestore.

Finally, you can use the `CrudRepositoryImpl` to create your repository.

```dart
final CrudRepository<User> userRepository = CrudRepositoryImpl(
  crudDataSource: crudDataSource,
  modelMapper: ModelMapper(
    fromJson: (json) => UserModel.fromJson(json ?? {}),
    toJson: (user) => UserModel(
      id: user.id,
      name: user.name,
      email: user.email,
      phone: user.phone,
    ).toJson(),
  ),
);
```

> The `modelMapper` parameter is used to convert the data from and to the model. In the `toJson` make sure to use the same data structure as the one used in the data source. For example the previous `id()` function is used to get the id of the raw data, if `id` is missing from the raw data, the `id()` function will not works.

And in your widget tree you can use the `CrudBuilder` to build your UI.

```dart
...
BlocBuilder<UserCubit, CrudState>(
  buildWhen: (previous, current) {
    if (current is CrudLoading && current is! CrudReading) {
      return false;
    }
    return true;
  },
  builder: (context, state) {
    return Expanded(
      child: CrudBuilder.typed<CrudListLoaded<User?>>(
        state: state,
        builder: ((context, state) {
          return ListView.builder(
            itemCount: state.data.length,
            itemBuilder: (context, index) {
              final user = state.data.elementAt(index);
              return ListTile(
                title: Text(user?.name ?? 'Error'),
                subtitle: Text(user?.email ?? 'Error'),
                onTap: () {
                  context
                      .read<UserCubit>()
                      .delete(id: (user?.id)!);
                },
                onLongPress: () {
                  context.read<UserCubit>().update(
                        single: UpdateParameters(
                            id: user?.id ?? '',
                            raw: {
                              'email': '${user?.id}@updated.io',
                            }),
                      );
                },
              );
            },
          );
        }),
        initialBuilder: (context, state) =>
            const Center(child: CircularProgressIndicator()),
        loadingBuilder: (context, state) =>
            const Center(child: CircularProgressIndicator()),
        errorBuilder: (context, state) => Text("Error: $state"),
      ),
    );
  },
),
...
```

> This piece of code is used to build a list of users. When you tap on a user, it will delete it. When you long press on a user, it will update it. Also, the loading state is not always displayed, it will only be displayed when the state is `CrudReading`, so when the state is `CrudUpdating` or `CrudDeleting`, the loading state will not be displayed.
