inject<T> method Null safety

Injected<T> inject<T>(
  1. T creator(
      ),
    1. {T? initialState,
    2. SnapState<T>? stateInterceptor(
      1. SnapState<T> currentSnap,
      2. SnapState<T> nextSnap
      )?,
    3. void onInitialized(
      1. T? s
      )?,
    4. SideEffects<T>? sideEffects,
    5. DependsOn<T>? dependsOn,
    6. int undoStackLength = 0,
    7. PersistState<T> persist(
        )?,
      1. bool isLazy = true,
      2. bool autoDisposeWhenNotUsed = true,
      3. String? debugPrintWhenNotifiedPreMessage,
      4. Object? toDebugString(
        1. T?
        )?,
      5. @Deprecated('Use stateInterceptor instead') SnapState<T>? middleSnapState(
        1. MiddleSnapState<T> middleSnap
        )?,
      6. @Deprecated('Use sideEffects instead') void onDisposed(
        1. T s
        )?,
      7. @Deprecated('Use sideEffects instead') void onWaiting(
          )?,
        1. @Deprecated('Use sideEffects instead') void onData(
          1. T s
          )?,
        2. @Deprecated('Use sideEffects instead') On<void>? onSetState,
        3. @Deprecated('Use sideEffects instead') void onError(
          1. dynamic e,
          2. StackTrace? s
          )?}
        )

        Injection of a primitive, enum, or object.

        State can be injected globally or scoped locally:

        Scoped locally means that the state's flow is encapsulated within the widget and its children. If more than one widget is created, each has its own independent state.

        • Global state:

          //In the global scope
          final myState = RM.inject(() => MyState())
          

          // Or Encapsulate it inside a business logic class (BLOC):

          //For the sake of best practice, one strives to make the class immutable
          @immutable
          class MyBloc {  // or MyViewModel, or MyController
            final _myState1 = RM.inject(() => MyState1())
            final _myState2 = RM.inject(() => MyState2())
          
            //Other logic that mutate _myState1 and _myState2
          }
          
          //As MyBloc is immutable, it is safe to instantiate it globally
          final myBloc = MyBloc();
          
        • Scoped state (local state)

          If the state or the Bloc are configurable (parametrized), Just declare them globally and override the state in the widget tree.

          BloC stands for Business Logic, and when it is attached to the widget tree it becomes a Presentation logic or view model.

          // The state will be initialized in the widget tree.
          final myState = RM.inject(() => throw UnimplementedError())
          
          // In the widget tree
          myState.inherited(
            stateOverride: () {
              return MyState(parm1,param2);
            },
            builder: (context) {
              // Read the state through the context
              final _myState = myState.of(context);
            }
          )
          

          Similar with Blocs

          final myBloC = RM.inject<myBloC>(() => throw UnimplementedError())
          
          //In the widget tree
          myState.inherited(
            stateOverride: () {
              return myBloC(parm1, param2);
            },
            builder: (context) {
              final _myBloC = myBloC.of(context);
            }
          )
          

        Parameters:

        creator: Required callback that returns <T>

        A callback that is used to create an instance of the injected object. It is called when:

        • The state is first initialized
        • The state is refreshed by calling InjectedBase.refresh method.
        • Any of the states that it depends on emits a notification.

        initialState: Optional <T>

        The initial state. It is useful when injecting Future or Stream. If you try to get the state of non-resolved Future or Stream of non-nullable state, it will throw if initialState is not defined.

        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)

        sideEffects: Optional SideEffects

        Used to handle sideEffects when the state is initialized, mutated and disposed of. Side effects defined here are called global (default) and can be overridden when calling InjectedBase.setState method.

        See also: InjectedBase.setState, OnBuilder.sideEffects and OnReactive.sideEffects

        onInitialized: Optional callback That exposed the state

        Callback to be executed after the injected model is first created. It is similar to SideEffects.initState except that it exposes the state for some useful cases.

        If the injected state is stream, onInitialized additionally exposes the StreamSubscription object to be able to pause the stream.

        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.

        You can set when the state should be recreated, the time of debounce and the time of throttle.

        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

        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);

        Example:

        final myState = RM.inject(
         () => [],
         stateInterceptor: (currentSnap, nextSnap) {
           return nextSnap.copyTo(data: [
             ...currentSnap.state,
             ...nextSnap.state,
           ]);
         },
        );
        /// later on
        myState.state = ['one'];
        print(myState.state); // ['one']
        
        myState.state = ['two'];
        print(myState.state); // ['one', 'two']
        
        

        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 Injected<T> inject<T>(
          T Function() creator, {
          T? initialState,
          SnapState<T>? Function(SnapState<T> currentSnap, SnapState<T> nextSnap)?
              stateInterceptor,
          void Function(T? s)? onInitialized,
          SideEffects<T>? sideEffects,
          DependsOn<T>? dependsOn,
          //
          int undoStackLength = 0,
          PersistState<T> Function()? persist,
          //
          bool isLazy = true,
          //
          bool autoDisposeWhenNotUsed = true,
          String? debugPrintWhenNotifiedPreMessage,
          Object? Function(T?)? toDebugString,
          @Deprecated('Use stateInterceptor instead')
              SnapState<T>? Function(MiddleSnapState<T> middleSnap)? middleSnapState,
          @Deprecated('Use sideEffects instead') void Function(T s)? onDisposed,
          @Deprecated('Use sideEffects instead') void Function()? onWaiting,
          @Deprecated('Use sideEffects instead') void Function(T s)? onData,
          @Deprecated('Use sideEffects instead') On<void>? onSetState,
          @Deprecated('Use sideEffects instead')
              void Function(dynamic e, StackTrace? s)? onError,
        }) {
          late final InjectedImp<T> inj;
        
          return inj = InjectedImp<T>(
            creator: creator,
            initialState: initialState,
            onInitialized: sideEffects?.initState != null
                ? (_) => sideEffects!.initState!()
                : onInitialized,
            onSetState: On(
              () {
                sideEffects
                  ?..onSetState?.call(inj.snapState)
                  ..onAfterBuild?.call();
                onSetState?.call(inj.snapState);
              },
            ),
            onWaiting: onWaiting,
            onDataForSideEffect: onData,
            onError: onError,
            onDisposed: sideEffects?.dispose != null
                ? (_) => sideEffects!.dispose!()
                : onDisposed,
            dependsOn: dependsOn,
            undoStackLength: undoStackLength,
            persist: persist,
            middleSnapState: stateInterceptor != null
                ? (middleSnap) => stateInterceptor(
                      middleSnap.currentSnap,
                      middleSnap.nextSnap,
                    )
                : middleSnapState,
            isLazy: isLazy,
            debugPrintWhenNotifiedPreMessage: debugPrintWhenNotifiedPreMessage,
            toDebugString: toDebugString,
            autoDisposeWhenNotUsed: autoDisposeWhenNotUsed,
          );
        }