injectAuth<T, P> method Null safety

InjectedAuth<T, P> injectAuth<T, P>(
  1. IAuth<T, P> repository(
      ),
    1. {T? unsignedUser,
    2. P param(
        )?,
      1. Duration autoRefreshTokenOrSignOut(
        1. T user
        )?,
      2. FutureOr<Stream<T>> onAuthStream(
        1. IAuth<T, P> repo
        )?,
      3. PersistState<T> persist(
          )?,
        1. void onSigned(
          1. T s
          )?,
        2. void onUnsigned(
            )?,
          1. SnapState<T>? stateInterceptor(
            1. SnapState<T> currentSnap,
            2. SnapState<T> nextSnap
            )?,
          2. SideEffects<T>? sideEffects,
          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 onInitialized(
            1. T? s
            )?,
          7. @Deprecated('Use sideEffects instead') void onDisposed(
            1. T s
            )?,
          8. @Deprecated('Use sideEffects instead') On<void>? onSetState,
          9. @Deprecated('Use `autoRefreshTokenOrSignOut` instead') Duration autoSignOut(
            1. T user
            )?}
          )

          Injection of a state that can authenticate and authorize a user.

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

          The approach consists of the following steps:

          • Define the User Model. (The name is up to you).
          • You may define a class (or enum) to parametrize the query.
          • Your repository must implements IAuth<T, P> where T is the User type and P is the parameter type. with IAuth<T, P> you define sign-(in, up , out) methods.
          • Instantiate an InjectedAuth object using RM.injectAuth method.
          • Later on use InjectedAuth.auth.signUp, InjectedAuth.auth.signIn, and InjectedAuth.auth.signOut for sign up, sign in, sign out.
          • In the UI you can use OnAuthBuilder to listen the this injected state and define the appropriate view for each state.

          Parameters:

          repository: Required callback that returns an object that implements IAuth<T, P>

          IAuth<T, P> forces you to implement the following methods:

          1. Future<void> init() to initialize your authentication service (if it deeds to).

          2. Future<T> signIn(P? param) To sign in using your authentication service. With param you can parametrize your query Example:

                @override
                Future<User> signIn(UserParam param) {
                  switch (param.signIn) {
                    case SignIn.anonymously:
                      return _signInAnonymously();
                    case SignIn.withGoogle:
                      return _signInWithGoogle();
                    case SignIn.withEmailAndPassword:
                      return _signInWithEmailAndPassword(
                        param.email,
                        param.password,
                      );
            
                    default:
                      throw UnimplementedError();
                  }
                }
            
          3. Future<T> signUp(P? param) To sign up

          4. Future<T> signOut(P? param) To sign out

          5. Future<T> refreshToken(T currentUser) To refresh user token It exposes the currentUser model, where you get the refresh token. If the token is successfully refreshed, a new copy of the current user holding the new token is return.

            Example:

              @override
              Future<User?>? refreshToken(User? currentUser) async {
            
               final response = await http.post( ... );
            
               if (response.codeStatus == 200){
                return currentUser!.copyWith(
                  token: response.body['id_token'],
                  refreshToken: response.body['refresh_token'],
                  tokenExpiration: DateTime.now().add(
                      Duration(seconds: response.body[expires_in] ),
                  ),
                );
               }
            
               return null;
            
              }
            
          6. void dispose() To dispose any resources.

          Apart from these six methods, you can define other custom methods and invoke them using InjectedAuth.getRepoAs method.

          unsignedUser: Optional T

          An object that represents an unsigned user. If T is nullable unsignedUser is null. unsignedUser value is used internally to decide to call signed hooks or unsigned hooks.

          param: Optional callback that returns P

          The default param object to be used in IAuth.signIn, IAuth.signUp, and IAuth.signOut methods.

          You can override the default value when calling InjectedAuth.auth.signIn , InjectedAuth.auth.signUp, InjectedAuth.auth.signOut

          autoRefreshTokenOrSignOut: Optional callback that exposes the signed user and returns a Duration.

          After the return duration, the user will try to refresh the token as implemented inIAuth.refreshToken.If the token is not refreshed then the user is sign out.

          See IAuth.refreshToken

          onAuthStream: Optional callback that exposes the repository and

          returns a stream. It is used to listen to a stream from the repository. The stream emits the value of the currentUser. Depending on the emitted user, sign in or sign out hooks will be invoked.

          persist: Optional callback that return PersistState

          If defined, the signed user 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());
          }
          

          If persist is defined the signed user information is persisted and when the app starts up, the user information is retrieved from the local storage and it is automatically signed in if it has no expired token.

          Example:

          final user = RM.injectAuth<User?, UserParam>(
            () => FireBaseAuth(),
            persist: () => PersistState<User?>(
              key: '__User__',
              toJson: (user) => user?.toJson(),
              fromJson: (json) {
                final user = User.fromJson(json);
                return user.token.isNotExpired ? user : null;
              },
            ),
          );
          

          onSigned: Optional callback that exposes the signed user

          It is used to call side effects when the user is signed.

          onUnSigned: Optional callback

          It is used to call side effects when the user is unsigned.

          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.

          The callback return the next SnapState. It may be the same as the next state or you can change it.

          sideEffects: Optional SideEffects

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

          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 InjectedAuth<T, P> injectAuth<T, P>(
            IAuth<T, P> Function() repository, {
            T? unsignedUser,
            P Function()? param,
            Duration Function(T user)? autoRefreshTokenOrSignOut,
            FutureOr<Stream<T>> Function(IAuth<T, P> repo)? onAuthStream,
            PersistState<T> Function()? persist,
            //
            void Function(T s)? onSigned,
            void Function()? onUnsigned,
            SnapState<T>? Function(
              SnapState<T> currentSnap,
              SnapState<T> nextSnap,
            )?
                stateInterceptor,
            SideEffects<T>? sideEffects,
            //
            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,
            @Deprecated('Use `autoRefreshTokenOrSignOut` instead')
                Duration Function(T user)? autoSignOut,
          }) {
            assert(() {
              if (null is! T && unsignedUser == null) {
                StatesRebuilerLogger.log(
                  '$T is non-nullable and the unsignedUser is null',
                  'You have to define unsignedUser parameter.\n'
                      'If you want the unsignedUser to be null use nullable type ($T?)',
                );
                return false;
              }
          
              return true;
            }());
            assert(() {
              if (null is T && unsignedUser != null) {
                StatesRebuilerLogger.log(
                  '$T is nullable, null is considered as the unsigned user',
                  'You can not set a non-null unsignedUser\n'
                      'If you want the unsignedUSer to be non-null use non-nullable type ($T).',
                );
                return false;
              }
          
              return true;
            }());
          
            late final InjectedAuthImp<T, P> inj;
            return inj = InjectedAuthImp<T, P>(
              repoCreator: repository,
              unsignedUser: unsignedUser,
              param: param,
              onSigned: onSigned,
              onUnsigned: onUnsigned,
              autoSignOut: autoRefreshTokenOrSignOut ?? autoSignOut,
              onAuthStream: onAuthStream,
              //
              middleSnapState: stateInterceptor != null
                  ? (middleSnap) => stateInterceptor(
                        middleSnap.currentSnap,
                        middleSnap.nextSnap,
                      )
                  : middleSnapState,
              sideEffects: SideEffects<T>(
                initState: () {
                  if (sideEffects?.initState != null) {
                    sideEffects?.initState?.call();
                  } else {
                    onInitialized?.call(inj.state);
                  }
                },
                onSetState: (snap) {
                  if (sideEffects?.onSetState != null) {
                    sideEffects?.onSetState?.call(snap);
                  } else {
                    onSetState?.call(snap);
                  }
                },
                dispose: () {
                  if (sideEffects?.dispose != null) {
                    sideEffects?.dispose?.call();
                  } else {
                    onDisposed?.call(inj.state);
                  }
                },
              ),
              //
              persist: persist,
              debugPrintWhenNotifiedPreMessage: debugPrintWhenNotifiedPreMessage,
              toDebugString: toDebugString,
            );
          }