diff --git a/packages/wyatt_crud_bloc/README.md b/packages/wyatt_crud_bloc/README.md index 8b55e735..4f483186 100644 --- a/packages/wyatt_crud_bloc/README.md +++ b/packages/wyatt_crud_bloc/README.md @@ -1,39 +1,167 @@ - -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 +

+ Style: Wyatt Analysis + SDK: Flutter +

-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 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 { + final CrudRepository _crudRepository; + + UserCubit(this._crudRepository); + + @override + CreateOperation? get createOperation => + Create(_crudRepository); + + @override + DeleteOperation? get deleteOperation => + Delete(_crudRepository); + + @override + ReadOperation? get readOperation => + GetAll(_crudRepository); + + @override + UpdateOperation? 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 userLocalDataSource = + CrudInMemoryDataSourceImpl(toMap: (user) => user.toMap()); + + final CrudRepository userRepository = + CrudRepositoryImpl(crudDataSource: userLocalDataSource); + + return RepositoryProvider>.value( + value: userRepository, + child: BlocProvider( + 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( + builder: (context, state) { + return CrudBuilder.typed>( + 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().delete(id: (user?.id)!); + }, + ); + }, + ); + }), + initialBuilder: (context, state) => const Text("Loading..."), + loadingBuilder: (context, state) => const Text("Loading..."), + errorBuilder: (context, state) => Text("Error: $state"), + ); + }, +), +... +``` diff --git a/packages/wyatt_crud_bloc/example/lib/app.dart b/packages/wyatt_crud_bloc/example/lib/app.dart index 47d7e59e..d5339343 100644 --- a/packages/wyatt_crud_bloc/example/lib/app.dart +++ b/packages/wyatt_crud_bloc/example/lib/app.dart @@ -36,8 +36,12 @@ class MyApp extends StatelessWidget { return RepositoryProvider>.value( value: userRepository, - child: BlocProvider( - create: (context) => UserCubit(userRepository)..getAll(), + child: MultiBlocProvider( + providers: [ + BlocProvider( + 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().delete( - (user?.id)!, - ); + context.read().delete(id: (user?.id)!); }, ); }, @@ -108,22 +110,10 @@ class MyHomePage extends StatelessWidget { ), ElevatedButton( onPressed: () { - context.read().deleteAll(); - }, - child: const Text("DeleteAll"), - ), - ElevatedButton( - onPressed: () { - context.read().getAll(); + context.read().read(); }, child: const Text("GetAll"), ), - ElevatedButton( - onPressed: () { - context.read().query([LimitQuery(2)]); - }, - child: const Text("Query"), - ), const SizedBox(height: 20), ], ), diff --git a/packages/wyatt_crud_bloc/example/lib/models.dart b/packages/wyatt_crud_bloc/example/lib/models.dart index b4ccfebf..e9ed1c7f 100644 --- a/packages/wyatt_crud_bloc/example/lib/models.dart +++ b/packages/wyatt_crud_bloc/example/lib/models.dart @@ -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?)!['name'] as String, - // email: (object.data() as Map?)!['email'] as String, - // phone: (object.data() as Map?)!['phone'] as String, - // ); - // } - // return null; - // } Map toMap() { return { diff --git a/packages/wyatt_crud_bloc/example/lib/user_cubit.dart b/packages/wyatt_crud_bloc/example/lib/user_cubit.dart index 703d6686..cc34df6c 100644 --- a/packages/wyatt_crud_bloc/example/lib/user_cubit.dart +++ b/packages/wyatt_crud_bloc/example/lib/user_cubit.dart @@ -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 { final CrudRepository _crudRepository; UserCubit(this._crudRepository); @override - Create? get crudCreate => Create(_crudRepository); + CreateOperation? get createOperation => + Create(_crudRepository); @override - Delete? get crudDelete => Delete(_crudRepository); + DeleteOperation? get deleteOperation => + Delete(_crudRepository); @override - DeleteAll? get crudDeleteAll => DeleteAll(_crudRepository); + ReadOperation? get readOperation => + GetAll(_crudRepository); @override - Get? get crudGet => Get(_crudRepository); - - @override - GetAll? get crudGetAll => GetAll(_crudRepository); - - @override - Query? get crudQuery => Query(_crudRepository); - - @override - Update? get crudUpdate => Update(_crudRepository); - - @override - UpdateAll? get crudUpdateAll => UpdateAll(_crudRepository); + UpdateOperation? get updateOperation => + Update(_crudRepository); } diff --git a/packages/wyatt_crud_bloc/lib/src/core/core.dart b/packages/wyatt_crud_bloc/lib/src/core/core.dart index 1d218692..12ebcadf 100644 --- a/packages/wyatt_crud_bloc/lib/src/core/core.dart +++ b/packages/wyatt_crud_bloc/lib/src/core/core.dart @@ -15,3 +15,4 @@ // along with this program. If not, see . export 'enums/where_query_type.dart'; +export 'mixins/operation.dart'; diff --git a/packages/wyatt_crud_bloc/lib/src/core/enums/where_query_type.dart b/packages/wyatt_crud_bloc/lib/src/core/enums/where_query_type.dart index f20879e7..6a7c9bfe 100644 --- a/packages/wyatt_crud_bloc/lib/src/core/enums/where_query_type.dart +++ b/packages/wyatt_crud_bloc/lib/src/core/enums/where_query_type.dart @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +/// Defines different query types for WhereQuery. enum WhereQueryType { isEqualTo, isNotEqualTo, diff --git a/packages/wyatt_crud_bloc/lib/src/core/mixins/operation.dart b/packages/wyatt_crud_bloc/lib/src/core/mixins/operation.dart new file mode 100644 index 00000000..7a1b9b09 --- /dev/null +++ b/packages/wyatt_crud_bloc/lib/src/core/mixins/operation.dart @@ -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 . + +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 on AsyncUseCase {} + +/// Defines every read operation in CRUD. +mixin ReadOperation on AsyncUseCase {} + +/// Defines every update operation in CRUD. +mixin UpdateOperation on AsyncUseCase {} + +/// Defines every delete operation in CRUD. +mixin DeleteOperation on AsyncUseCase {} diff --git a/packages/wyatt_crud_bloc/lib/src/data/data_sources/local/crud_in_memory_data_source_impl.dart b/packages/wyatt_crud_bloc/lib/src/data/data_sources/local/crud_in_memory_data_source_impl.dart index bb892251..7c2f706b 100644 --- a/packages/wyatt_crud_bloc/lib/src/data/data_sources/local/crud_in_memory_data_source_impl.dart +++ b/packages/wyatt_crud_bloc/lib/src/data/data_sources/local/crud_in_memory_data_source_impl.dart @@ -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 extends CrudDataSource { + /// {@macro crud_in_memory_data_source_impl} CrudInMemoryDataSourceImpl({required this.toMap, Map? data}) : _data = data ?? {}; final Map _data; diff --git a/packages/wyatt_crud_bloc/lib/src/data/data_sources/remote/crud_firestore_data_source_impl.dart b/packages/wyatt_crud_bloc/lib/src/data/data_sources/remote/crud_firestore_data_source_impl.dart index e70ecc9c..2915f2e6 100644 --- a/packages/wyatt_crud_bloc/lib/src/data/data_sources/remote/crud_firestore_data_source_impl.dart +++ b/packages/wyatt_crud_bloc/lib/src/data/data_sources/remote/crud_firestore_data_source_impl.dart @@ -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 extends CrudDataSource { + /// {@macro crud_firestore_data_source_impl} CrudFirestoreDataSourceImpl( String collection, { + /// The function that converts a [DocumentSnapshot] to a [Model]. required Model Function( DocumentSnapshot>, SnapshotOptions?, ) fromFirestore, + + /// The function that converts a [Model] to a [Map]. required Map Function(Model, SetOptions?) toFirestore, FirebaseFirestore? firestore, }) : _firestore = firestore ?? FirebaseFirestore.instance, diff --git a/packages/wyatt_crud_bloc/lib/src/data/repositories/crud_repository_impl.dart b/packages/wyatt_crud_bloc/lib/src/data/repositories/crud_repository_impl.dart index f26fdc1d..7f068be2 100644 --- a/packages/wyatt_crud_bloc/lib/src/data/repositories/crud_repository_impl.dart +++ b/packages/wyatt_crud_bloc/lib/src/data/repositories/crud_repository_impl.dart @@ -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 extends CrudRepository { - CrudRepositoryImpl({ + /// {@macro crud_repository_impl} + const CrudRepositoryImpl({ required CrudDataSource crudDataSource, }) : _crudDataSource = crudDataSource; final CrudDataSource _crudDataSource; @@ -99,6 +103,6 @@ class CrudRepositoryImpl if (lst.isNotNull) { return Ok, AppException>(lst); } - return Err, AppException>(ServerException()); + return Err, AppException>(const ServerException()); }); } diff --git a/packages/wyatt_crud_bloc/lib/src/domain/data_sources/crud_data_source.dart b/packages/wyatt_crud_bloc/lib/src/domain/data_sources/crud_data_source.dart index 95defb4c..b6d0ba30 100644 --- a/packages/wyatt_crud_bloc/lib/src/domain/data_sources/crud_data_source.dart +++ b/packages/wyatt_crud_bloc/lib/src/domain/data_sources/crud_data_source.dart @@ -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 extends BaseDataSource { + /// {@macro crud_data_source} + const CrudDataSource(); + + /// Creates a new [Model] object. Future create(Model object, {String? id}); + /// Gets a [Model] object by its [id]. Future get(String id); + /// Gets all [Model] objects. Future> getAll(); + /// Updates a [Model] object by its [id]. Future update( String id, { Model? object, Map? raw, }); + /// Updates all [Model] objects. Future updateAll(Map? data); + /// Deletes a [Model] object by its [id]. Future delete(String id); + /// Deletes all [Model] objects. Future deleteAll(); + /// Queries [Model] objects by [conditions]. Future> query(List conditions); + /// Streams [Model] objects by [conditions]. Stream> stream({ String? id, List? conditions, diff --git a/packages/wyatt_crud_bloc/lib/src/domain/entities/object_model.dart b/packages/wyatt_crud_bloc/lib/src/domain/entities/object_model.dart index 2b419e41..10dc5a25 100644 --- a/packages/wyatt_crud_bloc/lib/src/domain/entities/object_model.dart +++ b/packages/wyatt_crud_bloc/lib/src/domain/entities/object_model.dart @@ -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; } diff --git a/packages/wyatt_crud_bloc/lib/src/domain/entities/query.dart b/packages/wyatt_crud_bloc/lib/src/domain/entities/query.dart index a414f3ff..5fe8908f 100644 --- a/packages/wyatt_crud_bloc/lib/src/domain/entities/query.dart +++ b/packages/wyatt_crud_bloc/lib/src/domain/entities/query.dart @@ -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 parser(QueryInterface condition, Q query); +// // ignore: one_member_abstracts +// abstract class QueryParser { +// Q parser(QueryInterface condition, Q query); +// } + +typedef QueryParser = 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 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; } diff --git a/packages/wyatt_crud_bloc/lib/src/domain/repositories/crud_repository.dart b/packages/wyatt_crud_bloc/lib/src/domain/repositories/crud_repository.dart index f0313c44..95b52190 100644 --- a/packages/wyatt_crud_bloc/lib/src/domain/repositories/crud_repository.dart +++ b/packages/wyatt_crud_bloc/lib/src/domain/repositories/crud_repository.dart @@ -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 extends BaseRepository { + /// {@macro crud_repository} + const CrudRepository(); + + /// Creates a new object. FutureOrResult create(Model object, {String? id}); + + /// Gets an object by its [id]. FutureOrResult get(String id); + + /// Gets all objects. FutureOrResult> getAll(); + + /// Updates an object by its [id]. FutureOrResult update( String id, { Model? object, Map? raw, }); + + /// Updates all objects. FutureOrResult updateAll(Map raw); + + /// Deletes an object by its [id]. FutureOrResult delete(String id); + + /// Deletes all objects. FutureOrResult deleteAll(); + + /// Queries objects by [conditions]. FutureOrResult> query(List conditions); + + /// Streams objects by [conditions]. StreamResult> stream({ String? id, List? conditions, diff --git a/packages/wyatt_crud_bloc/lib/src/domain/usecases/create.dart b/packages/wyatt_crud_bloc/lib/src/domain/usecases/create.dart index 8a6940b5..ff88a046 100644 --- a/packages/wyatt_crud_bloc/lib/src/domain/usecases/create.dart +++ b/packages/wyatt_crud_bloc/lib/src/domain/usecases/create.dart @@ -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 extends AsyncUseCase { - final CrudRepository _crudRepository; +/// {@template create} +/// A use case that creates an object model. +/// {@endtemplate} +class Create extends AsyncUseCase + with CreateOperation { + /// {@macro create} + const Create(this._crudRepository); - Create(this._crudRepository); + final CrudRepository _crudRepository; @override FutureOr onStart(Model? params) { diff --git a/packages/wyatt_crud_bloc/lib/src/domain/usecases/delete.dart b/packages/wyatt_crud_bloc/lib/src/domain/usecases/delete.dart index cdebd878..d8ffb17d 100644 --- a/packages/wyatt_crud_bloc/lib/src/domain/usecases/delete.dart +++ b/packages/wyatt_crud_bloc/lib/src/domain/usecases/delete.dart @@ -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 extends AsyncUseCase { - Delete(this._crudRepository); +/// {@template delete} +/// A use case that deletes an object model. +/// {@endtemplate} +class Delete extends AsyncUseCase + with DeleteOperation { + /// {@macro delete} + const Delete(this._crudRepository); + final CrudRepository _crudRepository; @override FutureOr onStart(String? params) { if (params == null) { - throw ClientException('Id cannot be null.'); + throw const ClientException('Id cannot be null.'); } } diff --git a/packages/wyatt_crud_bloc/lib/src/domain/usecases/delete_all.dart b/packages/wyatt_crud_bloc/lib/src/domain/usecases/delete_all.dart index a1a36275..ae1935d7 100644 --- a/packages/wyatt_crud_bloc/lib/src/domain/usecases/delete_all.dart +++ b/packages/wyatt_crud_bloc/lib/src/domain/usecases/delete_all.dart @@ -15,11 +15,18 @@ // along with this program. If not, see . 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 extends AsyncUseCase { - DeleteAll(this._crudRepository); +/// {@template delete_all} +/// A use case that deletes all the object models. +/// {@endtemplate} +class DeleteAll extends AsyncUseCase + with DeleteOperation { + /// {@macro delete_all} + const DeleteAll(this._crudRepository); + final CrudRepository _crudRepository; @override diff --git a/packages/wyatt_crud_bloc/lib/src/domain/usecases/get.dart b/packages/wyatt_crud_bloc/lib/src/domain/usecases/get.dart index 40d6719c..08850936 100644 --- a/packages/wyatt_crud_bloc/lib/src/domain/usecases/get.dart +++ b/packages/wyatt_crud_bloc/lib/src/domain/usecases/get.dart @@ -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 extends AsyncUseCase { +/// {@template get} +/// A use case that gets an object model. +/// {@endtemplate} +class Get extends AsyncUseCase + with ReadOperation { + /// {@macro get} Get(this._crudRepository); + final CrudRepository _crudRepository; @override FutureOr onStart(String? params) { if (params == null) { - throw ClientException('Id cannot be null.'); + throw const ClientException('Id cannot be null.'); } } diff --git a/packages/wyatt_crud_bloc/lib/src/domain/usecases/get_all.dart b/packages/wyatt_crud_bloc/lib/src/domain/usecases/get_all.dart index 6fde76b7..63379212 100644 --- a/packages/wyatt_crud_bloc/lib/src/domain/usecases/get_all.dart +++ b/packages/wyatt_crud_bloc/lib/src/domain/usecases/get_all.dart @@ -15,12 +15,18 @@ // along with this program. If not, see . 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 - extends AsyncUseCase> { - GetAll(this._crudRepository); +/// {@template get_all} +/// A use case that gets all the object models. +/// {@endtemplate} +class GetAll extends AsyncUseCase> + with ReadOperation> { + /// {@macro get_all} + const GetAll(this._crudRepository); + final CrudRepository _crudRepository; @override diff --git a/packages/wyatt_crud_bloc/lib/src/domain/usecases/params/stream_parameters.dart b/packages/wyatt_crud_bloc/lib/src/domain/usecases/params/stream_parameters.dart index ed7768d4..0b2d178b 100644 --- a/packages/wyatt_crud_bloc/lib/src/domain/usecases/params/stream_parameters.dart +++ b/packages/wyatt_crud_bloc/lib/src/domain/usecases/params/stream_parameters.dart @@ -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? 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? conditions; } diff --git a/packages/wyatt_crud_bloc/lib/src/domain/usecases/params/update_parameters.dart b/packages/wyatt_crud_bloc/lib/src/domain/usecases/params/update_parameters.dart index cbe4cd2b..c44ab784 100644 --- a/packages/wyatt_crud_bloc/lib/src/domain/usecases/params/update_parameters.dart +++ b/packages/wyatt_crud_bloc/lib/src/domain/usecases/params/update_parameters.dart @@ -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 . +/// {@template update_parameters} +/// Represents the parameters for an update use case +/// {@endtemplate} class UpdateParameters { - final String id; - final Model? object; - final Map? 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? raw; } diff --git a/packages/wyatt_crud_bloc/lib/src/domain/usecases/query.dart b/packages/wyatt_crud_bloc/lib/src/domain/usecases/query.dart index 2070b015..4869fa0d 100644 --- a/packages/wyatt_crud_bloc/lib/src/domain/usecases/query.dart +++ b/packages/wyatt_crud_bloc/lib/src/domain/usecases/query.dart @@ -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 - extends AsyncUseCase, List> { - Query(this._crudRepository); + extends AsyncUseCase, List> + with ReadOperation, List> { + /// {@macro query} + const Query(this._crudRepository); + final CrudRepository _crudRepository; @override FutureOr onStart(List? params) { if (params == null) { - throw ClientException('List of conditions cannot be null.'); + throw const ClientException('List of conditions cannot be null.'); } } diff --git a/packages/wyatt_crud_bloc/lib/src/domain/usecases/stream_query.dart b/packages/wyatt_crud_bloc/lib/src/domain/usecases/stream_query.dart deleted file mode 100644 index 35e42b9e..00000000 --- a/packages/wyatt_crud_bloc/lib/src/domain/usecases/stream_query.dart +++ /dev/null @@ -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 . - -// class Stream extends UseCase> { -// final CrudRepository _crudRepository; - -// Stream(this._crudRepository); - -// @override -// StreamResult> call(StreamParameters params) => -// _crudRepository.stream(id: params.id, conditions: params.conditions); -// } - -// class StreamQuery -// extends StreamUseCase> { -// final CrudRepository _crudRepository; - -// StreamQuery(this._crudRepository); - -// @override -// FutureOr onStart(StreamParameters? params) { -// if(params == null){ -// throw ClientException('Stream parameters cannot be null.'); -// } -// } - -// @override -// FutureOrResult>> call(StreamParameters? params) => -// _crudRepository.stream(); -// } diff --git a/packages/wyatt_crud_bloc/lib/src/domain/usecases/update.dart b/packages/wyatt_crud_bloc/lib/src/domain/usecases/update.dart index 70c82b73..237e3e9d 100644 --- a/packages/wyatt_crud_bloc/lib/src/domain/usecases/update.dart +++ b/packages/wyatt_crud_bloc/lib/src/domain/usecases/update.dart @@ -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 - extends AsyncUseCase, void> { - Update(this._crudRepository); + extends AsyncUseCase, void> + with UpdateOperation> { + /// {@macro update} + const Update(this._crudRepository); + final CrudRepository _crudRepository; @override FutureOr onStart(UpdateParameters? params) { if (params == null) { - throw ClientException('Update parameters cannot be null.'); + throw const ClientException('Update parameters cannot be null.'); } } diff --git a/packages/wyatt_crud_bloc/lib/src/domain/usecases/update_all.dart b/packages/wyatt_crud_bloc/lib/src/domain/usecases/update_all.dart index 60a81957..22c32181 100644 --- a/packages/wyatt_crud_bloc/lib/src/domain/usecases/update_all.dart +++ b/packages/wyatt_crud_bloc/lib/src/domain/usecases/update_all.dart @@ -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 - extends AsyncUseCase, void> { - UpdateAll(this._crudRepository); + extends AsyncUseCase, void> + with UpdateOperation> { + /// {@macro update_all} + const UpdateAll(this._crudRepository); + final CrudRepository _crudRepository; @override FutureOr onStart(Map? params) { if (params == null) { - throw ClientException('Data cannot be null.'); + throw const ClientException('Data cannot be null.'); } } diff --git a/packages/wyatt_crud_bloc/lib/src/domain/usecases/usecases.dart b/packages/wyatt_crud_bloc/lib/src/domain/usecases/usecases.dart index b53d1702..4a8d8467 100644 --- a/packages/wyatt_crud_bloc/lib/src/domain/usecases/usecases.dart +++ b/packages/wyatt_crud_bloc/lib/src/domain/usecases/usecases.dart @@ -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'; diff --git a/packages/wyatt_crud_bloc/lib/src/features/crud/blocs/crud_advanced_cubit.dart b/packages/wyatt_crud_bloc/lib/src/features/crud/blocs/crud_advanced_cubit.dart new file mode 100644 index 00000000..d41e35d9 --- /dev/null +++ b/packages/wyatt_crud_bloc/lib/src/features/crud/blocs/crud_advanced_cubit.dart @@ -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 . + +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 + extends CrudBaseCubit { + /// {@macro crud_cubit} + CrudAdvancedCubit() : super(); + + Create? get crudCreate; + DeleteAll? get crudDeleteAll; + Delete? get crudDelete; + GetAll? get crudGetAll; + Get? get crudGet; + Query? get crudQuery; + UpdateAll? get crudUpdateAll; + Update? get crudUpdate; + + FutureOr 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) { + if (stateCopy.data == null) { + return CrudLoaded(model); + } + if (stateCopy.data!.id == model.id) { + return CrudLoaded(model); + } + + return stateCopy; + } + if (stateCopy is CrudListLoaded) { + if (stateCopy.data.isEmpty) { + return CrudListLoaded([model]); + } + final List lst = stateCopy.data.toList()..add(model); + + return CrudListLoaded(lst); + } + + return const CrudOkReturn(); + }, + (error) => CrudError(error.toString()), + ), + ); + } + + FutureOr 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) { + if (stateCopy.data?.id == id) { + return CrudLoaded(null); + } + + return stateCopy; + } + if (stateCopy is CrudListLoaded) { + return CrudListLoaded( + stateCopy.data.where((element) => element?.id != id).toList(), + ); + } + + return const CrudOkReturn(); + }, + (error) => CrudError(error.toString()), + ), + ); + } + + FutureOr 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) { + return CrudLoaded(null); + } + if (stateCopy is CrudListLoaded) { + return CrudListLoaded(const []); + } + + return const CrudOkReturn(); + }, + (error) => CrudError(error.toString()), + ), + ); + } + + FutureOr get(String id) async { + final crud = crudGet; + if (crud == null) { + return; + } + emit(const CrudLoading()); + final result = await crud.call(id); + emit( + result.fold( + CrudLoaded.new, + (error) => CrudError(error.toString()), + ), + ); + } + + FutureOr getAll() async { + final crud = crudGetAll; + if (crud == null) { + return; + } + emit(const CrudLoading()); + final result = await crud.call(null); + emit( + result.fold( + CrudListLoaded.new, + (error) => CrudError(error.toString()), + ), + ); + } + + FutureOr query(List conditions) async { + final crud = crudQuery; + if (crud == null) { + return; + } + + emit(const CrudLoading()); + final result = await crud.call(conditions); + emit( + result.fold( + CrudListLoaded.new, + (error) => CrudError(error.toString()), + ), + ); + } + + FutureOr update(UpdateParameters 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) { + 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(newVersion.ok); + } + } + return stateCopy; + } + if (stateCopy is CrudListLoaded) { + 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(newList + [newVersion.ok]); + } + } + return stateCopy; + } + return const CrudOkReturn(); + }, + (error) async => CrudError(error.toString()), + ), + ); + } + + FutureOr updateAll(Map 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) { + // 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(newVersion.ok); + } + return stateCopy; + } + if (stateCopy is CrudListLoaded) { + 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 ids = stateCopy.data + .map( + (e) => e?.id, + ) + .toList(); + final result = await crudQuery.call([ + WhereQuery( + WhereQueryType.whereIn, + 'id', + ids, + ) + ]); + if (result.isOk) { + return CrudListLoaded(result.ok ?? []); + } + return stateCopy; + } + return const CrudOkReturn(); + }, + (error) async => CrudError(error.toString()), + ), + ); + } +} diff --git a/packages/wyatt_crud_bloc/lib/src/core/extensions/num_extension.dart b/packages/wyatt_crud_bloc/lib/src/features/crud/blocs/crud_base_cubit/crud_base_cubit.dart similarity index 51% rename from packages/wyatt_crud_bloc/lib/src/core/extensions/num_extension.dart rename to packages/wyatt_crud_bloc/lib/src/features/crud/blocs/crud_base_cubit/crud_base_cubit.dart index abe45b91..c694b880 100644 --- a/packages/wyatt_crud_bloc/lib/src/core/extensions/num_extension.dart +++ b/packages/wyatt_crud_bloc/lib/src/features/crud/blocs/crud_base_cubit/crud_base_cubit.dart @@ -14,38 +14,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -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 { + /// {@macro crud_base_cubit} + CrudBaseCubit() : super(const CrudInitial()); } diff --git a/packages/wyatt_crud_bloc/lib/src/features/crud/cubit/crud_state.dart b/packages/wyatt_crud_bloc/lib/src/features/crud/blocs/crud_base_cubit/crud_state.dart similarity index 58% rename from packages/wyatt_crud_bloc/lib/src/features/crud/cubit/crud_state.dart rename to packages/wyatt_crud_bloc/lib/src/features/crud/blocs/crud_base_cubit/crud_state.dart index f8ab26e2..64f2479a 100644 --- a/packages/wyatt_crud_bloc/lib/src/features/crud/cubit/crud_state.dart +++ b/packages/wyatt_crud_bloc/lib/src/features/crud/blocs/crud_base_cubit/crud_state.dart @@ -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 . -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 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 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 extends CrudSuccess { const CrudLoaded(this.data); final T? data; @@ -51,6 +68,9 @@ class CrudLoaded extends CrudSuccess { List 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 extends CrudSuccess { const CrudListLoaded(this.data); final List data; diff --git a/packages/wyatt_crud_bloc/lib/src/features/crud/blocs/crud_cubit.dart b/packages/wyatt_crud_bloc/lib/src/features/crud/blocs/crud_cubit.dart new file mode 100644 index 00000000..5d18975f --- /dev/null +++ b/packages/wyatt_crud_bloc/lib/src/features/crud/blocs/crud_cubit.dart @@ -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 . + +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 extends CrudBaseCubit { + /// {@macro crud_cubit} + CrudCubit() : super(); + + /// Create operation. + /// Can be create. + CreateOperation? get createOperation; + + /// Read operation. + /// Can be get, getAll, query. + ReadOperation? get readOperation; + + /// Update operation. + /// Can be update, updateAll. + UpdateOperation? get updateOperation; + + /// Delete operation. + /// Can be delete or deleteAll. + DeleteOperation? get deleteOperation; + + Expected? _checkOperation(dynamic operation) { + if (operation == null) { + return null; + } + if (operation is! Expected) { + return null; + } + return operation; + } + + FutureOr create(Model model) async { + if (_checkOperation>(createOperation) != null) { + return _create(model); + } + } + + FutureOr read({String? id, List? conditions}) async { + if (_checkOperation>(readOperation) != null && id != null) { + return _get(id); + } + if (_checkOperation>(readOperation) != null) { + return _getAll(); + } + if (_checkOperation>(readOperation) != null && + conditions != null) { + return _query(conditions); + } + if (_checkOperation>(readOperation) != null && + conditions == null) { + return _getAll(); + } + } + + FutureOr update( + UpdateParameters? single, + Map? all, + ) async { + if (_checkOperation>(updateOperation) != null && + single != null) { + return _update(single); + } + + if (_checkOperation>(updateOperation) != null && + all != null) { + return _updateAll(all); + } + } + + FutureOr delete({String? id}) async { + if (_checkOperation>(deleteOperation) != null && id != null) { + return _delete(id); + } + if (_checkOperation>(deleteOperation) != null) { + return _deleteAll(); + } + } + + FutureOr _create(Model model) async { + final crud = _checkOperation>(createOperation); + if (crud == null) { + return; + } + + final stateCopy = state; + emit(const CrudLoading()); + final result = await crud.call(model); + emit( + result.fold( + (_) { + if (stateCopy is CrudLoaded) { + if (stateCopy.data == null) { + return CrudLoaded(model); + } + if (stateCopy.data!.id == model.id) { + return CrudLoaded(model); + } + + return stateCopy; + } + if (stateCopy is CrudListLoaded) { + if (stateCopy.data.isEmpty) { + return CrudListLoaded([model]); + } + final List lst = stateCopy.data.toList()..add(model); + + return CrudListLoaded(lst); + } + + return const CrudOkReturn(); + }, + (error) => CrudError(error.toString()), + ), + ); + } + + FutureOr _delete(String id) async { + final crud = _checkOperation>(deleteOperation); + if (crud == null) { + return; + } + + final stateCopy = state; + emit(const CrudLoading()); + final result = await crud.call(id); + emit( + result.fold( + (_) { + if (stateCopy is CrudLoaded) { + if (stateCopy.data?.id == id) { + return CrudLoaded(null); + } + + return stateCopy; + } + if (stateCopy is CrudListLoaded) { + return CrudListLoaded( + stateCopy.data.where((element) => element?.id != id).toList(), + ); + } + + return const CrudOkReturn(); + }, + (error) => CrudError(error.toString()), + ), + ); + } + + FutureOr _deleteAll() async { + final crud = _checkOperation>(deleteOperation); + if (crud == null) { + return; + } + final stateCopy = state; + emit(const CrudLoading()); + final result = await crud.call(null); + emit( + result.fold( + (_) { + if (stateCopy is CrudLoaded) { + return CrudLoaded(null); + } + if (stateCopy is CrudListLoaded) { + return CrudListLoaded(const []); + } + + return const CrudOkReturn(); + }, + (error) => CrudError(error.toString()), + ), + ); + } + + FutureOr _get(String id) async { + final crud = _checkOperation>(readOperation); + if (crud == null) { + return; + } + emit(const CrudLoading()); + final result = await crud.call(id); + emit( + result.fold( + CrudLoaded.new, + (error) => CrudError(error.toString()), + ), + ); + } + + FutureOr _getAll() async { + final crud = _checkOperation>(readOperation); + if (crud == null) { + return; + } + emit(const CrudLoading()); + final result = await crud.call(null); + emit( + result.fold( + CrudListLoaded.new, + (error) => CrudError(error.toString()), + ), + ); + } + + FutureOr _query(List conditions) async { + final crud = _checkOperation>(readOperation); + if (crud == null) { + return; + } + + emit(const CrudLoading()); + final result = await crud.call(conditions); + emit( + result.fold( + CrudListLoaded.new, + (error) => CrudError(error.toString()), + ), + ); + } + + FutureOr _update(UpdateParameters param) async { + final crud = _checkOperation>(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) { + if (stateCopy.data?.id == param.id) { + // Same object, need to update actual stateCopy + final crudGet = _checkOperation>(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(newVersion.ok); + } + } + return stateCopy; + } + if (stateCopy is CrudListLoaded) { + final bool listContains = + stateCopy.data.any((element) => element?.id == param.id); + if (listContains) { + // Loaded objects contains the modified object. + + final crudGet = _checkOperation>(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(newList + [newVersion.ok]); + } + } + return stateCopy; + } + return const CrudOkReturn(); + }, + (error) async => CrudError(error.toString()), + ), + ); + } + + FutureOr _updateAll(Map param) async { + final crud = _checkOperation>(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) { + // Same object, need to update actual stateCopy + final crudGet = _checkOperation>(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(newVersion.ok); + } + return stateCopy; + } + if (stateCopy is CrudListLoaded) { + final crudQuery = _checkOperation>(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 ids = stateCopy.data + .map( + (e) => e?.id, + ) + .toList(); + final result = await crudQuery.call([ + WhereQuery( + WhereQueryType.whereIn, + 'id', + ids, + ) + ]); + if (result.isOk) { + return CrudListLoaded(result.ok ?? []); + } + return stateCopy; + } + return const CrudOkReturn(); + }, + (error) async => CrudError(error.toString()), + ), + ); + } +} diff --git a/packages/wyatt_crud_bloc/lib/src/features/crud/builder/crud_builder.dart b/packages/wyatt_crud_bloc/lib/src/features/crud/builder/crud_builder.dart index 62f1faaa..d864ce7a 100644 --- a/packages/wyatt_crud_bloc/lib/src/features/crud/builder/crud_builder.dart +++ b/packages/wyatt_crud_bloc/lib/src/features/crud/builder/crud_builder.dart @@ -15,15 +15,19 @@ // along with this program. If not, see . 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 extends StatelessWidget { - /// `` - /// - /// - 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 extends StatelessWidget { super.key, }); - /// `` + /// {@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 typed({ required CrudState state, diff --git a/packages/wyatt_crud_bloc/lib/src/features/crud/crud.dart b/packages/wyatt_crud_bloc/lib/src/features/crud/crud.dart index cb18407c..16113197 100644 --- a/packages/wyatt_crud_bloc/lib/src/features/crud/crud.dart +++ b/packages/wyatt_crud_bloc/lib/src/features/crud/crud.dart @@ -14,5 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +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'; diff --git a/packages/wyatt_crud_bloc/lib/src/features/crud/cubit/crud_cubit.dart b/packages/wyatt_crud_bloc/lib/src/features/crud/cubit/crud_cubit.dart deleted file mode 100644 index dcb466aa..00000000 --- a/packages/wyatt_crud_bloc/lib/src/features/crud/cubit/crud_cubit.dart +++ /dev/null @@ -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 . - -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 extends Cubit { - CrudCubit() : super(CrudInitial()); - Create? get crudCreate; - DeleteAll? get crudDeleteAll; - Delete? get crudDelete; - GetAll? get crudGetAll; - Get? get crudGet; - Query? get crudQuery; - UpdateAll? get crudUpdateAll; - Update? get crudUpdate; - - FutureOr 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) { - return stateCopy; - } - if (stateCopy is CrudListLoaded) { - if (stateCopy.data.isEmpty) { - return CrudListLoaded([model]); - } - final List lst = stateCopy.data.toList()..add(model); - return CrudListLoaded(lst); - } - return const CrudOkReturn(); - }, - (error) => CrudError(error.toString()), - ), - ); - } - } - - FutureOr 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) { - return stateCopy; - } - if (stateCopy is CrudListLoaded) { - return CrudListLoaded( - stateCopy.data.where((element) => element?.id != id).toList(), - ); - } - return const CrudOkReturn(); - }, - (error) => CrudError(error.toString()), - ), - ); - } - } - - FutureOr deleteAll() async { - if (crudDeleteAll != null) { - final stateCopy = state; - emit(CrudLoading()); - final result = await crudDeleteAll!.call(null); - emit( - result.fold( - (_) { - if (stateCopy is CrudLoaded) { - return CrudLoaded(null); - } - if (stateCopy is CrudListLoaded) { - return CrudListLoaded(const []); - } - return const CrudOkReturn(); - }, - (error) => CrudError(error.toString()), - ), - ); - } - } - - FutureOr get(String id) async { - if (crudGet != null) { - emit(CrudLoading()); - final result = await crudGet!.call(id); - emit( - result.fold( - CrudLoaded.new, - (error) => CrudError(error.toString()), - ), - ); - } - } - - FutureOr getAll() async { - if (crudGetAll != null) { - emit(CrudLoading()); - final result = await crudGetAll!.call(null); - emit( - result.fold( - CrudListLoaded.new, - (error) => CrudError(error.toString()), - ), - ); - } - } - - FutureOr query(List conditions) async { - if (crudQuery != null) { - emit(CrudLoading()); - final result = await crudQuery!.call(conditions); - emit( - result.fold( - CrudListLoaded.new, - (error) => CrudError(error.toString()), - ), - ); - } - } - - FutureOr update(UpdateParameters 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) { - 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(newVersion.ok); - } - } - return stateCopy; - } - if (stateCopy is CrudListLoaded) { - 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(newList + [newVersion.ok]); - } - } - return stateCopy; - } - return const CrudOkReturn(); - }, - (error) async => CrudError(error.toString()), - ), - ); - } - } - - FutureOr updateAll(Map 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) { - // 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(newVersion.ok); - } - return stateCopy; - } - if (stateCopy is CrudListLoaded) { - 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 ids = stateCopy.data - .map( - (e) => e?.id, - ) - .toList(); - final result = await crudQuery!.call([ - WhereQuery( - WhereQueryType.whereIn, - 'id', - ids, - ) - ]); - if (result.isOk) { - return CrudListLoaded(result.ok ?? []); - } - return stateCopy; - } - return const CrudOkReturn(); - }, - (error) async => CrudError(error.toString()), - ), - ); - } - } -} diff --git a/packages/wyatt_crud_bloc/pubspec.yaml b/packages/wyatt_crud_bloc/pubspec.yaml index 058de9d4..52960c0e 100644 --- a/packages/wyatt_crud_bloc/pubspec.yaml +++ b/packages/wyatt_crud_bloc/pubspec.yaml @@ -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/