injectAuth<T, P> static method

InjectedAuth<T, P> injectAuth<T, P>(
  1. IAuth<T, P> repository(), {
  2. T? unsignedUser,
  3. P param()?,
  4. Duration autoRefreshTokenOrSignOut(
    1. T user
    )?,
  5. FutureOr<Stream<T>> onAuthStream(
    1. IAuth<T, P> repo
    )?,
  6. PersistState<T> persist()?,
  7. void onSigned(
    1. T s
    )?,
  8. void onUnsigned()?,
  9. SnapState<T>? stateInterceptor(
    1. SnapState<T> currentSnap,
    2. SnapState<T> nextSnap
    )?,
  10. SideEffects<T>? sideEffects,
  11. String? debugPrintWhenNotifiedPreMessage,
  12. Object? toDebugString(
    1. T?
    )?,
})

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,
}) {
  assert(() {
    if (null is! T && unsignedUser == null) {
      StatesRebuilerLogger.log(
        '',
        '$T is non-nullable and the unsignedUser is null',
      );
      StatesRebuilerLogger.log(
        '',
        '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',
      );
      StatesRebuilerLogger.log(
        '$T is nullable, null is considered as the unsigned user. '
            'You can not set a non-null "unsignedUser"',
        'IF YOU WANT THE "unsignedUser" TO BE NON-NULL USE NON6 NULLABLE TYPE  ($T)',
      );
      return false;
    }

    return true;
  }());

  return InjectedAuthImp(
    repoCreator: repository,
    unsignedUser: unsignedUser,
    param: param,
    autoSignOut: autoRefreshTokenOrSignOut,
    onSigned: onSigned,
    onUnsigned: onUnsigned,
    onAuthStream: onAuthStream,
    stateInterceptor: stateInterceptor,
    sideEffects: sideEffects,
    persist: persist,
    debugPrintWhenNotifiedPreMessage: debugPrintWhenNotifiedPreMessage,
    toDebugString: toDebugString,
  );

  // 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,
  // );
}