docs(crud): update readme

This commit is contained in:
Hugo Pointcheval 2023-11-13 18:25:25 +01:00
parent 74593fa9fc
commit bd80934196
Signed by: hugo
GPG Key ID: 3AAC487E131E00BC
2 changed files with 156 additions and 79 deletions

View File

@ -27,10 +27,7 @@ CRUD Bloc Pattern utilities for Flutter.
This package defines a set of classes that can be used to implement the CRUD Bloc Pattern.
* Model
* Data Source
+ In Memory
+ Firestore
* Repository
* Use Case
+ Create
@ -40,128 +37,207 @@ This package defines a set of classes that can be used to implement the CRUD Blo
+ Update All
+ Delete
+ Delete All
+ Query
+ 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 a model class that extends the `ObjectModel` class.
Create your entity using Wyatt Architecture and Equatable
```dart
class User extends ObjectModel {
@override
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 name;
final String email;
final String phone;
const User({
required this.name,
required this.email,
required this.phone,
this.id,
});
Map<String, Object> toMap() {
return {
'name': name ?? '',
};
}
@override
String toString() => 'User(id: $id, name: $name)';
List<Object?> get props => [id, name, email, phone];
}
```
You have to implement a bloc.
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;
final CrudRepository<User> crudRepository;
UserCubit(this._crudRepository);
UserCubit(this.crudRepository);
@override
CreateOperation<User, dynamic>? get createOperation =>
Create(_crudRepository);
DefaultCreate<User>? get createOperation => Create(crudRepository);
@override
DeleteOperation<User, dynamic>? get deleteOperation =>
Delete(_crudRepository);
DefaultDelete? get deleteOperation => Delete(crudRepository);
@override
ReadOperation<User, dynamic, dynamic>? get readOperation =>
GetAll(_crudRepository);
DefaultRead? get readOperation => GetAll(crudRepository);
@override
UpdateOperation<User, dynamic>? get updateOperation =>
Update(_crudRepository);
DefaultUpdate? get updateOperation => Update(crudRepository);
@override
ModelIdentifier<User> get modelIdentifier => ModelIdentifier(
getIdentifier: (user) => user.id ?? '',
);
}
```
> You can also use the `CrudAdvancedCubit` class to implement a bloc that handles all the use cases.
> 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.
Then you can use the bloc in your widget with a data source and a repository.
> 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
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
final CrudDataSource<User> userLocalDataSource =
CrudInMemoryDataSourceImpl<User>(toMap: (user) => user.toMap());
final CrudRepository<User> userRepository =
CrudRepositoryImpl(crudDataSource: userLocalDataSource);
return RepositoryProvider<CrudRepository<User>>.value(
value: userRepository,
child: BlocProvider<UserCubit>(
create: (context) => UserCubit(userRepository)..read(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
),
),
);
}
}
final CrudDataSource crudDataSource = CrudDataSourceInMemoryImpl(
id: (operation, json) => json['id'] as String?,
);
```
And anywhere in your widget tree you can use the BlocBuilder to build your widget.
> 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 CrudBuilder.typed<CrudListLoaded<User?>>(
state: state,
builder: ((context, state) {
return ListView.builder(
shrinkWrap: true,
itemCount: state.data.length,
itemBuilder: (context, index) {
final user = state.data.elementAt(index);
return ListTile(
title: Text(user?.name ?? 'Error'),
subtitle: Text(user?.id ?? 'Error'),
onTap: () {
context.read<UserCubit>().delete(id: (user?.id)!);
},
);
},
);
}),
initialBuilder: (context, state) => const Text("Loading..."),
loadingBuilder: (context, state) => const Text("Loading..."),
errorBuilder: (context, state) => Text("Error: $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.

View File

@ -24,7 +24,8 @@ dependencies:
version: ^0.0.5
wyatt_crud_bloc:
path: ../
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/
version: 0.1.1+1
dev_dependencies:
flutter_test: { sdk: flutter }