Skip to content

Ready handler for synchronous creations of instances #115

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
mr-mmmmore opened this issue Oct 12, 2020 · 9 comments
Closed

Ready handler for synchronous creations of instances #115

mr-mmmmore opened this issue Oct 12, 2020 · 9 comments

Comments

@mr-mmmmore
Copy link

mr-mmmmore commented Oct 12, 2020

I am using GetIt in an injection container and I would like to trigger some code when a singleton is created lazily and synchronously. The API could be something like this:

// new usage of signalsReady for sync registrations
sl.registerLazySingleton<ClassA>(() => ClassA(), signalsReady: true); 
sl.registerLazySingleton<ClassB>(() => ClassB());
// new onReady handler, called when the instance of ClassA is created
sl.onReady<ClassA>((obj) => sl<ClassB>().doSomeStuffWithA(obj));

Which would do this: when the instance of A is created, the callback creates an instance of B (sl<ClassB>()) that do some stuff with the A instance (for example adding it as a listener). Here the classes don't need to perform async tasks when created, they just need to be created only when needed, be ignorant of each other and trigger onReady when created.

Is there already a way to do this? As far as I understand the signalsReady/isReady/dependsOn/etc. stuff is for async singletons and factories.

@escamoteur
Copy link
Collaborator

you only need such a system if async singletons are involved because Dart is single threaded. you can use registerSingletonWidthDependency if you have a sync singleton that depends on one or more async singletons.
Or do I miss here something?

@mr-mmmmore
Copy link
Author

Do you mean something like this?

sl.registerLazySingleton<ClassA>(() => ClassA()); 
sl.registerSingletonWithDependencies<ClassB>(
  () {
    var b = ClassB();
    b.doSomeStuffWithA(sl<ClassA>());
    return b;
  },
  dependsOn: [ClassA]);

I hadn't thought of using registerSingletonWithDependencies since its not lazy, but I now assume that depending on a lazily created instance makes it lazy, do you confirm?

@escamoteur
Copy link
Collaborator

Nope, you only can use async singletons or other withDependencies in dependsOn.

In the scenario in your example you have to do nothing. just declare both as Lazy and you are fine

@mr-mmmmore
Copy link
Author

OK, but then how do I trigger code upon creation of ClassA without introducing dependencies inside it? One option would be to trigger it from inside the ClassA constructor, using a callback:

ClassA {
  ClassA({Function onCreated}) {
    Function.apply(onCreated, [this]);
  }
}

sl.registerLazySingleton<ClassA>(() => ClassA(
  onCreated: (a) {
    sl<ClassB>().doSomeStuffWithA(a):
  })); 
sl.registerLazySingleton<ClassB>(() => ClassB()); 

But I'd rather avoid it because it looks weird and clutters ClassA. And since GetIt handles the creation of instance I though it would be natural for it to notify when instances are created.

@escamoteur
Copy link
Collaborator

Why is it important to know when the instance of A is created? It happens to moment the first tries to access it.

I'm just trying to understand your application to come up with a good idea. You are the first asking for something like that.
As all is single threaded normally there is no need for that.

@mr-mmmmore
Copy link
Author

Why is it important to know when the instance of A is created? It happens to moment the first tries to access it.

Because I need to run some code at that moment: as soon as the class is created I want to register it as a listener to another one. It is not possible before because the instance doesn't exist (since it's created lazily), and it must be done as soon as the instance exists.

I'm just trying to understand your application to come up with a good idea. You are the first asking for something like that.
As all is single threaded normally there is no need for that.

I am doing all the registering in a single container file and almost never use GetIt elsewhere (I use it in the views to inject view models). All the classes get their dependencies injected in this container file. I want to add bindings between classes at the moment I define their creation because it would be simpler to do it there.

@escamoteur
Copy link
Collaborator

Hmm, I wouldn't do it that way, but just in case.
Why not registering B first (lazily) and then register B inside the constructor of the lazy registered A using sl()?

Also lazy registrations only make sense for objects that take time to construct or are otherwise expensive.

@mr-mmmmore
Copy link
Author

mr-mmmmore commented Oct 13, 2020

Hmm, I wouldn't do it that way, but just in case.
Why not registering B first (lazily) and then register B inside the constructor of the lazy registered A using sl()?

Yes, doing the needed stuff inside the constructor of A seems the way to go. But I don't want all my classes to have a dependency to GetIt and in general I prefer to inject instances through constructors. So I can adapt the classes in order to make the dependency added to A acceptable : define an interface for class B, make A depend on this interface and inject the implementation in the constructor of A.

But I encounter the following problem: class B actually implements 2 interfaces and I would like to get the same instance when asking any of these 2 interfaces:

abstract class Contract1 {
  void method1();
}
abstract class Contract2 {
  void method2();
}

class B implements Contract1, Contract2 {
  @override
  void method1() => print('method1');
  @override
  void method2() => print('method2');
}
// first attempt -------------
// this registers a different instance of B for Contract2:
sl.registerLazySingleton<Contract1>(() => B());
sl.registerLazySingleton<Contract2>(() => B());
assert(sl<Contract1>().hashCode == sl<Contract2>().hashCode, 'different'); // --> different

// second attempt -------------
// this works but is akward isn't it?
// It only works because we know that the implementation of Contract1 is also a Contract2.
sl.registerLazySingleton<Contract2>(() => sl<Contract1>() as Contract2);
assert(sl<Contract1>().hashCode != sl<Contract2>().hashCode, 'same'); // --> same

What do you think about sl<Contract1>() as Contract2 ? Since it's all in the same file and I register Contract1 just before, I could live with it, but do you think there is a better way to achieve this?

Also lazy registrations only make sense for objects that take time to construct or are otherwise expensive.

Since I am doing all the registering at startup I think the time to call all these constructors will add up and increase the app starting time. So I prefer to only create what is needed and defer the rest to later.

@escamoteur
Copy link
Collaborator

Unfortunately automatic injection through the constructor isn't possible without code generation and I'm not a fan of it because its to much magic going on.

I always use interfaces as the type to register a singleton so you are free to switch implementations any time.
actually your solution is the one that also came to my mind. you could case instead to the other interface to the implementation type by this you don't make it dependent of another class.

If you decide to go with a service locator I don't see anything bad using it inside all classes where needed.

BTW have you already heard that there is now a get_it_mixin and a get_it_hooks?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants