feat(crud): change responsibility of blocs (closes #45, closes #44)

This commit is contained in:
Hugo Pointcheval 2023-04-13 20:21:30 +02:00
parent 4acab9a662
commit 216a6c2aae
Signed by: hugo
GPG Key ID: 3AAC487E131E00BC
34 changed files with 1146 additions and 498 deletions

View File

@ -1,39 +1,167 @@
<!--
This README describes the package. If you publish this package to pub.dev,
this README's contents appear on the landing page for your package.
For information about how to write a good package README, see the guide for
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
For general information about developing packages, see the Dart guide for
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
and the Flutter guide for
[developing packages and plugins](https://flutter.dev/developing-packages).
* 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/>.
-->
TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.
# CRUD BloC
## Features
<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>
TODO: List what your package can do. Maybe include images, gifs, or videos.
CRUD Bloc Pattern utilities for Flutter.
## Getting started
This package defines a set of classes that can be used to implement the CRUD Bloc Pattern.
TODO: List prerequisites and provide or point to information on how to
start using the package.
* Model
* Data Source
+ In Memory
+ Firestore
* Repository
* Use Case
+ Create
+ Get
+ Get All
+ Update
+ Update All
+ Delete
+ Delete All
+ Query
* 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
TODO: Include short and useful examples for package users. Add longer examples
to `/example` folder.
Create a model class that extends the `ObjectModel` class.
```dart
const like = 'sample';
class User extends ObjectModel {
@override
final String? id;
final String? name;
const User({
required this.name,
this.id,
});
Map<String, Object> toMap() {
return {
'name': name ?? '',
};
}
@override
String toString() => 'User(id: $id, name: $name)';
}
```
## Additional information
You have to implement a bloc.
TODO: Tell users more about the package: where to find more information, how to
contribute to the package, how to file issues, what response they can expect
from the package authors, and more.
```dart
/// A [CrudCubit] for [User].
class UserCubit extends CrudCubit<User> {
final CrudRepository<User> _crudRepository;
UserCubit(this._crudRepository);
@override
CreateOperation<User, dynamic>? get createOperation =>
Create(_crudRepository);
@override
DeleteOperation<User, dynamic>? get deleteOperation =>
Delete(_crudRepository);
@override
ReadOperation<User, dynamic, dynamic>? get readOperation =>
GetAll(_crudRepository);
@override
UpdateOperation<User, dynamic>? get updateOperation =>
Update(_crudRepository);
}
```
> You can also use the `CrudAdvancedCubit` class to implement a bloc that handles all the use cases.
Then you can use the bloc in your widget with a data source and a repository.
```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(),
),
),
);
}
}
```
And anywhere in your widget tree you can use the BlocBuilder to build your widget.
```dart
...
BlocBuilder<UserCubit, CrudState>(
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"),
);
},
),
...
```

View File

@ -36,8 +36,12 @@ class MyApp extends StatelessWidget {
return RepositoryProvider<CrudRepository<User>>.value(
value: userRepository,
child: BlocProvider<UserCubit>(
create: (context) => UserCubit(userRepository)..getAll(),
child: MultiBlocProvider(
providers: [
BlocProvider<UserCubit>(
create: (context) => UserCubit(userRepository)..read(),
),
],
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
@ -78,9 +82,7 @@ class MyHomePage extends StatelessWidget {
title: Text(user?.name ?? 'Error'),
subtitle: Text(user?.id ?? 'Error'),
onTap: () {
context.read<UserCubit>().delete(
(user?.id)!,
);
context.read<UserCubit>().delete(id: (user?.id)!);
},
);
},
@ -108,22 +110,10 @@ class MyHomePage extends StatelessWidget {
),
ElevatedButton(
onPressed: () {
context.read<UserCubit>().deleteAll();
},
child: const Text("DeleteAll"),
),
ElevatedButton(
onPressed: () {
context.read<UserCubit>().getAll();
context.read<UserCubit>().read();
},
child: const Text("GetAll"),
),
ElevatedButton(
onPressed: () {
context.read<UserCubit>().query([LimitQuery(2)]);
},
child: const Text("Query"),
),
const SizedBox(height: 20),
],
),

View File

@ -18,37 +18,18 @@ import 'package:wyatt_crud_bloc/wyatt_crud_bloc.dart';
class User extends ObjectModel {
@override
String? id;
final String? id;
String? name;
String? email;
String? phone;
final String? name;
final String? email;
final String? phone;
User({
const User({
required this.name,
required this.email,
required this.phone,
this.id,
});
// User._();
// factory User.parser() {
// return User._();
// }
// @override
// User? from(DocumentSnapshot? object) {
// if (object == null) return null;
// if (object.exists) {
// return User(
// id: object.id,
// name: (object.data() as Map<String, dynamic>?)!['name'] as String,
// email: (object.data() as Map<String, dynamic>?)!['email'] as String,
// phone: (object.data() as Map<String, dynamic>?)!['phone'] as String,
// );
// }
// return null;
// }
Map<String, Object> toMap() {
return {

View File

@ -17,32 +17,25 @@
import 'package:crud_bloc_example/models.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
Create<User>? get crudCreate => Create(_crudRepository);
CreateOperation<User, dynamic>? get createOperation =>
Create(_crudRepository);
@override
Delete<User>? get crudDelete => Delete(_crudRepository);
DeleteOperation<User, dynamic>? get deleteOperation =>
Delete(_crudRepository);
@override
DeleteAll<User>? get crudDeleteAll => DeleteAll(_crudRepository);
ReadOperation<User, dynamic, dynamic>? get readOperation =>
GetAll(_crudRepository);
@override
Get<User>? get crudGet => Get(_crudRepository);
@override
GetAll<User>? get crudGetAll => GetAll(_crudRepository);
@override
Query<User>? get crudQuery => Query(_crudRepository);
@override
Update<User>? get crudUpdate => Update(_crudRepository);
@override
UpdateAll<User>? get crudUpdateAll => UpdateAll(_crudRepository);
UpdateOperation<User, dynamic>? get updateOperation =>
Update(_crudRepository);
}

View File

@ -15,3 +15,4 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'enums/where_query_type.dart';
export 'mixins/operation.dart';

View File

@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
/// Defines different query types for WhereQuery.
enum WhereQueryType {
isEqualTo,
isNotEqualTo,

View File

@ -0,0 +1,30 @@
// 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/>.
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/object_model.dart';
/// Defines every write operation in CRUD.
mixin CreateOperation<Model extends ObjectModel, Out> on AsyncUseCase<Model, Out> {}
/// Defines every read operation in CRUD.
mixin ReadOperation<Model extends ObjectModel, In, Out> on AsyncUseCase<In, Out> {}
/// Defines every update operation in CRUD.
mixin UpdateOperation<Model extends ObjectModel, In> on AsyncUseCase<In, void> {}
/// Defines every delete operation in CRUD.
mixin DeleteOperation<Model extends ObjectModel, In> on AsyncUseCase<In, void> {}

View File

@ -17,13 +17,17 @@
import 'dart:async';
import 'package:wyatt_crud_bloc/src/core/enums/where_query_type.dart';
import 'package:wyatt_crud_bloc/src/core/extensions/num_extension.dart';
import 'package:wyatt_crud_bloc/src/domain/data_sources/crud_data_source.dart';
import 'package:wyatt_crud_bloc/src/domain/data_sources/data_sources.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/object_model.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/query.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
/// {@template crud_in_memory_data_source_impl}
/// A [CrudDataSource] that stores data in memory.
/// {@endtemplate}
class CrudInMemoryDataSourceImpl<Model extends ObjectModel>
extends CrudDataSource<Model> {
/// {@macro crud_in_memory_data_source_impl}
CrudInMemoryDataSourceImpl({required this.toMap, Map<String, Model>? data})
: _data = data ?? {};
final Map<String, Model> _data;

View File

@ -20,15 +20,23 @@ import 'package:wyatt_crud_bloc/src/domain/data_sources/crud_data_source.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/object_model.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/query.dart';
/// {@template crud_firestore_data_source_impl}
/// A concrete implementation of [CrudDataSource] that uses
/// [FirebaseFirestore] as the data source.
/// {@endtemplate}
class CrudFirestoreDataSourceImpl<Model extends ObjectModel, Entity>
extends CrudDataSource<Model> {
/// {@macro crud_firestore_data_source_impl}
CrudFirestoreDataSourceImpl(
String collection, {
/// The function that converts a [DocumentSnapshot] to a [Model].
required Model Function(
DocumentSnapshot<Map<String, dynamic>>,
SnapshotOptions?,
)
fromFirestore,
/// The function that converts a [Model] to a [Map<String, Object?>].
required Map<String, Object?> Function(Model, SetOptions?) toFirestore,
FirebaseFirestore? firestore,
}) : _firestore = firestore ?? FirebaseFirestore.instance,

View File

@ -21,9 +21,13 @@ import 'package:wyatt_crud_bloc/src/domain/entities/query.dart';
import 'package:wyatt_crud_bloc/src/domain/repositories/crud_repository.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
/// {@template crud_repository_impl}
/// A repository that implements the [CrudRepository] interface.
/// {@endtemplate}
class CrudRepositoryImpl<Model extends ObjectModel>
extends CrudRepository<Model> {
CrudRepositoryImpl({
/// {@macro crud_repository_impl}
const CrudRepositoryImpl({
required CrudDataSource<Model> crudDataSource,
}) : _crudDataSource = crudDataSource;
final CrudDataSource<Model> _crudDataSource;
@ -99,6 +103,6 @@ class CrudRepositoryImpl<Model extends ObjectModel>
if (lst.isNotNull) {
return Ok<List<Model?>, AppException>(lst);
}
return Err<List<Model?>, AppException>(ServerException());
return Err<List<Model?>, AppException>(const ServerException());
});
}

View File

@ -17,27 +17,42 @@
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/query.dart';
/// {@template crud_data_source}
/// A [BaseDataSource] that provides SCRUD operations.
/// {@endtemplate}
abstract class CrudDataSource<Model> extends BaseDataSource {
/// {@macro crud_data_source}
const CrudDataSource();
/// Creates a new [Model] object.
Future<void> create(Model object, {String? id});
/// Gets a [Model] object by its [id].
Future<Model?> get(String id);
/// Gets all [Model] objects.
Future<List<Model?>> getAll();
/// Updates a [Model] object by its [id].
Future<void> update(
String id, {
Model? object,
Map<String, dynamic>? raw,
});
/// Updates all [Model] objects.
Future<void> updateAll(Map<String, Object?>? data);
/// Deletes a [Model] object by its [id].
Future<void> delete(String id);
/// Deletes all [Model] objects.
Future<void> deleteAll();
/// Queries [Model] objects by [conditions].
Future<List<Model?>> query(List<QueryInterface> conditions);
/// Streams [Model] objects by [conditions].
Stream<List<Model?>> stream({
String? id,
List<QueryInterface>? conditions,

View File

@ -16,6 +16,13 @@
import 'package:wyatt_architecture/wyatt_architecture.dart';
/// {@template object_model}
/// An abstract class that represents an object model.
/// {@endtemplate}
abstract class ObjectModel extends Entity {
/// {@macro object_model}
const ObjectModel();
/// The id of the object model.
String? get id;
}

View File

@ -17,27 +17,59 @@
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_crud_bloc/src/core/enums/where_query_type.dart';
// ignore: one_member_abstracts
abstract class QueryParser<Q> {
Q parser(QueryInterface condition, Q query);
// // ignore: one_member_abstracts
// abstract class QueryParser<Q> {
// Q parser(QueryInterface condition, Q query);
// }
typedef QueryParser<Q> = Q Function(QueryInterface condition, Q query);
/// {@template query}
/// An abstract class that represents a query.
/// {@endtemplate}
abstract class QueryInterface extends Entity {
/// {@macro query}
const QueryInterface();
}
abstract class QueryInterface extends Entity {}
/// {@template where_query}
/// Represents a where query.
/// {@endtemplate}
class WhereQuery<Value> extends QueryInterface {
WhereQuery(this.type, this.field, this.value);
/// {@macro where_query}
const WhereQuery(this.type, this.field, this.value);
/// The type of the where query.
final WhereQueryType type;
/// The field of the where query.
final String field;
/// The value of the where query.
final Value value;
}
/// {@template limit_query}
/// Represents a limit query.
/// {@endtemplate}
class LimitQuery extends QueryInterface {
LimitQuery(this.limit);
/// {@macro limit_query}
const LimitQuery(this.limit);
/// The limit of the limit query.
final int limit;
}
/// {@template offset_query}
/// Represents an offset query.
/// {@endtemplate}
class OrderByQuery extends QueryInterface {
OrderByQuery(this.field, {this.ascending = true});
/// {@macro offset_query}
const OrderByQuery(this.field, {this.ascending = true});
/// The field of the order by query.
final String field;
/// The ascending of the order by query.
final bool ascending;
}

View File

@ -18,20 +18,43 @@ import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/object_model.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/query.dart';
/// {@template crud_repository}
/// An abstract class that represents a SCRUD repository.
/// {@endtemplate}
abstract class CrudRepository<Model extends ObjectModel>
extends BaseRepository {
/// {@macro crud_repository}
const CrudRepository();
/// Creates a new object.
FutureOrResult<void> create(Model object, {String? id});
/// Gets an object by its [id].
FutureOrResult<Model?> get(String id);
/// Gets all objects.
FutureOrResult<List<Model?>> getAll();
/// Updates an object by its [id].
FutureOrResult<void> update(
String id, {
Model? object,
Map<String, dynamic>? raw,
});
/// Updates all objects.
FutureOrResult<void> updateAll(Map<String, Object?> raw);
/// Deletes an object by its [id].
FutureOrResult<void> delete(String id);
/// Deletes all objects.
FutureOrResult<void> deleteAll();
/// Queries objects by [conditions].
FutureOrResult<List<Model?>> query(List<QueryInterface> conditions);
/// Streams objects by [conditions].
StreamResult<List<Model?>> stream({
String? id,
List<QueryInterface>? conditions,

View File

@ -1,4 +1,3 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
@ -18,13 +17,19 @@
import 'dart:async';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_crud_bloc/src/core/mixins/operation.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/object_model.dart';
import 'package:wyatt_crud_bloc/src/domain/repositories/crud_repository.dart';
class Create<Model extends ObjectModel> extends AsyncUseCase<Model, void> {
final CrudRepository<Model> _crudRepository;
/// {@template create}
/// A use case that creates an object model.
/// {@endtemplate}
class Create<Model extends ObjectModel> extends AsyncUseCase<Model, void>
with CreateOperation<Model, void> {
/// {@macro create}
const Create(this._crudRepository);
Create(this._crudRepository);
final CrudRepository<Model> _crudRepository;
@override
FutureOr<void> onStart(Model? params) {

View File

@ -17,17 +17,24 @@
import 'dart:async';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_crud_bloc/src/core/mixins/operation.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/object_model.dart';
import 'package:wyatt_crud_bloc/src/domain/repositories/crud_repository.dart';
class Delete<Model extends ObjectModel> extends AsyncUseCase<String, void> {
Delete(this._crudRepository);
/// {@template delete}
/// A use case that deletes an object model.
/// {@endtemplate}
class Delete<Model extends ObjectModel> extends AsyncUseCase<String, void>
with DeleteOperation<Model, String> {
/// {@macro delete}
const Delete(this._crudRepository);
final CrudRepository<Model> _crudRepository;
@override
FutureOr<void> onStart(String? params) {
if (params == null) {
throw ClientException('Id cannot be null.');
throw const ClientException('Id cannot be null.');
}
}

View File

@ -15,11 +15,18 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_crud_bloc/src/core/mixins/operation.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/object_model.dart';
import 'package:wyatt_crud_bloc/src/domain/repositories/crud_repository.dart';
class DeleteAll<Model extends ObjectModel> extends AsyncUseCase<void, void> {
DeleteAll(this._crudRepository);
/// {@template delete_all}
/// A use case that deletes all the object models.
/// {@endtemplate}
class DeleteAll<Model extends ObjectModel> extends AsyncUseCase<void, void>
with DeleteOperation<Model, void> {
/// {@macro delete_all}
const DeleteAll(this._crudRepository);
final CrudRepository<Model> _crudRepository;
@override

View File

@ -17,17 +17,24 @@
import 'dart:async';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_crud_bloc/src/core/mixins/operation.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/object_model.dart';
import 'package:wyatt_crud_bloc/src/domain/repositories/crud_repository.dart';
class Get<Model extends ObjectModel> extends AsyncUseCase<String, Model?> {
/// {@template get}
/// A use case that gets an object model.
/// {@endtemplate}
class Get<Model extends ObjectModel> extends AsyncUseCase<String, Model?>
with ReadOperation<Model, String, Model?> {
/// {@macro get}
Get(this._crudRepository);
final CrudRepository<Model> _crudRepository;
@override
FutureOr<void> onStart(String? params) {
if (params == null) {
throw ClientException('Id cannot be null.');
throw const ClientException('Id cannot be null.');
}
}

View File

@ -15,12 +15,18 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_crud_bloc/src/core/mixins/operation.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/object_model.dart';
import 'package:wyatt_crud_bloc/src/domain/repositories/crud_repository.dart';
class GetAll<Model extends ObjectModel>
extends AsyncUseCase<void, List<Model?>> {
GetAll(this._crudRepository);
/// {@template get_all}
/// A use case that gets all the object models.
/// {@endtemplate}
class GetAll<Model extends ObjectModel> extends AsyncUseCase<void, List<Model?>>
with ReadOperation<Model, void, List<Model?>> {
/// {@macro get_all}
const GetAll(this._crudRepository);
final CrudRepository<Model> _crudRepository;
@override

View File

@ -1,4 +1,3 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
@ -17,12 +16,19 @@
import 'package:wyatt_crud_bloc/src/domain/entities/query.dart';
/// {@template stream_parameters}
/// Represents the parameters for a query stream
/// {@endtemplate}
class StreamParameters {
final String? id;
final List<QueryInterface>? conditions;
StreamParameters({
/// {@macro stream_parameters}
const StreamParameters({
this.id,
this.conditions,
});
/// The id of the object model.
final String? id;
/// The conditions of the query.
final List<QueryInterface>? conditions;
}

View File

@ -1,4 +1,3 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (C) 2022 WYATT GROUP
// Please see the AUTHORS file for details.
//
@ -15,14 +14,23 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
/// {@template update_parameters}
/// Represents the parameters for an update use case
/// {@endtemplate}
class UpdateParameters<Model> {
final String id;
final Model? object;
final Map<String, dynamic>? raw;
UpdateParameters({
/// {@macro update_parameters}
const UpdateParameters({
required this.id,
this.object,
this.raw,
});
/// The id of the object model.
final String id;
/// The object model.
final Model? object;
/// The raw data.
final Map<String, dynamic>? raw;
}

View File

@ -17,19 +17,26 @@
import 'dart:async';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_crud_bloc/src/core/mixins/operation.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/object_model.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/query.dart';
import 'package:wyatt_crud_bloc/src/domain/repositories/crud_repository.dart';
/// {@template query}
/// A use case that queries the object models.
/// {@endtemplate}
class Query<Model extends ObjectModel>
extends AsyncUseCase<List<QueryInterface>, List<Model?>> {
Query(this._crudRepository);
extends AsyncUseCase<List<QueryInterface>, List<Model?>>
with ReadOperation<Model, List<QueryInterface>, List<Model?>> {
/// {@macro query}
const Query(this._crudRepository);
final CrudRepository<Model> _crudRepository;
@override
FutureOr<void> onStart(List<QueryInterface>? params) {
if (params == null) {
throw ClientException('List of conditions cannot be null.');
throw const ClientException('List of conditions cannot be null.');
}
}

View File

@ -1,44 +0,0 @@
// Copyright (C) 2022 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/>.
// class Stream<Model extends ObjectModel> extends UseCase<StreamParameters,
//List<Model?>> {
// final CrudRepository<Model> _crudRepository;
// Stream(this._crudRepository);
// @override
// StreamResult<List<Model?>> call(StreamParameters params) =>
// _crudRepository.stream(id: params.id, conditions: params.conditions);
// }
// class StreamQuery<Model extends ObjectModel>
// extends StreamUseCase<StreamParameters, List<Model?>> {
// final CrudRepository<Model> _crudRepository;
// StreamQuery(this._crudRepository);
// @override
// FutureOr<void> onStart(StreamParameters? params) {
// if(params == null){
// throw ClientException('Stream parameters cannot be null.');
// }
// }
// @override
// FutureOrResult<Stream<List<Model?>>> call(StreamParameters? params) =>
// _crudRepository.stream();
// }

View File

@ -17,19 +17,26 @@
import 'dart:async';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_crud_bloc/src/core/mixins/operation.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/object_model.dart';
import 'package:wyatt_crud_bloc/src/domain/repositories/crud_repository.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/params/update_parameters.dart';
/// {@template update}
/// A use case that updates an object model.
/// {@endtemplate}
class Update<Model extends ObjectModel>
extends AsyncUseCase<UpdateParameters<Model>, void> {
Update(this._crudRepository);
extends AsyncUseCase<UpdateParameters<Model>, void>
with UpdateOperation<Model, UpdateParameters<Model>> {
/// {@macro update}
const Update(this._crudRepository);
final CrudRepository<Model> _crudRepository;
@override
FutureOr<void> onStart(UpdateParameters<Model>? params) {
if (params == null) {
throw ClientException('Update parameters cannot be null.');
throw const ClientException('Update parameters cannot be null.');
}
}

View File

@ -17,18 +17,25 @@
import 'dart:async';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_crud_bloc/src/core/mixins/operation.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/object_model.dart';
import 'package:wyatt_crud_bloc/src/domain/repositories/crud_repository.dart';
/// {@template update_all}
/// A use case that updates all the object models.
/// {@endtemplate}
class UpdateAll<Model extends ObjectModel>
extends AsyncUseCase<Map<String, Object?>, void> {
UpdateAll(this._crudRepository);
extends AsyncUseCase<Map<String, Object?>, void>
with UpdateOperation<Model, Map<String, Object?>> {
/// {@macro update_all}
const UpdateAll(this._crudRepository);
final CrudRepository<Model> _crudRepository;
@override
FutureOr<void> onStart(Map<String, Object?>? params) {
if (params == null) {
throw ClientException('Data cannot be null.');
throw const ClientException('Data cannot be null.');
}
}

View File

@ -21,6 +21,5 @@ export 'get.dart';
export 'get_all.dart';
export 'params/params.dart';
export 'query.dart';
export 'stream_query.dart';
export 'update.dart';
export 'update_all.dart';

View File

@ -0,0 +1,305 @@
// 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/>.
import 'dart:async';
import 'package:wyatt_crud_bloc/src/core/enums/where_query_type.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/object_model.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/query.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/create.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/delete.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/delete_all.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/get.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/get_all.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/params/update_parameters.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/query.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/update.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/update_all.dart';
import 'package:wyatt_crud_bloc/src/features/crud/blocs/crud_base_cubit/crud_base_cubit.dart';
/// {@template crud_cubit_advanced}
/// Cubit that handles CRUD operations with more granularity.
/// {@endtemplate}
abstract class CrudAdvancedCubit<Model extends ObjectModel>
extends CrudBaseCubit {
/// {@macro crud_cubit}
CrudAdvancedCubit() : super();
Create<Model>? get crudCreate;
DeleteAll<Model>? get crudDeleteAll;
Delete<Model>? get crudDelete;
GetAll<Model>? get crudGetAll;
Get<Model>? get crudGet;
Query<Model>? get crudQuery;
UpdateAll<Model>? get crudUpdateAll;
Update<Model>? get crudUpdate;
FutureOr<void> create(Model model) async {
final crud = crudCreate;
if (crud == null) {
return;
}
final stateCopy = state;
emit(const CrudLoading());
final result = await crud.call(model);
emit(
result.fold(
(_) {
if (stateCopy is CrudLoaded<Model?>) {
if (stateCopy.data == null) {
return CrudLoaded<Model?>(model);
}
if (stateCopy.data!.id == model.id) {
return CrudLoaded<Model?>(model);
}
return stateCopy;
}
if (stateCopy is CrudListLoaded<Model?>) {
if (stateCopy.data.isEmpty) {
return CrudListLoaded<Model?>([model]);
}
final List<Model?> lst = stateCopy.data.toList()..add(model);
return CrudListLoaded<Model?>(lst);
}
return const CrudOkReturn();
},
(error) => CrudError(error.toString()),
),
);
}
FutureOr<void> delete(String id) async {
final crud = crudDelete;
if (crud == null) {
return;
}
final stateCopy = state;
emit(const CrudLoading());
final result = await crud.call(id);
emit(
result.fold(
(_) {
if (stateCopy is CrudLoaded<Model?>) {
if (stateCopy.data?.id == id) {
return CrudLoaded<Model?>(null);
}
return stateCopy;
}
if (stateCopy is CrudListLoaded<Model?>) {
return CrudListLoaded<Model?>(
stateCopy.data.where((element) => element?.id != id).toList(),
);
}
return const CrudOkReturn();
},
(error) => CrudError(error.toString()),
),
);
}
FutureOr<void> deleteAll() async {
final crud = crudDeleteAll;
if (crud == null) {
return;
}
final stateCopy = state;
emit(const CrudLoading());
final result = await crud.call(null);
emit(
result.fold(
(_) {
if (stateCopy is CrudLoaded<Model?>) {
return CrudLoaded<Model?>(null);
}
if (stateCopy is CrudListLoaded<Model?>) {
return CrudListLoaded<Model?>(const []);
}
return const CrudOkReturn();
},
(error) => CrudError(error.toString()),
),
);
}
FutureOr<void> get(String id) async {
final crud = crudGet;
if (crud == null) {
return;
}
emit(const CrudLoading());
final result = await crud.call(id);
emit(
result.fold(
CrudLoaded<Model?>.new,
(error) => CrudError(error.toString()),
),
);
}
FutureOr<void> getAll() async {
final crud = crudGetAll;
if (crud == null) {
return;
}
emit(const CrudLoading());
final result = await crud.call(null);
emit(
result.fold(
CrudListLoaded<Model?>.new,
(error) => CrudError(error.toString()),
),
);
}
FutureOr<void> query(List<QueryInterface> conditions) async {
final crud = crudQuery;
if (crud == null) {
return;
}
emit(const CrudLoading());
final result = await crud.call(conditions);
emit(
result.fold(
CrudListLoaded<Model?>.new,
(error) => CrudError(error.toString()),
),
);
}
FutureOr<void> update(UpdateParameters<Model> param) async {
final crud = crudUpdate;
if (crud == null) {
return;
}
final stateCopy = state;
emit(const CrudLoading());
final result = await crud.call(param);
emit(
await result.foldAsync(
(_) async {
if (stateCopy is CrudLoaded<Model?>) {
if (stateCopy.data?.id == param.id) {
// Same object, need to update actual stateCopy
final crudGet = this.crudGet;
if (crudGet == null) {
// No read operation, can't update stateCopy.
return stateCopy;
}
final newVersion = await crudGet.call(param.id);
if (newVersion.isOk) {
return CrudLoaded<Model?>(newVersion.ok);
}
}
return stateCopy;
}
if (stateCopy is CrudListLoaded<Model?>) {
final bool listContains =
stateCopy.data.any((element) => element?.id == param.id);
if (listContains) {
// Loaded objects contains the modified object.
final crudGet = this.crudGet;
if (crudGet == null) {
// No read operation, can't update stateCopy.
return stateCopy;
}
final newVersion = await crudGet.call(param.id);
if (newVersion.isOk) {
final newList = stateCopy.data
.where(
(element) => element?.id != param.id,
)
.toList();
return CrudListLoaded<Model?>(newList + [newVersion.ok]);
}
}
return stateCopy;
}
return const CrudOkReturn();
},
(error) async => CrudError(error.toString()),
),
);
}
FutureOr<void> updateAll(Map<String, Object?> param) async {
final crud = crudUpdateAll;
if (crud == null) {
return;
}
final stateCopy = state;
emit(const CrudLoading());
final result = await crud.call(param);
emit(
await result.foldAsync(
(_) async {
if (stateCopy is CrudLoaded<Model?>) {
// Same object, need to update actual stateCopy
final crudGet = this.crudGet;
if (crudGet == null) {
// No read operation, can't update stateCopy.
return stateCopy;
}
final actualId = stateCopy.data?.id;
final newVersion = await crudGet.call(actualId ?? '');
if (newVersion.isOk) {
return CrudLoaded<Model?>(newVersion.ok);
}
return stateCopy;
}
if (stateCopy is CrudListLoaded<Model?>) {
final crudQuery = this.crudQuery;
if (crudQuery == null) {
// No read operation, can't update stateCopy.
return stateCopy;
}
// Load all id to retrieve exactly same object
// (not all because previous stateCopy can be a query result)
final List<String?> ids = stateCopy.data
.map(
(e) => e?.id,
)
.toList();
final result = await crudQuery.call([
WhereQuery(
WhereQueryType.whereIn,
'id',
ids,
)
]);
if (result.isOk) {
return CrudListLoaded<Model?>(result.ok ?? []);
}
return stateCopy;
}
return const CrudOkReturn();
},
(error) async => CrudError(error.toString()),
),
);
}
}

View File

@ -14,38 +14,15 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
extension NumExtension on num? {
bool operator <(num? other) {
if (this == null || other == null) {
return false;
}
return this < other;
}
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
bool operator >(num? other) {
if (this == null || other == null) {
return false;
}
return this > other;
}
part 'crud_state.dart';
bool operator <=(num? other) {
if (this == null && other == null) {
return true;
}
if (this == null || other == null) {
return false;
}
return this <= other;
}
bool operator >=(num? other) {
if (this == null && other == null) {
return true;
}
if (this == null || other == null) {
return false;
}
return this >= other;
}
/// {@template crud_base_cubit}
/// A base [Cubit] that handles SCRUD operations.
/// {@endtemplate}
abstract class CrudBaseCubit extends Cubit<CrudState> {
/// {@macro crud_base_cubit}
CrudBaseCubit() : super(const CrudInitial());
}

View File

@ -1,4 +1,4 @@
// Copyright (C) 2022 WYATT GROUP
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
part of 'crud_cubit.dart';
part of 'crud_base_cubit.dart';
abstract class CrudState extends Equatable {
const CrudState();
@ -23,18 +23,17 @@ abstract class CrudState extends Equatable {
List<Object?> get props => [];
}
class CrudInitial extends CrudState {}
class CrudLoading extends CrudState {}
abstract class CrudSuccess extends CrudState {
const CrudSuccess();
/// Initial state of the CrudBaseCubit.
class CrudInitial extends CrudState {
const CrudInitial();
}
class CrudOkReturn extends CrudState {
const CrudOkReturn();
/// Loading state of the CrudBaseCubit.
class CrudLoading extends CrudState {
const CrudLoading();
}
/// Error state of the CrudBaseCubit.
class CrudError extends CrudState {
const CrudError(this.message);
final String? message;
@ -43,6 +42,24 @@ class CrudError extends CrudState {
List<Object?> get props => [message];
}
/// Success state of the CrudBaseCubit.
/// This state is used to indicate that the operation was successful.
/// Can be one or list of objects.
abstract class CrudSuccess extends CrudState {
const CrudSuccess();
}
/// Success state of the CrudBaseCubit.
/// This state is used to indicate that the operation was successful.
/// Contains no objects.
/// Used for create, update, delete operations.
class CrudOkReturn extends CrudSuccess {
const CrudOkReturn();
}
/// Loaded state of the CrudBaseCubit.
/// This state is used to indicate that the operation was successful.
/// Contains one object.
class CrudLoaded<T> extends CrudSuccess {
const CrudLoaded(this.data);
final T? data;
@ -51,6 +68,9 @@ class CrudLoaded<T> extends CrudSuccess {
List<Object?> get props => [data];
}
/// Loaded state of the CrudBaseCubit.
/// This state is used to indicate that the operation was successful.
/// Contains list of objects.
class CrudListLoaded<T> extends CrudSuccess {
const CrudListLoaded(this.data);
final List<T?> data;

View File

@ -0,0 +1,369 @@
// 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/>.
import 'dart:async';
import 'package:wyatt_crud_bloc/src/core/enums/where_query_type.dart';
import 'package:wyatt_crud_bloc/src/core/mixins/operation.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/object_model.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/query.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/create.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/delete.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/delete_all.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/get.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/get_all.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/params/update_parameters.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/query.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/update.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/update_all.dart';
import 'package:wyatt_crud_bloc/src/features/crud/blocs/crud_base_cubit/crud_base_cubit.dart';
/// {@template crud_cubit}
/// Cubit that handles CRUD operations.
/// {@endtemplate}
abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
/// {@macro crud_cubit}
CrudCubit() : super();
/// Create operation.
/// Can be create.
CreateOperation<Model, dynamic>? get createOperation;
/// Read operation.
/// Can be get, getAll, query.
ReadOperation<Model, dynamic, dynamic>? get readOperation;
/// Update operation.
/// Can be update, updateAll.
UpdateOperation<Model, dynamic>? get updateOperation;
/// Delete operation.
/// Can be delete or deleteAll.
DeleteOperation<Model, dynamic>? get deleteOperation;
Expected? _checkOperation<Expected>(dynamic operation) {
if (operation == null) {
return null;
}
if (operation is! Expected) {
return null;
}
return operation;
}
FutureOr<void> create(Model model) async {
if (_checkOperation<Create<Model>>(createOperation) != null) {
return _create(model);
}
}
FutureOr<void> read({String? id, List<QueryInterface>? conditions}) async {
if (_checkOperation<Get<Model>>(readOperation) != null && id != null) {
return _get(id);
}
if (_checkOperation<GetAll<Model>>(readOperation) != null) {
return _getAll();
}
if (_checkOperation<Query<Model>>(readOperation) != null &&
conditions != null) {
return _query(conditions);
}
if (_checkOperation<Query<Model>>(readOperation) != null &&
conditions == null) {
return _getAll();
}
}
FutureOr<void> update(
UpdateParameters<Model>? single,
Map<String, dynamic>? all,
) async {
if (_checkOperation<Update<Model>>(updateOperation) != null &&
single != null) {
return _update(single);
}
if (_checkOperation<UpdateAll<Model>>(updateOperation) != null &&
all != null) {
return _updateAll(all);
}
}
FutureOr<void> delete({String? id}) async {
if (_checkOperation<Delete<Model>>(deleteOperation) != null && id != null) {
return _delete(id);
}
if (_checkOperation<DeleteAll<Model>>(deleteOperation) != null) {
return _deleteAll();
}
}
FutureOr<void> _create(Model model) async {
final crud = _checkOperation<Create<Model>>(createOperation);
if (crud == null) {
return;
}
final stateCopy = state;
emit(const CrudLoading());
final result = await crud.call(model);
emit(
result.fold(
(_) {
if (stateCopy is CrudLoaded<Model?>) {
if (stateCopy.data == null) {
return CrudLoaded<Model?>(model);
}
if (stateCopy.data!.id == model.id) {
return CrudLoaded<Model?>(model);
}
return stateCopy;
}
if (stateCopy is CrudListLoaded<Model?>) {
if (stateCopy.data.isEmpty) {
return CrudListLoaded<Model?>([model]);
}
final List<Model?> lst = stateCopy.data.toList()..add(model);
return CrudListLoaded<Model?>(lst);
}
return const CrudOkReturn();
},
(error) => CrudError(error.toString()),
),
);
}
FutureOr<void> _delete(String id) async {
final crud = _checkOperation<Delete<Model>>(deleteOperation);
if (crud == null) {
return;
}
final stateCopy = state;
emit(const CrudLoading());
final result = await crud.call(id);
emit(
result.fold(
(_) {
if (stateCopy is CrudLoaded<Model?>) {
if (stateCopy.data?.id == id) {
return CrudLoaded<Model?>(null);
}
return stateCopy;
}
if (stateCopy is CrudListLoaded<Model?>) {
return CrudListLoaded<Model?>(
stateCopy.data.where((element) => element?.id != id).toList(),
);
}
return const CrudOkReturn();
},
(error) => CrudError(error.toString()),
),
);
}
FutureOr<void> _deleteAll() async {
final crud = _checkOperation<DeleteAll<Model>>(deleteOperation);
if (crud == null) {
return;
}
final stateCopy = state;
emit(const CrudLoading());
final result = await crud.call(null);
emit(
result.fold(
(_) {
if (stateCopy is CrudLoaded<Model?>) {
return CrudLoaded<Model?>(null);
}
if (stateCopy is CrudListLoaded<Model?>) {
return CrudListLoaded<Model?>(const []);
}
return const CrudOkReturn();
},
(error) => CrudError(error.toString()),
),
);
}
FutureOr<void> _get(String id) async {
final crud = _checkOperation<Get<Model>>(readOperation);
if (crud == null) {
return;
}
emit(const CrudLoading());
final result = await crud.call(id);
emit(
result.fold(
CrudLoaded<Model?>.new,
(error) => CrudError(error.toString()),
),
);
}
FutureOr<void> _getAll() async {
final crud = _checkOperation<GetAll<Model>>(readOperation);
if (crud == null) {
return;
}
emit(const CrudLoading());
final result = await crud.call(null);
emit(
result.fold(
CrudListLoaded<Model?>.new,
(error) => CrudError(error.toString()),
),
);
}
FutureOr<void> _query(List<QueryInterface> conditions) async {
final crud = _checkOperation<Query<Model>>(readOperation);
if (crud == null) {
return;
}
emit(const CrudLoading());
final result = await crud.call(conditions);
emit(
result.fold(
CrudListLoaded<Model?>.new,
(error) => CrudError(error.toString()),
),
);
}
FutureOr<void> _update(UpdateParameters<Model> param) async {
final crud = _checkOperation<Update<Model>>(updateOperation);
if (crud == null) {
return;
}
final stateCopy = state;
emit(const CrudLoading());
final result = await crud.call(param);
emit(
await result.foldAsync(
(_) async {
if (stateCopy is CrudLoaded<Model?>) {
if (stateCopy.data?.id == param.id) {
// Same object, need to update actual stateCopy
final crudGet = _checkOperation<Get<Model>>(readOperation);
if (crudGet == null) {
// No read operation, can't update stateCopy.
return stateCopy;
}
final newVersion = await crudGet.call(param.id);
if (newVersion.isOk) {
return CrudLoaded<Model?>(newVersion.ok);
}
}
return stateCopy;
}
if (stateCopy is CrudListLoaded<Model?>) {
final bool listContains =
stateCopy.data.any((element) => element?.id == param.id);
if (listContains) {
// Loaded objects contains the modified object.
final crudGet = _checkOperation<Get<Model>>(readOperation);
if (crudGet == null) {
// No read operation, can't update stateCopy.
return stateCopy;
}
final newVersion = await crudGet.call(param.id);
if (newVersion.isOk) {
final newList = stateCopy.data
.where(
(element) => element?.id != param.id,
)
.toList();
return CrudListLoaded<Model?>(newList + [newVersion.ok]);
}
}
return stateCopy;
}
return const CrudOkReturn();
},
(error) async => CrudError(error.toString()),
),
);
}
FutureOr<void> _updateAll(Map<String, Object?> param) async {
final crud = _checkOperation<UpdateAll<Model>>(updateOperation);
if (crud == null) {
return;
}
final stateCopy = state;
emit(const CrudLoading());
final result = await crud.call(param);
emit(
await result.foldAsync(
(_) async {
if (stateCopy is CrudLoaded<Model?>) {
// Same object, need to update actual stateCopy
final crudGet = _checkOperation<Get<Model>>(readOperation);
if (crudGet == null) {
// No read operation, can't update stateCopy.
return stateCopy;
}
final actualId = stateCopy.data?.id;
final newVersion = await crudGet.call(actualId ?? '');
if (newVersion.isOk) {
return CrudLoaded<Model?>(newVersion.ok);
}
return stateCopy;
}
if (stateCopy is CrudListLoaded<Model?>) {
final crudQuery = _checkOperation<Query<Model>>(readOperation);
if (crudQuery == null) {
// No read operation, can't update stateCopy.
return stateCopy;
}
// Load all id to retrieve exactly same object
// (not all because previous stateCopy can be a query result)
final List<String?> ids = stateCopy.data
.map(
(e) => e?.id,
)
.toList();
final result = await crudQuery.call([
WhereQuery(
WhereQueryType.whereIn,
'id',
ids,
)
]);
if (result.isOk) {
return CrudListLoaded<Model?>(result.ok ?? []);
}
return stateCopy;
}
return const CrudOkReturn();
},
(error) async => CrudError(error.toString()),
),
);
}
}

View File

@ -15,15 +15,19 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:flutter/material.dart';
import 'package:wyatt_crud_bloc/src/features/crud/cubit/crud_cubit.dart';
import 'package:wyatt_crud_bloc/src/features/crud/blocs/crud_base_cubit/crud_base_cubit.dart';
/// {@template crud_builder}
/// A widget that builds itself based on the latest snapshot of interaction
/// with a [CrudBaseCubit].
///
/// * I = Initial State
/// * L = Loading State
/// * S = Success State
/// * E = Error State
/// {@endtemplate}
class CrudBuilder<I, L, S, E> extends StatelessWidget {
/// `<I, L, S, E>`
///
/// - I: the Initial State
/// - L: the Loading State
/// - S: the Success State
/// - E: the Error State
/// {@macro crud_builder}
const CrudBuilder({
required this.state,
required this.builder,
@ -34,11 +38,11 @@ class CrudBuilder<I, L, S, E> extends StatelessWidget {
super.key,
});
/// `<CrudInitial, CrudLoading, S extends CrudSuccess, CrudError>`
/// {@macro crud_builder}
///
/// - S: the Success State
///
/// For CrudStates only.
/// This factory constructor is used to create a [CrudBuilder] with
/// [CrudState]s. `S` is the Success State, and it must be a subtype of
/// [CrudSuccess]. It the only type that you have to specify.
static CrudBuilder<CrudInitial, CrudLoading, CrudSuccess, CrudError>
typed<S extends CrudSuccess>({
required CrudState state,

View File

@ -14,5 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'blocs/crud_advanced_cubit.dart';
export 'blocs/crud_base_cubit/crud_base_cubit.dart';
export 'blocs/crud_cubit.dart';
export 'builder/builder.dart';
export 'cubit/crud_cubit.dart';

View File

@ -1,266 +0,0 @@
// Copyright (C) 2022 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/>.
import 'dart:async';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_crud_bloc/src/core/enums/where_query_type.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/object_model.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/query.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/create.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/delete.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/delete_all.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/get.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/get_all.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/params/update_parameters.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/query.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/update.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/update_all.dart';
part 'crud_state.dart';
abstract class CrudCubit<Model extends ObjectModel> extends Cubit<CrudState> {
CrudCubit() : super(CrudInitial());
Create<Model>? get crudCreate;
DeleteAll<Model>? get crudDeleteAll;
Delete<Model>? get crudDelete;
GetAll<Model>? get crudGetAll;
Get<Model>? get crudGet;
Query<Model>? get crudQuery;
UpdateAll<Model>? get crudUpdateAll;
Update<Model>? get crudUpdate;
FutureOr<void> create(Model model) async {
if (crudCreate != null) {
final stateCopy = state;
emit(CrudLoading());
final result = await crudCreate!.call(model);
emit(
result.fold(
(_) {
if (stateCopy is CrudLoaded<Model?>) {
return stateCopy;
}
if (stateCopy is CrudListLoaded<Model?>) {
if (stateCopy.data.isEmpty) {
return CrudListLoaded<Model?>([model]);
}
final List<Model?> lst = stateCopy.data.toList()..add(model);
return CrudListLoaded<Model?>(lst);
}
return const CrudOkReturn();
},
(error) => CrudError(error.toString()),
),
);
}
}
FutureOr<void> delete(String id) async {
if (crudDelete != null) {
final stateCopy = state;
emit(CrudLoading());
final result = await crudDelete!.call(id);
emit(
result.fold(
(_) {
if (stateCopy is CrudLoaded<Model?>) {
return stateCopy;
}
if (stateCopy is CrudListLoaded<Model?>) {
return CrudListLoaded<Model?>(
stateCopy.data.where((element) => element?.id != id).toList(),
);
}
return const CrudOkReturn();
},
(error) => CrudError(error.toString()),
),
);
}
}
FutureOr<void> deleteAll() async {
if (crudDeleteAll != null) {
final stateCopy = state;
emit(CrudLoading());
final result = await crudDeleteAll!.call(null);
emit(
result.fold(
(_) {
if (stateCopy is CrudLoaded<Model?>) {
return CrudLoaded<Model?>(null);
}
if (stateCopy is CrudListLoaded<Model?>) {
return CrudListLoaded<Model?>(const []);
}
return const CrudOkReturn();
},
(error) => CrudError(error.toString()),
),
);
}
}
FutureOr<void> get(String id) async {
if (crudGet != null) {
emit(CrudLoading());
final result = await crudGet!.call(id);
emit(
result.fold(
CrudLoaded<Model?>.new,
(error) => CrudError(error.toString()),
),
);
}
}
FutureOr<void> getAll() async {
if (crudGetAll != null) {
emit(CrudLoading());
final result = await crudGetAll!.call(null);
emit(
result.fold(
CrudListLoaded<Model?>.new,
(error) => CrudError(error.toString()),
),
);
}
}
FutureOr<void> query(List<QueryInterface> conditions) async {
if (crudQuery != null) {
emit(CrudLoading());
final result = await crudQuery!.call(conditions);
emit(
result.fold(
CrudListLoaded<Model?>.new,
(error) => CrudError(error.toString()),
),
);
}
}
FutureOr<void> update(UpdateParameters<Model> param) async {
if (crudUpdate != null) {
final stateCopy = state;
emit(CrudLoading());
final result = await crudUpdate!.call(param);
emit(
await result.foldAsync(
(_) async {
if (stateCopy is CrudLoaded<Model?>) {
if (stateCopy.data?.id == param.id) {
// Same object, need to update actual stateCopy
if (crudGet == null) {
throw ClientException(
'Need to init Get usecase to use update.',
);
}
final newVersion = await crudGet!.call(param.id);
if (newVersion.isOk) {
return CrudLoaded<Model?>(newVersion.ok);
}
}
return stateCopy;
}
if (stateCopy is CrudListLoaded<Model?>) {
final bool listContains =
stateCopy.data.any((element) => element?.id == param.id);
if (listContains) {
// Loaded objects contains the modified object.
if (crudGet == null) {
throw ClientException(
'Need to init Get usecase to use update.',
);
}
final newVersion = await crudGet!.call(param.id);
if (newVersion.isOk) {
final newList = stateCopy.data
.where(
(element) => element?.id != param.id,
)
.toList();
return CrudListLoaded<Model?>(newList + [newVersion.ok]);
}
}
return stateCopy;
}
return const CrudOkReturn();
},
(error) async => CrudError(error.toString()),
),
);
}
}
FutureOr<void> updateAll(Map<String, Object?> param) async {
if (crudUpdateAll != null) {
final stateCopy = state;
emit(CrudLoading());
final result = await crudUpdateAll!.call(param);
emit(
await result.foldAsync(
(_) async {
if (stateCopy is CrudLoaded<Model?>) {
// Same object, need to update actual stateCopy
if (crudGet == null) {
throw ClientException(
'Need to init Get usecase to use updateAll.',
);
}
final actualId = stateCopy.data?.id;
final newVersion = await crudGet!.call(actualId ?? '');
if (newVersion.isOk) {
return CrudLoaded<Model?>(newVersion.ok);
}
return stateCopy;
}
if (stateCopy is CrudListLoaded<Model?>) {
if (crudQuery == null) {
throw ClientException(
'Need to init Query usecase to use updateAll.',
);
}
// Load all id to retrieve exactly same object
// (not all because previous stateCopy can be a query result)
final List<String?> ids = stateCopy.data
.map(
(e) => e?.id,
)
.toList();
final result = await crudQuery!.call([
WhereQuery(
WhereQueryType.whereIn,
'id',
ids,
)
]);
if (result.isOk) {
return CrudListLoaded<Model?>(result.ok ?? []);
}
return stateCopy;
}
return const CrudOkReturn();
},
(error) async => CrudError(error.toString()),
),
);
}
}
}

View File

@ -10,8 +10,7 @@ environment:
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
flutter: { sdk: flutter }
flutter_bloc: ^8.1.1
equatable: ^2.0.5
@ -26,8 +25,7 @@ dependencies:
version: ^0.0.4
dev_dependencies:
flutter_test:
sdk: flutter
flutter_test: { sdk: flutter }
bloc_test: ^9.1.0
wyatt_analysis:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/