feat(crud)!: move crud firestore implementation into his own package

This commit is contained in:
2023-11-13 18:03:10 +01:00
parent af841baefb
commit 74593fa9fc
107 changed files with 1392 additions and 2410 deletions
@@ -14,5 +14,8 @@
// 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 'enums/operation_type.dart';
export 'enums/where_query_type.dart';
export 'mixins/operation.dart';
export 'model_identifier.dart';
export 'model_mapper.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,4 +14,10 @@
// 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 'crud_repository.dart';
enum OperationType {
query,
create,
read,
update,
delete,
}
@@ -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
@@ -15,20 +15,28 @@
// 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';
import 'package:wyatt_crud_bloc/src/core/enums/operation_type.dart';
/// Defines every write operation in CRUD.
mixin CreateOperation<Model extends ObjectModel, Out>
on AsyncUseCase<Model, Out> {}
mixin CreateOperation<In, Out> on AsyncUseCase<In, Out> {
/// {@macro create}
OperationType get operationType => OperationType.create;
}
/// Defines every read operation in CRUD.
mixin ReadOperation<Model extends ObjectModel, In, Out>
on AsyncUseCase<In, Out> {}
mixin ReadOperation<In, Out> on AsyncUseCase<In, Out> {
/// {@macro read}
OperationType get operationType => OperationType.read;
}
/// Defines every update operation in CRUD.
mixin UpdateOperation<Model extends ObjectModel, In>
on AsyncUseCase<In, void> {}
mixin UpdateOperation<In> on AsyncUseCase<In, void> {
/// {@macro update}
OperationType get operationType => OperationType.update;
}
/// Defines every delete operation in CRUD.
mixin DeleteOperation<Model extends ObjectModel, In>
on AsyncUseCase<In, void> {}
mixin DeleteOperation<In> on AsyncUseCase<In, void> {
/// {@macro delete}
OperationType get operationType => OperationType.delete;
}
@@ -0,0 +1,57 @@
// 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/>.
class ModelIdentifier<Model> {
const ModelIdentifier({
required this.getIdentifier,
});
final String Function(Model model) getIdentifier;
String? getIdentifierOrNull(Model? model) {
if (model == null) {
return null;
}
return getIdentifier(model);
}
bool isIdentifier({
required Model? model,
required String identifier,
}) {
if (model == null) {
return false;
}
return getIdentifier(model) == identifier;
}
bool areEqual(
Model? model1,
Model? model2,
) {
if (model1 == null && model2 == null) {
return true;
}
if (model1 == null || model2 == null) {
return false;
}
return getIdentifier(model1) == getIdentifier(model2);
}
}
@@ -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,5 +14,12 @@
// 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 'local/crud_in_memory_data_source_impl.dart';
export 'remote/crud_firestore_data_source_impl.dart';
class ModelMapper<Model> {
const ModelMapper({
required this.fromJson,
required this.toJson,
});
final Model Function(Map<String, dynamic>? json) fromJson;
final Map<String, dynamic> Function(Model model) toJson;
}
@@ -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,5 +14,6 @@
// 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 'data_sources/data_sources.dart';
export 'repositories/repositories.dart';
export 'data_sources/crud_data_source.dart';
export 'data_sources/crud_data_source_in_memory_impl.dart';
export 'repositories/crud_repository_impl.dart';
@@ -15,47 +15,54 @@
// 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/enums/operation_type.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 {
abstract class CrudDataSource 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,
const CrudDataSource({
required this.id,
});
/// Updates all [Model] objects.
final String? Function(
OperationType operationType,
Map<String, dynamic> object,
) id;
/// Creates a new object.
Future<void> create(Map<String, dynamic> object, {String? id});
/// Gets a object by its [id].
Future<Map<String, dynamic>?> get(String id);
/// Gets all objects.
Future<List<dynamic>?> getAll();
/// Updates a object by its [id].
Future<void> update(
String id, {
Map<String, dynamic>? object,
});
/// Updates all objects.
Future<void> updateAll(Map<String, Object?>? data);
/// Deletes a [Model] object by its [id].
/// Deletes a object by its [id].
Future<void> delete(String id);
/// Deletes all [Model] objects.
/// Deletes all objects.
Future<void> deleteAll();
/// Queries [Model] objects by [conditions].
Future<List<Model?>> query(List<QueryInterface> conditions);
/// Queries objects by [conditions].
Future<List<dynamic>?> search(List<Query> conditions);
/// Streams [Model] objects by [conditions].
Stream<List<Model?>> stream({
/// Streams objects by [conditions].
Stream<List<dynamic>?> stream({
String? id,
List<QueryInterface>? conditions,
List<Query>? conditions,
bool includeMetadataChanges = false,
});
}
@@ -16,28 +16,55 @@
import 'dart:async';
import 'package:wyatt_crud_bloc/src/core/enums/operation_type.dart';
import 'package:wyatt_crud_bloc/src/core/enums/where_query_type.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/data/data_sources/crud_data_source.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}
extension _ModelExtension on Map<String, dynamic> {
Map<String, dynamic> merge(
Map<String, dynamic>? other, {
bool skipNulls = false,
}) {
if (other == null) {
return this;
}
final Map<String, dynamic> result = {};
for (final key in keys) {
result[key] = other[key] ?? this[key];
}
for (final key in other.keys) {
if (!containsKey(key)) {
result[key] = other[key];
}
}
if (skipNulls) {
result.removeWhere((key, value) => other[key] == null);
}
return result;
}
}
/// {@template crud_data_source_in_memory_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;
final StreamController<List<Model?>> _streamData = StreamController();
class CrudDataSourceInMemoryImpl extends CrudDataSource {
/// {@macro crud_data_source_in_memory_impl}
CrudDataSourceInMemoryImpl({
required super.id,
Map<String, Map<String, dynamic>>? data,
}) : _data = data ?? {};
final Map<String, Object?> Function(Model) toMap;
final Map<String, Map<String, dynamic>> _data;
final StreamController<List<Map<String, dynamic>?>> _streamData =
StreamController();
@override
Future<void> create(Model object, {String? id}) async {
_data[id ?? object.id ?? ''] = object;
Future<void> create(Map<String, dynamic> object, {String? id}) async {
_data[id ?? this.id(OperationType.create, object) ?? ''] = object;
_streamData.add(_data.values.toList());
}
@@ -54,14 +81,14 @@ class CrudInMemoryDataSourceImpl<Model extends ObjectModel>
}
@override
Future<Model?> get(String id) async => _data[id];
Future<Map<String, dynamic>?> get(String id) async => _data[id];
@override
Future<List<Model?>> getAll() async => _data.values.toList();
Future<List<Map<String, dynamic>?>> getAll() async => _data.values.toList();
@override
Future<List<Model?>> query(List<QueryInterface> conditions) async {
List<Model> result = _data.values.toList();
Future<List<Map<String, dynamic>?>> search(List<Query> conditions) async {
List<Map<String, dynamic>> result = _data.values.toList();
for (final c in conditions) {
if (c is WhereQuery) {
@@ -81,15 +108,15 @@ class CrudInMemoryDataSourceImpl<Model extends ObjectModel>
}
}
result.cast<Model>();
result.cast<Map<String, dynamic>>();
return result;
}
@override
Stream<List<Model?>> stream({
Stream<List<Map<String, dynamic>?>> stream({
String? id,
List<QueryInterface>? conditions,
List<Query>? conditions,
bool includeMetadataChanges = false,
}) =>
_streamData.stream.map((result) {
@@ -97,7 +124,7 @@ class CrudInMemoryDataSourceImpl<Model extends ObjectModel>
return result;
}
List<Model?> res = result;
List<Map<String, dynamic>?> res = result;
for (final c in conditions) {
if (c is WhereQuery) {
@@ -120,56 +147,63 @@ class CrudInMemoryDataSourceImpl<Model extends ObjectModel>
@override
Future<void> update(
String id, {
Model? object,
Map<String, dynamic>? raw,
Map<String, dynamic>? object,
}) {
// TODO(hpcl): implement update
throw UnimplementedError();
if (object != null && _data.containsKey(id)) {
_data[id] = _data[id]!.merge(object);
_streamData.add(_data.values.toList());
}
return Future.value();
}
@override
Future<void> updateAll(Map<String, Object?>? data) {
// TODO(hcpl): implement updateAll
throw UnimplementedError();
if (data != null) {
for (final entry in _data.entries) {
_data[entry.key] = entry.value.merge(data);
}
_streamData.add(_data.values.toList());
}
return Future.value();
}
bool _whereQuery(QueryInterface condition, Model? object) {
bool _whereQuery(Query condition, Map<String, dynamic>? object) {
if (object == null) {
return false;
}
final raw = toMap.call(object);
if (condition is WhereQuery) {
switch (condition.type) {
case WhereQueryType.isEqualTo:
return raw[condition.field] == condition.value;
return object[condition.field] == condition.value;
case WhereQueryType.isNotEqualTo:
return raw[condition.field] != condition.value;
return object[condition.field] != condition.value;
case WhereQueryType.isLessThan:
return (raw[condition.field] as num?) < (condition.value as num?);
return (object[condition.field] as num?) < (condition.value as num?);
case WhereQueryType.isLessThanOrEqualTo:
return (raw[condition.field] as num?) <= (condition.value as num?);
return (object[condition.field] as num?) <= (condition.value as num?);
case WhereQueryType.isGreaterThan:
return (raw[condition.field] as num?) > (condition.value as num?);
return (object[condition.field] as num?) > (condition.value as num?);
case WhereQueryType.isGreaterThanOrEqualTo:
return (raw[condition.field] as num?) >= (condition.value as num?);
return (object[condition.field] as num?) >= (condition.value as num?);
case WhereQueryType.arrayContains:
return (raw[condition.field] as List<Object>?)
return (object[condition.field] as List<Object>?)
?.contains(condition.value) ??
false;
case WhereQueryType.arrayContainsAny:
bool res = false;
for (final o in condition.value as List<Object>) {
res = (raw[condition.field] as List<Object>?)?.contains(o) ?? false;
res = (object[condition.field] as List<Object>?)?.contains(o) ??
false;
}
return res;
case WhereQueryType.whereIn:
return (condition.value as List<Object>)
.contains(raw[condition.field]);
.contains(object[condition.field]);
case WhereQueryType.whereNotIn:
return !(condition.value as List<Object>)
.contains(raw[condition.field]);
.contains(object[condition.field]);
case WhereQueryType.isNull:
return raw[condition.field] == null;
return object[condition.field] == null;
}
}
return true;
@@ -1,230 +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 'package:cloud_firestore/cloud_firestore.dart';
import 'package:wyatt_crud_bloc/src/core/enums/where_query_type.dart';
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,
_toFirestore = toFirestore {
_collectionReference =
_firestore.collection(collection).withConverter<Model>(
fromFirestore: fromFirestore,
toFirestore: toFirestore,
);
}
final FirebaseFirestore _firestore;
final Map<String, Object?> Function(Model, SetOptions?) _toFirestore;
late CollectionReference<Model> _collectionReference;
@override
Future<void> create(Model object, {String? id}) {
if (id != null) {
return _collectionReference.doc(id).set(object);
} else {
if (object.id != null) {
return _collectionReference.doc(object.id).set(object);
}
return _collectionReference.add(object);
}
}
@override
Future<void> delete(String id) => _collectionReference.doc(id).delete();
@override
Future<void> deleteAll() async {
final batch = _firestore.batch();
final QuerySnapshot snapshots = await _collectionReference.get();
for (final DocumentSnapshot snapshot in snapshots.docs) {
batch.delete(snapshot.reference);
}
return batch.commit();
}
@override
Future<Model?> get(String id) async {
final DocumentSnapshot<Model> snapshot =
await _collectionReference.doc(id).get();
return snapshot.data();
}
@override
Future<List<Model?>> getAll() async {
final QuerySnapshot<Model> snapshots = await _collectionReference.get();
return snapshots.docs.map((snapshot) => snapshot.data()).toList();
}
@override
Future<List<Model?>> query(List<QueryInterface> conditions) async {
Query<Model> query = _collectionReference;
for (final condition in conditions) {
query = _queryParser(condition, query);
}
final QuerySnapshot<Model> snapshots = await query.get();
return snapshots.docs.map((snapshot) => snapshot.data()).toList();
}
@override
Stream<List<Model?>> stream({
String? id,
List<QueryInterface>? conditions,
bool includeMetadataChanges = false,
}) {
if (id != null) {
return _collectionReference
.doc(id)
.snapshots(
includeMetadataChanges: includeMetadataChanges,
)
.map<List<Model?>>(
(snapshot) => [snapshot.data()],
);
} else {
if (conditions != null) {
Query<Model> query = _collectionReference;
for (final condition in conditions) {
query = _queryParser(condition, query);
}
return query
.snapshots(
includeMetadataChanges: includeMetadataChanges,
)
.map(
(querySnapshot) => querySnapshot.docs
.map((snapshot) => snapshot.data())
.toList(),
);
} else {
return _collectionReference
.snapshots(
includeMetadataChanges: includeMetadataChanges,
)
.map(
(querySnapshot) => querySnapshot.docs
.map((snapshot) => snapshot.data())
.toList(),
);
}
}
}
@override
Future<void> update(
String id, {
Model? object,
Map<String, dynamic>? raw,
}) {
if (object != null) {
return _collectionReference
.doc(id)
.update(_toFirestore.call(object, null));
} else {
if (raw != null) {
return _collectionReference.doc(id).update(raw);
} else {
throw Exception('You must provide an object or a raw map');
}
}
}
@override
Future<void> updateAll(Map<String, Object?>? data) async {
if (data == null) {
throw Exception('You must provide data to update');
}
final batch = _firestore.batch();
final QuerySnapshot<Model> snapshots = await _collectionReference.get();
for (final DocumentSnapshot snapshot in snapshots.docs) {
batch.update(snapshot.reference, data);
}
return batch.commit();
}
Query<Model> _queryParser(QueryInterface condition, Object query) {
query as Query<Model>;
if (condition is WhereQuery) {
switch (condition.type) {
case WhereQueryType.isEqualTo:
return query.where(condition.field, isEqualTo: condition.value);
case WhereQueryType.isNotEqualTo:
return query.where(condition.field, isNotEqualTo: condition.value);
case WhereQueryType.isLessThan:
return query.where(condition.field, isLessThan: condition.value);
case WhereQueryType.isLessThanOrEqualTo:
return query.where(
condition.field,
isLessThanOrEqualTo: condition.value,
);
case WhereQueryType.isGreaterThan:
return query.where(condition.field, isGreaterThan: condition.value);
case WhereQueryType.isGreaterThanOrEqualTo:
return query.where(
condition.field,
isGreaterThanOrEqualTo: condition.value,
);
case WhereQueryType.arrayContains:
return query.where(condition.field, arrayContains: condition.value);
case WhereQueryType.arrayContainsAny:
return query.where(
condition.field,
arrayContainsAny: condition.value as List<Object>,
);
case WhereQueryType.whereIn:
return query.where(
condition.field,
whereIn: condition.value as List<Object>,
);
case WhereQueryType.whereNotIn:
return query.where(
condition.field,
whereNotIn: condition.value as List<Object>,
);
case WhereQueryType.isNull:
return query.where(condition.field, isNull: condition.value as bool);
}
} else if (condition is LimitQuery) {
return query.limit(condition.limit);
} else if (condition is OrderByQuery) {
return query.orderBy(
condition.field,
descending: !condition.ascending,
);
}
return query;
}
}
@@ -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
@@ -15,8 +15,8 @@
// 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/data_sources/crud_data_source.dart';
import 'package:wyatt_crud_bloc/src/domain/entities/object_model.dart';
import 'package:wyatt_crud_bloc/src/core/model_mapper.dart';
import 'package:wyatt_crud_bloc/src/data/data_sources/crud_data_source.dart';
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';
@@ -24,19 +24,23 @@ 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> {
class CrudRepositoryImpl<Model> extends CrudRepository<Model> {
/// {@macro crud_repository_impl}
const CrudRepositoryImpl({
required CrudDataSource<Model> crudDataSource,
required this.modelMapper,
required CrudDataSource crudDataSource,
}) : _crudDataSource = crudDataSource;
final CrudDataSource<Model> _crudDataSource;
final CrudDataSource _crudDataSource;
@override
final ModelMapper<Model> modelMapper;
@override
FutureOrResult<void> create(Model object, {String? id}) =>
Result.tryCatchAsync<void, AppException, AppException>(
() async {
await _crudDataSource.create(object, id: id);
await _crudDataSource.create(modelMapper.toJson(object), id: id);
},
(error) => error,
);
@@ -44,14 +48,26 @@ class CrudRepositoryImpl<Model extends ObjectModel>
@override
FutureOrResult<Model?> get(String id) =>
Result.tryCatchAsync<Model?, AppException, AppException>(
() async => _crudDataSource.get(id),
() async => modelMapper.fromJson(await _crudDataSource.get(id)),
(error) => error,
);
@override
FutureOrResult<List<Model?>> getAll() =>
Result.tryCatchAsync<List<Model?>, AppException, AppException>(
() async => _crudDataSource.getAll(),
() async {
final lst = await _crudDataSource.getAll();
if (lst.isNotNull) {
return lst!.map((raw) {
try {
return modelMapper.fromJson(raw as Map<String, dynamic>?);
} catch (e) {
return null;
}
}).toList();
}
throw const ServerException();
},
(error) => error,
);
@@ -62,7 +78,18 @@ class CrudRepositoryImpl<Model extends ObjectModel>
Map<String, dynamic>? raw,
}) =>
Result.tryCatchAsync<void, AppException, AppException>(
() async => _crudDataSource.update(id, object: object, raw: raw),
() async {
if (object != null) {
await _crudDataSource.update(
id,
object: modelMapper.toJson(object),
);
} else if (raw != null) {
await _crudDataSource.update(id, object: raw);
} else {
throw const ServerException();
}
},
(error) => error,
);
@@ -88,20 +115,40 @@ class CrudRepositoryImpl<Model extends ObjectModel>
);
@override
FutureOrResult<List<Model?>> query(List<QueryInterface> conditions) =>
FutureOrResult<List<Model?>> search(List<Query> conditions) =>
Result.tryCatchAsync<List<Model?>, AppException, AppException>(
() async => _crudDataSource.query(conditions),
() async {
final lst = await _crudDataSource.search(conditions);
if (lst.isNotNull) {
return lst!.map((raw) {
try {
return modelMapper.fromJson(raw as Map<String, dynamic>?);
} catch (e) {
return null;
}
}).toList();
}
throw const ServerException();
},
(error) => error,
);
@override
StreamResult<List<Model?>> stream({
String? id,
List<QueryInterface>? conditions,
List<Query>? conditions,
}) =>
_crudDataSource.stream(id: id, conditions: conditions).map((lst) {
if (lst.isNotNull) {
return Ok<List<Model?>, AppException>(lst);
return Ok<List<Model?>, AppException>(
lst!.map((raw) {
try {
return modelMapper.fromJson(raw as Map<String, dynamic>?);
} catch (e) {
return null;
}
}).toList(),
);
}
return Err<List<Model?>, AppException>(const ServerException());
});
@@ -1,17 +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/>.
export 'crud_repository_impl.dart';
@@ -1,17 +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/>.
export 'crud_data_source.dart';
@@ -14,7 +14,6 @@
// 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 'data_sources/data_sources.dart';
export 'entities/entities.dart';
export 'repositories/repositories.dart';
export 'entities/query.dart';
export 'repositories/crud_repository.dart';
export 'usecases/usecases.dart';
@@ -1,18 +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/>.
export 'object_model.dart';
export 'query.dart';
@@ -1,28 +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 '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;
}
@@ -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
@@ -17,25 +17,20 @@
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);
// }
typedef QueryParser<Q> = Q Function(QueryInterface condition, Q query);
typedef QueryParser<Q> = Q Function(Query condition, Q query);
/// {@template query}
/// An abstract class that represents a query.
/// {@endtemplate}
abstract class QueryInterface extends Entity {
base class Query extends Entity {
/// {@macro query}
const QueryInterface();
const Query();
}
/// {@template where_query}
/// Represents a where query.
/// {@endtemplate}
class WhereQuery<Value> extends QueryInterface {
final class WhereQuery<Value> extends Query {
/// {@macro where_query}
const WhereQuery(this.type, this.field, this.value);
@@ -52,7 +47,7 @@ class WhereQuery<Value> extends QueryInterface {
/// {@template limit_query}
/// Represents a limit query.
/// {@endtemplate}
class LimitQuery extends QueryInterface {
final class LimitQuery extends Query {
/// {@macro limit_query}
const LimitQuery(this.limit);
@@ -63,7 +58,7 @@ class LimitQuery extends QueryInterface {
/// {@template offset_query}
/// Represents an offset query.
/// {@endtemplate}
class OrderByQuery extends QueryInterface {
final class OrderByQuery extends Query {
/// {@macro offset_query}
const OrderByQuery(this.field, {this.ascending = true});
@@ -15,17 +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/domain/entities/object_model.dart';
import 'package:wyatt_crud_bloc/src/core/model_mapper.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 {
abstract class CrudRepository<Model> extends BaseRepository {
/// {@macro crud_repository}
const CrudRepository();
ModelMapper<Model> get modelMapper;
/// Creates a new object.
FutureOrResult<void> create(Model object, {String? id});
@@ -52,11 +53,11 @@ abstract class CrudRepository<Model extends ObjectModel>
FutureOrResult<void> deleteAll();
/// Queries objects by [conditions].
FutureOrResult<List<Model?>> query(List<QueryInterface> conditions);
FutureOrResult<List<Model?>> search(List<Query> conditions);
/// Streams objects by [conditions].
StreamResult<List<Model?>> stream({
String? id,
List<QueryInterface>? conditions,
List<Query>? conditions,
});
}
@@ -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,31 +14,22 @@
// 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_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/usecases.dart';
/// {@template create}
/// A use case that creates an object model.
/// {@endtemplate}
class Create<Model extends ObjectModel> extends AsyncUseCase<Model, void>
class Create<Model> extends Usecase<Model, void>
with CreateOperation<Model, void> {
/// {@macro create}
const Create(this._crudRepository);
const Create(this.crudRepository);
final CrudRepository<Model> _crudRepository;
@override
FutureOr<void> onStart(Model? params) {
if (params == null) {
throw ClientException('$Model cannot be null.');
}
}
final CrudRepository<Model> crudRepository;
@override
FutureOrResult<void> execute(Model? params) =>
_crudRepository.create(params!);
crudRepository.create(params as Model);
}
@@ -14,31 +14,21 @@
// 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_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/usecases.dart';
/// {@template delete}
/// A use case that deletes an object model.
/// {@endtemplate}
class Delete<Model extends ObjectModel> extends AsyncUseCase<String, void>
with DeleteOperation<Model, String> {
class Delete<Model> extends Usecase<String, void> with DeleteOperation<String> {
/// {@macro delete}
const Delete(this._crudRepository);
const Delete(this.crudRepository);
final CrudRepository<Model> _crudRepository;
@override
FutureOr<void> onStart(String? params) {
if (params == null) {
throw const ClientException('Id cannot be null.');
}
}
final CrudRepository<Model> crudRepository;
@override
FutureOrResult<void> execute(String? params) =>
_crudRepository.delete(params!);
crudRepository.delete(params!);
}
@@ -16,19 +16,18 @@
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/usecases.dart';
/// {@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> {
class DeleteAll<Model> extends NoParamUsecase<void> with DeleteOperation<void> {
/// {@macro delete_all}
const DeleteAll(this._crudRepository);
const DeleteAll(this.crudRepository);
final CrudRepository<Model> _crudRepository;
final CrudRepository<Model> crudRepository;
@override
FutureOrResult<void> execute(void params) => _crudRepository.deleteAll();
FutureOrResult<void> execute(void params) => crudRepository.deleteAll();
}
@@ -14,31 +14,21 @@
// 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_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/usecases.dart';
/// {@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?> {
class Get<Model> extends Usecase<String, Model?>
with ReadOperation<String, Model?> {
/// {@macro get}
Get(this._crudRepository);
Get(this.crudRepository);
final CrudRepository<Model> _crudRepository;
final CrudRepository<Model> crudRepository;
@override
FutureOr<void> onStart(String? params) {
if (params == null) {
throw const ClientException('Id cannot be null.');
}
}
@override
FutureOrResult<Model?> execute(String? params) =>
_crudRepository.get(params!);
FutureOrResult<Model?> execute(String? params) => crudRepository.get(params!);
}
@@ -16,19 +16,19 @@
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/usecases.dart';
/// {@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?>> {
class GetAll<Model> extends NoParamUsecase<List<Model?>>
with ReadOperation<void, List<Model?>> {
/// {@macro get_all}
const GetAll(this._crudRepository);
const GetAll(this.crudRepository);
final CrudRepository<Model> _crudRepository;
final CrudRepository<Model> crudRepository;
@override
FutureOrResult<List<Model?>> execute(void params) => _crudRepository.getAll();
FutureOrResult<List<Model?>> execute(void params) => crudRepository.getAll();
}
@@ -30,5 +30,5 @@ class StreamParameters {
final String? id;
/// The conditions of the query.
final List<QueryInterface>? conditions;
final List<Query>? conditions;
}
@@ -14,33 +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/>.
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';
import 'package:wyatt_crud_bloc/src/domain/usecases/usecases.dart';
/// {@template query}
/// {@template search}
/// A use case that queries the object models.
/// {@endtemplate}
class Query<Model extends ObjectModel>
extends AsyncUseCase<List<QueryInterface>, List<Model?>>
with ReadOperation<Model, List<QueryInterface>, List<Model?>> {
/// {@macro query}
const Query(this._crudRepository);
class Search<Model> extends Usecase<List<Query>, List<Model?>>
with ReadOperation<List<Query>, List<Model?>> {
/// {@macro search}
const Search(this.crudRepository);
final CrudRepository<Model> _crudRepository;
final CrudRepository<Model> crudRepository;
@override
FutureOr<void> onStart(List<QueryInterface>? params) {
if (params == null) {
throw const ClientException('List of conditions cannot be null.');
}
}
@override
FutureOrResult<List<Model?>> execute(List<QueryInterface>? params) =>
_crudRepository.query(params!);
FutureOrResult<List<Model?>> execute(List<Query>? params) =>
crudRepository.search(params!);
}
@@ -0,0 +1,44 @@
// 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 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_crud_bloc/src/core/mixins/operation.dart';
import 'package:wyatt_crud_bloc/src/domain/repositories/crud_repository.dart';
import 'package:wyatt_crud_bloc/src/domain/usecases/usecases.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
/// {@template stream}
/// A use case that streams the object models.
/// {@endtemplate}
class Stream<Model>
extends Usecase<StreamParameters, StreamResult<List<Model?>>>
with ReadOperation<StreamParameters, StreamResult<List<Model?>>> {
/// {@macro stream}
const Stream(this.crudRepository);
final CrudRepository<Model> crudRepository;
@override
FutureOrResult<StreamResult<List<Model?>>> execute(
StreamParameters? params,
) async =>
Ok(
crudRepository.stream(
id: params!.id,
conditions: params.conditions,
),
);
}
@@ -14,35 +14,24 @@
// 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_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';
import 'package:wyatt_crud_bloc/src/domain/usecases/usecases.dart';
/// {@template update}
/// A use case that updates an object model.
/// {@endtemplate}
class Update<Model extends ObjectModel>
extends AsyncUseCase<UpdateParameters<Model>, void>
with UpdateOperation<Model, UpdateParameters<Model>> {
class Update<Model> extends Usecase<UpdateParameters<Model>, void>
with UpdateOperation<UpdateParameters<Model>> {
/// {@macro update}
const Update(this._crudRepository);
const Update(this.crudRepository);
final CrudRepository<Model> _crudRepository;
@override
FutureOr<void> onStart(UpdateParameters<Model>? params) {
if (params == null) {
throw const ClientException('Update parameters cannot be null.');
}
}
final CrudRepository<Model> crudRepository;
@override
FutureOrResult<void> execute(UpdateParameters<Model>? params) =>
_crudRepository.update(
crudRepository.update(
params!.id,
object: params.object,
raw: params.raw,
@@ -14,32 +14,22 @@
// 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_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/usecases.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>
with UpdateOperation<Model, Map<String, Object?>> {
class UpdateAll<Model> extends Usecase<Map<String, Object?>, void>
with UpdateOperation<Map<String, Object?>> {
/// {@macro update_all}
const UpdateAll(this._crudRepository);
const UpdateAll(this.crudRepository);
final CrudRepository<Model> _crudRepository;
@override
FutureOr<void> onStart(Map<String, Object?>? params) {
if (params == null) {
throw const ClientException('Data cannot be null.');
}
}
final CrudRepository<Model> crudRepository;
@override
FutureOrResult<void> execute(Map<String, Object?>? params) =>
_crudRepository.updateAll(params!);
crudRepository.updateAll(params!);
}
@@ -14,12 +14,36 @@
// 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_architecture/wyatt_architecture.dart';
import 'package:wyatt_crud_bloc/src/core/enums/operation_type.dart';
export 'create.dart';
export 'delete.dart';
export 'delete_all.dart';
export 'get.dart';
export 'get_all.dart';
export 'params/params.dart';
export 'query.dart';
export 'search.dart';
export 'update.dart';
export 'update_all.dart';
abstract class Usecase<In, Out> extends AsyncUseCase<In, Out> {
const Usecase();
@override
FutureOr<void> onStart(In? params) {
if (params == null) {
throw ClientException('$In cannot be null.');
}
}
OperationType get operationType;
}
abstract class NoParamUsecase<Out> extends AsyncUseCase<void, Out> {
const NoParamUsecase();
OperationType get operationType;
}
@@ -17,7 +17,7 @@
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/core/model_identifier.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';
@@ -25,7 +25,7 @@ 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/search.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';
@@ -33,8 +33,7 @@ import 'package:wyatt_crud_bloc/src/features/crud/blocs/crud_base_cubit/crud_bas
/// {@template crud_cubit_advanced}
/// Cubit that handles CRUD operations with more granularity.
/// {@endtemplate}
abstract class CrudAdvancedCubit<Model extends ObjectModel>
extends CrudBaseCubit {
abstract class CrudAdvancedCubit<Model> extends CrudBaseCubit {
/// {@macro crud_cubit}
CrudAdvancedCubit() : super();
@@ -43,10 +42,14 @@ abstract class CrudAdvancedCubit<Model extends ObjectModel>
Delete<Model>? get crudDelete;
GetAll<Model>? get crudGetAll;
Get<Model>? get crudGet;
Query<Model>? get crudQuery;
Search<Model>? get crudSearch;
UpdateAll<Model>? get crudUpdateAll;
Update<Model>? get crudUpdate;
/// Model identifier.
/// Used to identify a model.
ModelIdentifier<Model> get modelIdentifier;
FutureOr<void> create(Model model) async {
final crud = crudCreate;
if (crud == null) {
@@ -54,7 +57,7 @@ abstract class CrudAdvancedCubit<Model extends ObjectModel>
}
final stateCopy = state;
emit(const CrudLoading());
emit(const CrudCreating());
final result = await crud.call(model);
emit(
result.fold(
@@ -63,7 +66,7 @@ abstract class CrudAdvancedCubit<Model extends ObjectModel>
if (stateCopy.data == null) {
return CrudLoaded<Model?>(model);
}
if (stateCopy.data!.id == model.id) {
if (modelIdentifier.areEqual(stateCopy.data, model)) {
return CrudLoaded<Model?>(model);
}
@@ -78,7 +81,7 @@ abstract class CrudAdvancedCubit<Model extends ObjectModel>
return CrudListLoaded<Model?>(lst);
}
return const CrudOkReturn();
return const CrudCreated();
},
(error) => CrudError(error.toString()),
),
@@ -92,13 +95,16 @@ abstract class CrudAdvancedCubit<Model extends ObjectModel>
}
final stateCopy = state;
emit(const CrudLoading());
emit(const CrudDeleting());
final result = await crud.call(id);
emit(
result.fold(
(_) {
if (stateCopy is CrudLoaded<Model?>) {
if (stateCopy.data?.id == id) {
if (modelIdentifier.isIdentifier(
model: stateCopy.data,
identifier: id,
)) {
return CrudLoaded<Model?>(null);
}
@@ -106,11 +112,19 @@ abstract class CrudAdvancedCubit<Model extends ObjectModel>
}
if (stateCopy is CrudListLoaded<Model?>) {
return CrudListLoaded<Model?>(
stateCopy.data.where((element) => element?.id != id).toList(),
stateCopy.data.where((element) {
if (element == null) {
return false;
}
return !modelIdentifier.isIdentifier(
model: element,
identifier: id,
);
}).toList(),
);
}
return const CrudOkReturn();
return const CrudDeleted();
},
(error) => CrudError(error.toString()),
),
@@ -123,7 +137,7 @@ abstract class CrudAdvancedCubit<Model extends ObjectModel>
return;
}
final stateCopy = state;
emit(const CrudLoading());
emit(const CrudDeleting());
final result = await crud.call(null);
emit(
result.fold(
@@ -147,7 +161,7 @@ abstract class CrudAdvancedCubit<Model extends ObjectModel>
if (crud == null) {
return;
}
emit(const CrudLoading());
emit(const CrudReading());
final result = await crud.call(id);
emit(
result.fold(
@@ -162,7 +176,7 @@ abstract class CrudAdvancedCubit<Model extends ObjectModel>
if (crud == null) {
return;
}
emit(const CrudLoading());
emit(const CrudReading());
final result = await crud.call(null);
emit(
result.fold(
@@ -172,13 +186,13 @@ abstract class CrudAdvancedCubit<Model extends ObjectModel>
);
}
FutureOr<void> query(List<QueryInterface> conditions) async {
final crud = crudQuery;
FutureOr<void> search(List<Query> conditions) async {
final crud = crudSearch;
if (crud == null) {
return;
}
emit(const CrudLoading());
emit(const CrudReading());
final result = await crud.call(conditions);
emit(
result.fold(
@@ -195,13 +209,16 @@ abstract class CrudAdvancedCubit<Model extends ObjectModel>
}
final stateCopy = state;
emit(const CrudLoading());
emit(const CrudUpdating());
final result = await crud.call(param);
emit(
await result.foldAsync(
(_) async {
if (stateCopy is CrudLoaded<Model?>) {
if (stateCopy.data?.id == param.id) {
if (modelIdentifier.isIdentifier(
model: stateCopy.data,
identifier: param.id,
)) {
// Same object, need to update actual stateCopy
final crudGet = this.crudGet;
if (crudGet == null) {
@@ -216,8 +233,15 @@ abstract class CrudAdvancedCubit<Model extends ObjectModel>
return stateCopy;
}
if (stateCopy is CrudListLoaded<Model?>) {
final bool listContains =
stateCopy.data.any((element) => element?.id == param.id);
final bool listContains = stateCopy.data.any((element) {
if (element == null) {
return false;
}
return modelIdentifier.isIdentifier(
model: element,
identifier: param.id,
);
});
if (listContains) {
// Loaded objects contains the modified object.
@@ -228,17 +252,23 @@ abstract class CrudAdvancedCubit<Model extends ObjectModel>
}
final newVersion = await crudGet.call(param.id);
if (newVersion.isOk) {
final newList = stateCopy.data
.where(
(element) => element?.id != param.id,
)
.toList();
final newList = stateCopy.data.where(
(element) {
if (element == null) {
return false;
}
return !modelIdentifier.isIdentifier(
model: element,
identifier: param.id,
);
},
).toList();
return CrudListLoaded<Model?>(newList + [newVersion.ok]);
}
}
return stateCopy;
}
return const CrudOkReturn();
return const CrudUpdated();
},
(error) async => CrudError(error.toString()),
),
@@ -252,7 +282,7 @@ abstract class CrudAdvancedCubit<Model extends ObjectModel>
}
final stateCopy = state;
emit(const CrudLoading());
emit(const CrudUpdating());
final result = await crud.call(param);
emit(
await result.foldAsync(
@@ -264,7 +294,8 @@ abstract class CrudAdvancedCubit<Model extends ObjectModel>
// No read operation, can't update stateCopy.
return stateCopy;
}
final actualId = stateCopy.data?.id;
final actualId =
modelIdentifier.getIdentifierOrNull(stateCopy.data);
final newVersion = await crudGet.call(actualId ?? '');
if (newVersion.isOk) {
return CrudLoaded<Model?>(newVersion.ok);
@@ -272,8 +303,8 @@ abstract class CrudAdvancedCubit<Model extends ObjectModel>
return stateCopy;
}
if (stateCopy is CrudListLoaded<Model?>) {
final crudQuery = this.crudQuery;
if (crudQuery == null) {
final crudSearch = this.crudSearch;
if (crudSearch == null) {
// No read operation, can't update stateCopy.
return stateCopy;
}
@@ -281,10 +312,11 @@ abstract class CrudAdvancedCubit<Model extends ObjectModel>
// (not all because previous stateCopy can be a query result)
final List<String?> ids = stateCopy.data
.map(
(e) => e?.id,
(e) => modelIdentifier.getIdentifierOrNull(e),
)
.where((element) => element != null)
.toList();
final result = await crudQuery.call([
final result = await crudSearch.call([
WhereQuery(
WhereQueryType.whereIn,
'id',
@@ -296,7 +328,7 @@ abstract class CrudAdvancedCubit<Model extends ObjectModel>
}
return stateCopy;
}
return const CrudOkReturn();
return const CrudUpdated();
},
(error) async => CrudError(error.toString()),
),
@@ -16,7 +16,7 @@
part of 'crud_base_cubit.dart';
abstract class CrudState extends Equatable {
sealed class CrudState extends Equatable {
const CrudState();
@override
@@ -29,10 +29,26 @@ class CrudInitial extends CrudState {
}
/// Loading state of the CrudBaseCubit.
class CrudLoading extends CrudState {
abstract class CrudLoading extends CrudState {
const CrudLoading();
}
class CrudCreating extends CrudLoading {
const CrudCreating();
}
class CrudReading extends CrudLoading {
const CrudReading();
}
class CrudUpdating extends CrudLoading {
const CrudUpdating();
}
class CrudDeleting extends CrudLoading {
const CrudDeleting();
}
/// Error state of the CrudBaseCubit.
class CrudError extends CrudState {
const CrudError(this.message);
@@ -57,6 +73,18 @@ class CrudOkReturn extends CrudSuccess {
const CrudOkReturn();
}
class CrudCreated extends CrudOkReturn {
const CrudCreated();
}
class CrudUpdated extends CrudOkReturn {
const CrudUpdated();
}
class CrudDeleted extends CrudOkReturn {
const CrudDeleted();
}
/// Loaded state of the CrudBaseCubit.
/// This state is used to indicate that the operation was successful.
/// Contains one object.
@@ -18,7 +18,7 @@ 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/core/model_identifier.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';
@@ -26,33 +26,42 @@ 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/search.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';
typedef DefaultCreate<Model> = CreateOperation<Model, void>;
typedef DefaultRead = ReadOperation<dynamic, dynamic>;
typedef DefaultUpdate = UpdateOperation<dynamic>;
typedef DefaultDelete = DeleteOperation<dynamic>;
/// {@template crud_cubit}
/// Cubit that handles CRUD operations.
/// Cubit that handles SCRUD operations.
/// {@endtemplate}
abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
abstract class CrudCubit<Model> extends CrudBaseCubit {
/// {@macro crud_cubit}
CrudCubit() : super();
/// Create operation.
/// Can be create.
CreateOperation<Model, dynamic>? get createOperation;
CreateOperation<Model, void>? get createOperation;
/// Read operation.
/// Can be get, getAll, query.
ReadOperation<Model, dynamic, dynamic>? get readOperation;
/// Can be get, getAll, search or stream.
ReadOperation<dynamic, dynamic>? get readOperation;
/// Update operation.
/// Can be update, updateAll.
UpdateOperation<Model, dynamic>? get updateOperation;
UpdateOperation<dynamic>? get updateOperation;
/// Delete operation.
/// Can be delete or deleteAll.
DeleteOperation<Model, dynamic>? get deleteOperation;
DeleteOperation<dynamic>? get deleteOperation;
/// Model identifier.
/// Used to identify a model.
ModelIdentifier<Model> get modelIdentifier;
Expected? _checkOperation<Expected>(dynamic operation) {
if (operation == null) {
@@ -70,27 +79,27 @@ abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
}
}
FutureOr<void> read({String? id, List<QueryInterface>? conditions}) async {
FutureOr<void> read({String? id, List<Query>? 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 &&
if (_checkOperation<Search<Model>>(readOperation) != null &&
conditions != null) {
return _query(conditions);
return _search(conditions);
}
if (_checkOperation<Query<Model>>(readOperation) != null &&
if (_checkOperation<Stream<Model>>(readOperation) != null &&
conditions == null) {
return _getAll();
}
}
FutureOr<void> update(
FutureOr<void> update({
UpdateParameters<Model>? single,
Map<String, dynamic>? all,
) async {
}) async {
if (_checkOperation<Update<Model>>(updateOperation) != null &&
single != null) {
return _update(single);
@@ -118,7 +127,7 @@ abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
}
final stateCopy = state;
emit(const CrudLoading());
emit(const CrudCreating());
final result = await crud.call(model);
emit(
result.fold(
@@ -127,7 +136,7 @@ abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
if (stateCopy.data == null) {
return CrudLoaded<Model?>(model);
}
if (stateCopy.data!.id == model.id) {
if (stateCopy.data == model) {
return CrudLoaded<Model?>(model);
}
@@ -142,7 +151,7 @@ abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
return CrudListLoaded<Model?>(lst);
}
return const CrudOkReturn();
return const CrudCreated();
},
(error) => CrudError(error.toString()),
),
@@ -156,13 +165,16 @@ abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
}
final stateCopy = state;
emit(const CrudLoading());
emit(const CrudDeleting());
final result = await crud.call(id);
emit(
result.fold(
(_) {
if (stateCopy is CrudLoaded<Model?>) {
if (stateCopy.data?.id == id) {
if (modelIdentifier.isIdentifier(
model: stateCopy.data,
identifier: id,
)) {
return CrudLoaded<Model?>(null);
}
@@ -170,11 +182,19 @@ abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
}
if (stateCopy is CrudListLoaded<Model?>) {
return CrudListLoaded<Model?>(
stateCopy.data.where((element) => element?.id != id).toList(),
stateCopy.data.where((element) {
if (element == null) {
return false;
}
return !modelIdentifier.isIdentifier(
model: element,
identifier: id,
);
}).toList(),
);
}
return const CrudOkReturn();
return const CrudDeleted();
},
(error) => CrudError(error.toString()),
),
@@ -187,7 +207,7 @@ abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
return;
}
final stateCopy = state;
emit(const CrudLoading());
emit(const CrudDeleting());
final result = await crud.call(null);
emit(
result.fold(
@@ -199,7 +219,7 @@ abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
return CrudListLoaded<Model?>(const []);
}
return const CrudOkReturn();
return const CrudDeleted();
},
(error) => CrudError(error.toString()),
),
@@ -211,7 +231,7 @@ abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
if (crud == null) {
return;
}
emit(const CrudLoading());
emit(const CrudReading());
final result = await crud.call(id);
emit(
result.fold(
@@ -226,7 +246,7 @@ abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
if (crud == null) {
return;
}
emit(const CrudLoading());
emit(const CrudReading());
final result = await crud.call(null);
emit(
result.fold(
@@ -236,13 +256,13 @@ abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
);
}
FutureOr<void> _query(List<QueryInterface> conditions) async {
final crud = _checkOperation<Query<Model>>(readOperation);
FutureOr<void> _search(List<Query> conditions) async {
final crud = _checkOperation<Search<Model>>(readOperation);
if (crud == null) {
return;
}
emit(const CrudLoading());
emit(const CrudReading());
final result = await crud.call(conditions);
emit(
result.fold(
@@ -259,13 +279,16 @@ abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
}
final stateCopy = state;
emit(const CrudLoading());
emit(const CrudUpdating());
final result = await crud.call(param);
emit(
await result.foldAsync(
(_) async {
if (stateCopy is CrudLoaded<Model?>) {
if (stateCopy.data?.id == param.id) {
if (modelIdentifier.isIdentifier(
model: stateCopy.data,
identifier: param.id,
)) {
// Same object, need to update actual stateCopy
final crudGet = _checkOperation<Get<Model>>(readOperation);
if (crudGet == null) {
@@ -280,29 +303,48 @@ abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
return stateCopy;
}
if (stateCopy is CrudListLoaded<Model?>) {
final bool listContains =
stateCopy.data.any((element) => element?.id == param.id);
final bool listContains = stateCopy.data.any((element) {
if (element == null) {
return false;
}
return modelIdentifier.isIdentifier(
model: element,
identifier: 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;
if (crudGet != null) {
final newVersion = await crudGet.call(param.id);
if (newVersion.isOk) {
final newList = stateCopy.data.where(
(element) {
if (element == null) {
return false;
}
return !modelIdentifier.isIdentifier(
model: element,
identifier: param.id,
);
},
).toList();
return CrudListLoaded<Model?>(newList + [newVersion.ok]);
}
}
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]);
// If get operation is not available, just reload all objects.
final crudGetAll = _checkOperation<GetAll<Model>>(readOperation);
if (crudGetAll != null) {
final newVersion = await crudGetAll.call(null);
if (newVersion.isOk) {
return CrudListLoaded<Model?>(newVersion.ok ?? []);
}
}
}
return stateCopy;
}
return const CrudOkReturn();
return const CrudUpdated();
},
(error) async => CrudError(error.toString()),
),
@@ -316,7 +358,7 @@ abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
}
final stateCopy = state;
emit(const CrudLoading());
emit(const CrudUpdating());
final result = await crud.call(param);
emit(
await result.foldAsync(
@@ -328,7 +370,8 @@ abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
// No read operation, can't update stateCopy.
return stateCopy;
}
final actualId = stateCopy.data?.id;
final actualId =
modelIdentifier.getIdentifierOrNull(stateCopy.data);
final newVersion = await crudGet.call(actualId ?? '');
if (newVersion.isOk) {
return CrudLoaded<Model?>(newVersion.ok);
@@ -336,7 +379,7 @@ abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
return stateCopy;
}
if (stateCopy is CrudListLoaded<Model?>) {
final crudQuery = _checkOperation<Query<Model>>(readOperation);
final crudQuery = _checkOperation<Search<Model>>(readOperation);
if (crudQuery == null) {
// No read operation, can't update stateCopy.
return stateCopy;
@@ -345,8 +388,9 @@ abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
// (not all because previous stateCopy can be a query result)
final List<String?> ids = stateCopy.data
.map(
(e) => e?.id,
(e) => modelIdentifier.getIdentifierOrNull(e),
)
.where((element) => element != null)
.toList();
final result = await crudQuery.call([
WhereQuery(
@@ -360,7 +404,7 @@ abstract class CrudCubit<Model extends ObjectModel> extends CrudBaseCubit {
}
return stateCopy;
}
return const CrudOkReturn();
return const CrudUpdated();
},
(error) async => CrudError(error.toString()),
),
@@ -1,17 +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/>.
export 'crud_builder.dart';
@@ -17,4 +17,4 @@
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 'builder/crud_builder.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