injectTheme<T> method Null safety

InjectedTheme<T> injectTheme<T>(
  1. {required Map<T, ThemeData> lightThemes,
  2. Map<T, ThemeData>? darkThemes,
  3. ThemeMode themeMode = ThemeMode.system,
  4. String? persistKey,
  5. SnapState<T>? stateInterceptor(
    1. SnapState<T> currentSnap,
    2. SnapState<T> nextSnap
    )?,
  6. SideEffects<T>? sideEffects,
  7. int undoStackLength = 0,
  8. DependsOn<T>? dependsOn,
  9. bool autoDisposeWhenNotUsed = true,
  10. bool isLazy = true,
  11. String? debugPrintWhenNotifiedPreMessage,
  12. Object? toDebugString(
    1. T?
    )?,
  13. @Deprecated('Use stateInterceptor instead') SnapState<T>? middleSnapState(
    1. MiddleSnapState<T> middleSnap
    )?,
  14. @Deprecated('Use sideEffects instead') void onInitialized(
    1. T? s
    )?,
  15. @Deprecated('Use sideEffects instead') void onDisposed(
    1. T s
    )?,
  16. @Deprecated('Use sideEffects instead') On<void>? onSetState}
)

Injection of a state that handle app theme switching.

This injected state abstracts the best practices of the clean architecture to come out with a simple, clean, and testable approach to manage app theming.

The approach consists of the following steps:

  • Instantiate an InjectedTheme object using RM.injectTheme method.
  • we use the TopAppWidget that must be on top of the MaterialApp widget.
     void main() {
       runApp(MyApp());
     }
    
     class MyApp extends StatelessWidget {
       // This widget is the root of your application.
       @override
       Widget build(BuildContext context) {
         return TopAppWidget(//Use TopAppWidget
           injectedTheme: themeRM, //Set te injectedTheme
           builder: (context) {
             return MaterialApp(
               theme: themeRM.lightTheme, //light theme
               darkTheme: themeRM.darkTheme, //dark theme
               themeMode: themeRM.themeMode, //theme mode
               home: HomePage(),
             );
           },
         );
       }
     }
    

Parameters:

lightThemes: Required Map<T, ThemeData>

Map of light themes the app supports. The keys of the Map are the names of the themes. T can be String or enumeration.

darkThemes: Optional Map<T, ThemeData>

Map of dark themes the app supports. There should be a correspondence between light and dark themes. Nevertheless, you can have light themes with no corresponding dark one.

themeMode: Optional ThemeMode

the ThemeMode the app should start with.

persistKey: Optional String

If defined the app theme is persisted to a local storage. The persisted theme will be used on app restarting.

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

stateInterceptor: Optional callback that exposes the current and

next SnapState This call back is fired after on state mutation (singed user change) and exposes both the current state just before mutation and the next state.

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

sideEffects: Optional SideEffects

Used to handle sideEffects when the state is initialized, mutated and disposed of.

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 InjectedTheme<T> injectTheme<T>({
  required Map<T, ThemeData> lightThemes,
  Map<T, ThemeData>? darkThemes,
  ThemeMode themeMode = ThemeMode.system,
  String? persistKey,
  //
  SnapState<T>? Function(
    SnapState<T> currentSnap,
    SnapState<T> nextSnap,
  )?
      stateInterceptor,
  SideEffects<T>? sideEffects,
  //
  int undoStackLength = 0,
  DependsOn<T>? dependsOn,
  //
  bool autoDisposeWhenNotUsed = true,
  bool isLazy = 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)? onInitialized,
  @Deprecated('Use sideEffects instead') void Function(T s)? onDisposed,
  @Deprecated('Use sideEffects instead') On<void>? onSetState,
}) {
  assert(
    T != dynamic && T != Object,
    'Type can not inferred, please declare it explicitly',
  );
  late final InjectedThemeImp<T> inj;
  return inj = InjectedThemeImp<T>(
    lightThemes: lightThemes,
    darkThemes: darkThemes,
    themeModel: themeMode,
    persistKey: persistKey,
    //
    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,
    //
    autoDisposeWhenNotUsed: autoDisposeWhenNotUsed,
    isLazy: isLazy,
    debugPrintWhenNotifiedPreMessage: debugPrintWhenNotifiedPreMessage,
    toDebugString: toDebugString,
  );
}