injectCRUD<T, P> static method
- ICRUD<
T, P> repository(), { - P param()?,
- bool readOnInitialization = false,
- OnCRUDSideEffects? onCRUDSideEffects,
- SideEffects<
List< ? sideEffects,T> > - PersistState<
List< persist()?,T> > - SnapState<
List< ? stateInterceptor()?,T> > - int undoStackLength = 0,
- DependsOn<
List< ? dependsOn,T> > - bool autoDisposeWhenNotUsed = true,
- bool isLazy = true,
- String? debugPrintWhenNotifiedPreMessage,
- String toDebugString(
- List<
T> ?
- List<
Injection of a state that can create, read, update and delete from a backend or database service.
This injected state abstracts the best practices of the clean architecture to come out with a simple, clean, and testable approach to manage CRUD operations.
The approach consists of the following steps:
- Define an
Item
Model. (The name is up to you). - You may define a class (or enum) to parametrize the query.
- Your repository must implements ICRUD<T, P> where T is the Item type and P is the parameter type.
- Instantiate an InjectedCRUD object using RM.injectCRUD method.
- Later on use InjectedCRUD.crud.create, InjectedCRUD.crud.read, InjectedCRUD.crud.update, and InjectedCRUD.crud.delete item.
- In the UI you can use ReactiveStatelessWidget, OnReactive,
ObBuilder
, or OnCRUDBuilder to listen to this injected state. - You may use InjectedCRUD.item.inherited for performant list of item rendering.
- CRUD methods can be invoked optimistically or pessimistically.
Parameters:
repository
: Required callback that returns an object that implements ICRUD<T, P>
ICRUD<T, P> forces you to implement the following methods:
-
Future<void> init()
to initialize your CRUD service (if it deeds to). -
Future<List<T>> read(P? param)
to read a list of Items. Withparam
you can parametrize your query Example:@override Future<List<Item>> read(Param? param) async { final items = await http.get('uri/${param.user.id}'); //After parsing return items; //OR if(param.queryType=='GetCompletedItems'){ final items = await http.get('uri/${param.user.id}/completed'); return items; }else if(param.queryType == 'GetActiveItems'){ final items = await http.get('uri/${param.user.id}/active'); return items; } }
-
Future<T> create(T item, P? param)
to create on Item -
Future<dynamic> update(List<T> items, P? param)
to update an item -
Future<dynamic> delete(List<T> items, P? param)
to delete an item -
Future<void> dispose()
to dispose resources.
Apart from these five methods, you can define other custom methods and invoke them using InjectedCRUD.getRepoAs method.
param
: Optional callback that returns P
The default param object to be used in ICRUD.create, ICRUD.read, ICRUD.update, and ICRUD.delete methods.
readOnInitialization
: Optional bool. Defaults to false
If true, a read query with the default param
will sent to the backend
service once the state is initialized.
onCRUDSideEffects
: Optional OnCRUDSideEffects object
Use to perform side effects when the app is waiting for a CRUD operation to resolve.
sideEffects
: Optional SideEffects
Used to handle side effects when the state is initialized, mutated and disposed of.
Both onCRUDSideEffects
and sideEffects
used for side effects. These
are the differences between them.
- In pessimistic mode they are equivalent. The
onWaiting
is called while waiting for the backend service result. - In optimistic mode, the difference is in the
onWaiting
hook. InsideEffects
theonWaiting
in never called. sideEffects
hasonData
callback.onCRUDSideEffects
hasonResult
callback that exposes the return result for the backend service.
persist
: Optional callback that return PersistState
If defined, the state will be persisted.
You have to provide a class that implements IPersistStore and initialize it in the main method.
For example
class IPersistStoreImp implements IPersistStore{
// ....
}
void main()async{
WidgetsFlutterBinding.ensureInitialized();
await RM.storageInitializer(IPersistStoreImp());
runApp(MyApp());
}
By default, the state is persisted whenever is mutated, but you can set it to be persisted manually, or once the state is disposed of.
You can debounce and throttle state persistence.
stateInterceptor
: Optional callback that exposes the current and
next SnapState This call back is fired after on state mutation and exposes both the current state just before mutation and the next state.
The callback return the next SnapState. It may be the same as the next state or you can change it. Useful in many scenarios where we want to concatenate both current and next snap (fetch for list of items is an example);
undoStackLength
: Optional integer
It defines the length of the undo/redo stack. If not defined, the undo/redo is disabled.
For the undo/redo state to work properly, the state must be immutable.
Further on, to undo or redo the state just call Injected.undoState and Injected.redoState
dependsOn
: optional DependsOn
Use to defined other injected states that this state depends on. When any of states it depends on is notified, this state is also notified and its creator is re-invoked. The state status will reflect a combination of the state status of dependencies:
- If any of dependency state isWaiting, this state isWaiting.
- If any of dependency state hasError, this state hasError.
- If any of dependency state isIdle, this state isIdle.
- If all dependency states have data, this state hasData.
autoDisposeWhenNotUsed
: Optional bool (Default true)
Whether to auto dispose the injected model when no longer used (listened to).
It is important to note that:
- A state never listened to for rebuild, never auto dispose even after it is mutated.
- By default, all states consumed in the widget tree will auto dispose.
- It is recommended to manually dispose state that are not auto disposed
using
InjectedBaseState.dispose
. You can dispose all states of the app using RM.disposeAll. - A state will auto dispose if all states it depends on are disposed of.
- Non disposed state may lead to unexpected behavior.
- To debug when state is initialized and disposed of use
debugPrintWhenNotifiedPreMessage
parameter (See below)
debugPrintWhenNotifiedPreMessage
: Optional String
if not null, print an informative message when this model is notified in the debug mode. It prints (FROM ==> TO state). The entered message will pré-append the debug message. Useful if the type of the injected model is primitive to distinguish between them.
toDebugString
: Optional callback that exposes the state
String representation of the state to be used in
debugPrintWhenNotifiedPreMessage
. Useful, for example, if the state is a
collection and you want to print its length only.
Implementation
static InjectedCRUD<T, P> injectCRUD<T, P>(
ICRUD<T, P> Function() repository, {
P Function()? param,
bool readOnInitialization = false,
OnCRUDSideEffects? onCRUDSideEffects,
SideEffects<List<T>>? sideEffects,
PersistState<List<T>> Function()? persist,
//
SnapState<List<T>>? Function(
SnapState<List<T>> currentSnap,
SnapState<List<T>> nextSnap,
)? stateInterceptor,
//
int undoStackLength = 0,
DependsOn<List<T>>? dependsOn,
//
bool autoDisposeWhenNotUsed = true,
bool isLazy = true,
String? debugPrintWhenNotifiedPreMessage,
String Function(List<T>?)? toDebugString,
}) {
return InjectedCRUDImp<T, P>(
repoCreator: repository,
param: param,
readOnInitialization: readOnInitialization,
stateInterceptor: stateInterceptor,
sideEffects: sideEffects,
onCRUDSideEffects: onCRUDSideEffects,
dependsOn: dependsOn,
persist: persist,
undoStackLength: undoStackLength,
autoDisposeWhenNotUsed: autoDisposeWhenNotUsed,
debugPrintWhenNotifiedPreMessage: debugPrintWhenNotifiedPreMessage,
toDebugString: toDebugString,
);
// late final InjectedCRUDImp<T, P> inj;
// return inj = InjectedCRUDImp<T, P>(
// repoCreator: repository,
// param: param,
// readOnInitialization: readOnInitialization,
// onCRUD: onCRUDSideEffects ?? onCRUD,
// //
// middleSnapState: stateInterceptor != null
// ? (middleSnap) => stateInterceptor(
// middleSnap.currentSnap,
// middleSnap.nextSnap,
// )
// : middleSnapState,
// onInitialized: sideEffects?.initState != null
// ? (_) => sideEffects!.initState!()
// : onInitialized,
// onDisposed: sideEffects?.dispose != null
// ? (_) => sideEffects!.dispose!()
// : onDisposed,
// onSetState: On(
// () {
// if (sideEffects?.onSetState != null) {
// sideEffects!.onSetState!(inj.snapState);
// } else {
// onSetState?.call(inj.snapState);
// }
// sideEffects?.onAfterBuild?.call();
// },
// ),
// //
// dependsOn: dependsOn,
// undoStackLength: undoStackLength,
// persist: persist,
// autoDisposeWhenNotUsed: autoDisposeWhenNotUsed,
// debugPrintWhenNotifiedPreMessage: debugPrintWhenNotifiedPreMessage,
// toDebugString: toDebugString,
// );
}