docs(crud): update readme
This commit is contained in:
parent
74593fa9fc
commit
bd80934196
@ -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.
|
||||
|
@ -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 }
|
||||
|
Loading…
x
Reference in New Issue
Block a user