fluttercommunity / get_it

Get It - Simple direct Service Locator that allows to decouple the interface from a concrete implementation and to access the concrete implementation from everywhere in your App. Maintainer: @escamoteur
https://pub.dev/packages/get_it
MIT License
1.34k stars 145 forks source link

[Feature request] Factory classes with manual lifecycle #202

Closed vishalrao8 closed 3 years ago

vishalrao8 commented 3 years ago

It would be helpful to have factory registrations with an extended lifecycle as multiple calls to factory registered class will result as a single instance up to the desired class or when called with a unique ID bound to each factory class. It's like singletons with parameter support. This can be useful in inter bloc communication where Bloc B can add events to Bloc A and injecting or accessing Bloc A inside Bloc B results as the same instance of Bloc A and not a new one. Once Bloc A is closed, the factory class can be reset like lazy singletons or scope.

I tried using Blocs as lazySingletons and resetting there lifecycle in onClose() method but had to switch to static instance pattern because singletons can't have factoryParams

  static BlocA? _instance;

  static BlocA getInstance({
    dynamic? param1,
    dynamic? param2,
  }) {
    if (_instance != null) {
      return _instance!;
    } else {
      return _instance = getIt<BlocA>(
        param1: param1,
        param2: param2,
      );
    }
  }
@override
  Future<void> close() {
    _instance = null;
    return super.close();
  }
escamoteur commented 3 years ago

What about adding an registerLazysingletonParam()?

vishalrao8 commented 3 years ago

is it coming in the next release?

escamoteur commented 3 years ago

let me check how much work it is

escamoteur commented 3 years ago

Hmm, why don't you just unregister the BLoC and register it again instead of using resetLazySingleton? I already added the feature but while writing the docs it just doesn't feel right.

vishalrao8 commented 3 years ago

resetting lazy singleton is not the problem, inability of singletons to receive parameters is because blocs have to depend on user passed parameters in some cases.

On Tue, 15 Jun, 2021, 9:55 pm escamoteur, @.***> wrote:

Hmm, why don't you just unregister the BLoC and register it again instead of using resetLazySingleton? I already added the feature but while writing the docs it just doesn't feel right.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/fluttercommunity/get_it/issues/202#issuecomment-861644588, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGOWIYMTYHSOJR6JC2NOLY3TS55HNANCNFSM46WCOW6A .

escamoteur commented 3 years ago

but you can pass any parameters when registering a Singleton. Why do they have to be set when you first access them and not when registering them?

vishalrao8 commented 3 years ago

Actually, I'm using the injectable library in flutter which generates all getIt code for me and registration happens with annotations. I can try to create getIt objects by myself for Bloc classes but that way I will have to move away from my existing pattern of auto registration to manual registration.

On Wed, Jun 16, 2021 at 2:09 PM escamoteur @.***> wrote:

but you can pass any parameters when registering a Singleton. Why do they have to be set when you first access them and not when registering them?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/fluttercommunity/get_it/issues/202#issuecomment-862170394, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGOWIYN3S6S424RS2K4TPXDTTBPLXANCNFSM46WCOW6A .

escamoteur commented 3 years ago

ahh, that's the reason. IMHO it would make more sense. I realized it when I wrote a test doing a second get with different parameters on the same lazySingleton which would not change the object which just feels wrong. Actually why aren't you using a factory for your BloCs?

vishalrao8 commented 3 years ago

Bloc provided in one widget tree can't be accessed in another tree when a new page is pushed in navigation. Assuming there is one bloc per screen. If I want the action of the user on one page to be reflected on some other page, I need the same instance of the previous pages' bloc in the current bloc. For example, there are three screens - Screen A, Screen B and Screen C with respective Bloc A, Bloc B, Bloc C with navigation order as screen A -> B -> C. If someone acts on-screen C, it should update the state of screen A and B too. For this scenario, I am planning to have a single static instance of Bloc A and Bloc B which will be accessed in Bloc C which can add events to these blocs. The lifecycle of these blocs will be dependant on the lifecycle of widget tree of the respective screen.

Bloc A will close when Screen A is removed from the widget tree. Bloc B will close when Screen B is removed from the widget tree.

This behaviour is already implemented in the flutter bloc library. I simply reset the static instance of the bloc in the close method.

What I am looking for with this feature request is, if I can register my bloc classes which have a lifecycle attached to my screen widget tree so that it only resets when the widget tree is removed i.e. when the screen is poped from the navigation stack. And required bloc classes can easily be injected into upcoming blocs which will get created when the user pushes a new screen.

Another approach to get access to the previously created bloc is passing blocs in widget's parameters and navigation's argument which is very hard to maintain and messes up the codebase. Bloc of screen A will be passed to screen B and from screen B to Bloc B. And the complexity increases with the number of sub navigation.

If bloc is registered at the base of the material app widget, it's accessible everywhere but I am talking about sub-navigation of a particular feature and making singleton instance blocs accessible in other bloc classes of that feature.

Like Screen A is the home page of the chat feature, Screen B is the message page of the chat feature and adding a new message on screen B should also update the state of Screen A with newly added message.

escamoteur commented 3 years ago

OK, I understand. Because I'm not that familiar with the BLoC library because I have my own StateManagement packages, I wasn't aware that the BLoC actually stores a state.

In that case I really would recommend registering the BLoCs manually when you need them by that you can pass the needed arguments. It would also be slightly more memory effcient because even if Lazysingletons aren't instantiated, the take a bit of memory. You probably would profit by using the scope functions from getit to easily dispose/undergister when you no longer need them.

vishalrao8 commented 3 years ago

Ok, will try and get back to you in case I need any help. Thank you for your cooperation and replies.

vishalrao8 commented 3 years ago

How much impact does registering lazySingletons have on memory as injectable library registers all the lazy singletons at once when configured at app startup.

escamoteur commented 3 years ago

this here is created for every registration of an object in GetIt for management

class _ServiceFactory<T extends Object, P1, P2> {
  final _ServiceFactoryType factoryType;

  final _GetItImplementation _getItInstance;

  late final Type param1Type;
  late final Type param2Type;

  /// Because of the different creation methods we need alternative factory functions
  /// only one of them is always set.
  final FactoryFunc<T>? creationFunction;
  final FactoryFuncAsync<T>? asyncCreationFunction;
  final FactoryFuncParam<T, P1?, P2?>? creationFunctionParam;
  final FactoryFuncParamAsync<T, P1?, P2?>? asyncCreationFunctionParam;

  ///  Dispose function that is used when a scope is popped
  final DisposingFunc<T>? disposeFunction;

  /// In case of a named registration the instance name is here stored for easy access
  final String? instanceName;

  /// true if one of the async registration functions have been used
  final bool isAsync;

  /// If a an existing Object gets registered or an async/lazy Singleton has finished
  /// its creation it is stored here
  Object? instance;

  /// the type that was used when registering. used for runtime checks
  late final Type registrationType;

  /// to enable Singletons to signal that they are ready (their initialization is finished)
  late Completer<T> _readyCompleter;

  /// the returned future of pending async factory calls or factory call with dependencies
  Future<T>? pendingResult;

  /// If other objects are waiting for this one
  /// they are stored here
  final List<Type> objectsWaiting = [];

  bool get isReady => _readyCompleter.isCompleted;

  bool get isNamedRegistration => instanceName != null;

  String get debugName => '$instanceName : $registrationType';

  bool get canBeWaitedFor =>
      shouldSignalReady || pendingResult != null || isAsync;

  final bool shouldSignalReady;