-
-
Notifications
You must be signed in to change notification settings - Fork 153
Register factories that take parameters #29
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
Comments
to make this types ave it would have to be a separate registration function which wouldn't be a big deal, but I'm not sure if it's a good idea to establish another Maybe adding a new method 'newInstanceOf<FactoryType,paramType>(paramValue)' for registered factories could be a solution. What do you think? @anaisbetts what do you think? |
On Android at the moment I'm using Shank SL. It allows up to 4 parameters to be passed which is usually enough. Even supporting 1 parameter could be enough as you can always wrap your parameters. This should work with singletons as well. So if you have
it should return 2 different instances. 1 and 3 would be same instance and 2 would be different instance |
IMHO this goes a bit against the idea of a singleton :-) My problem is with that that then you always will have to use a (_) => in theregistrations if there is no parameter expected. And also always supply a second generic type It would have to look like this: getIt.registerLazySingleton<MySingleton,String>((s) => MySingleton(s)); How do we ensure type safety in your above get examples? |
What about for non-singleton factories? Like a ViewModel. They would often need different params passed in for new instances each time. |
The thing is, the whole idea of registration is that you register an interface, and the thing created is a concrete type. Interfaces inherently have no concept of constructors (though you could argue that abstract base classes do). If you have 5 different implementations of If you are creating a parameterized ViewModel, you should just use Maybe I'm just not thinking through all of the use-cases, but in general it seems like if you have a need to use this feature, you might be putting the Wrong Things into service location, instead of "Things that vary based on Dev/Prod/Test". |
@anaisbetts I used to work with Xamarin and used AutoFac. I would register a bunch of objects in my container. Some wouldn't require any parameters, some would require other objects in the container as a dependency in the constructor, and others (ViewModels) would require other objects and also have some way to pass an argument (Usually with an The reason I would normally throw everything in my container/locator is that I can choose which of the dependent objects I want Real and which I want Mock. Most often everything but the ViewModel would be mocked to test the ViewModel itself. However in a larger, end to end or integration test, I would sometimes want more of my objects to be real and less mocks. (Real database, Mock API, as an example). So I guess I am just used to putting everything in a DI container and having it pull in what it needs to create an object and I am trying to mimic that here. Perhaps I should be thinking of things differently instead of trying to use just a ServiceLocator where I want DI? Not sure. |
Ah! So, the best pattern to do this is via optional parameters:
In production this is a parameterless constructor, and in a test you just use |
As a user of this library, I would very appreciate an ability to add parameters for registering factories. In a manner of what @escamoteur proposed. This possibility would make my code much cleaner. I use BLoCs, one per page. Sometimes I need to pass arguments there like an id of some object I want to display or smth else. Without this ability, I am forced to spoil my code with the details I don't want to see. Eg creation of some of the blocs can look like that: _bloc = MyBloc(
"some_recording_id",
getIt.get<UserRepository>(),
getIt.get<RecordingsRepository>(),
getIt.get<ConnectedDevicesRepository>(),
getIt.get<UploadRecordingsRepository>(),
getIt.get<UploadVideosRepository>(),
);
Instead of that what I really want is to divide this into two parts. First one should replace the code from above because it's the only one thing that I should care about during BLoC creation: _bloc = getIt.get<MyBloc>(firstParam: "some_recording_id");
And the second part should be declared where all my classes set up: _bloc = getIt.registerFactory<MyBloc, String>((String recordingId) =>
MyBloc(
recordingId,
getIt.get<UserRepository>(),
getIt.get<RecordingsRepository>(),
getIt.get<ConnectedDevicesRepository>(),
getIt.get<UploadRecordingsRepository>(),
getIt.get<UploadVideosRepository>(),
));
This helps me to keep my pages and widgets clear from all the dependencies that needed for BLoC only. And it easier to work with such code. Creation of all your business logic classes stored in one place. I understand that maybe this approach does not match with the pure concept of Service Locator pattern., but I don't think that a good library should have this aim in mind. Especially when there are not a lot of alternatives you can find. |
@votruk Would it be ok for you if this would be a separate registration function, so that the existing one wouldn't be changed? Otherwise we would force user always to pass a factoryFunction with a dummyID? |
@escamoteur I think it would be a wise decision taking into account backwards compatibility. The main question is about what methods should look like. Should it be like that for getters: getIt.getWithArg<T>(dynamic arg, [String instanceName]);
getIt.getWithArg2<T>(dynamic arg1, dynamic arg2, [String instanceName]);
getIt.getWithArg3<T>(dynamic arg1, dynamic arg2, dynamic arg3, [String instanceName]);
...
getIt.getWithArgs<T>(List<dynamic> args, [String instanceName]) Or it would be better to make types explicit? getIt.getWithArg<T, TArg>(TArg arg, [String instanceName]);
getIt.getWithArg2<T, TArg1, TArg2>(TArg1 arg1, TArg2 arg2, [String instanceName]);
getIt.getWithArg3<T, TArg1, TArg2, TArg3>(TArg1 arg1, TArg2 arg2, TArg3 arg3, [String instanceName]);
...
getIt.getWithArgs<T>(List<dynamic> args, [String instanceName]) What about instance names? Don't you think that it would be clearer to use named optional parameters instead? getIt.getWithArg<T, TArg>( TArg arg, {String instanceName});
getIt.getWithArg2<T, TArg1, TArg2>(TArg1 arg1, TArg2 arg2, {String instanceName});
getIt.getWithArg3<T, TArg1, TArg2, TArg3>(TArg1 arg1, TArg2 arg2, TArg3 arg3, {String instanceName});
...
getIt.getWithArgs<T>(List<dynamic> args, {String instanceName}) Registration: getIt.registerFactoryWithArg<T, TArg>(T Function(TArg arg) func, {String instanceName});
getIt.registerFactoryWithArg<T, TArg1, TArg2>(T Function(TArg1 arg1, TArg2 arg2) func, {String instanceName});
getIt.registerFactoryWithArg<T, TArg1, TArg2, TArg3>(T Function(TArg1 arg1, TArg2 arg2, TArg3 arg3) func, {String instanceName});
...
getIt.registerFactoryWithArg<T>(List<dynamic> args, {String instanceName}) Another question. Should we allow to register only factories or lazySingletons too? |
I'd make types explicit and would allow to register lazySingletons. What would be a reason not to? Question is how many parameters will you support. On Android I use a SL library that allows up to 3 parameters and it's usually enough. In rare cases when it's not you can always create wrapper model that cas all the required parameters in there |
@mvarnagiris class NameProvider {
final String _name;
NameProvider(this._name);
String get getName => _name;
}
getIt.registerLazySingletonWithArg<NameProvider, String>((name) => NameProvider(name));
class First {
String getNameForFirst() => getIt.getWithArg<NameProvider, String>("First class").getName();
}
class Second {
String getNameForSecond() => getIt.getWithArg<NameProvider, String>("Second class").getName();
}
void notObviousBehaviour() {
print(First().getNameForFirst());
print(Second().getNameForSecond());
// you'll get the output:
// "First class"
// "First class"
}
In this example, if the SecondClass would be instantiated before the FirstClass, the output would be completely different. But imagine this behaviour in a big app. Sometimes it would be very hard to understand what value would be in what moment there. I sympathise for the idea of creating lazy singletons with arguments. But I don't think that we should incorporate functionality that can lead to potential bugs. Maybe you have any solution to the problem and I'm just overreacting? Please share your thoughts. |
But surely those lazy singletons should be singletons for those specific parameters. So it should work correctly. This would allow you to introduce "scoped" factories. Where you register a singleton within a specific scope. When that scope is destroyed - all instances in that scope are destroyed. |
I don't see really the sense in allowing it for the lazy singleton as its a singleton, so not multiple instances. |
So I would go for getIt.get<T>( {String instanceName, dynamic arg}); |
So for example imagine I have interface for |
If you need two of the same time you can use named registration. |
But with the way I am suggesting you can remove the need for "named". In essence exchange String for an actual meaningful type. Would that not be better? Seems like with named you already have a way of registering multiple instances of a singleton. Why is that supported but you also say:
I'm probably missing something here |
Identifiying a singleton by a combination of its own and its parameters does not feel right for me. |
OK then there's no point in arguing really. I'm happy if this issue is closed. |
Oh I overread your comment that you want to add a scope to a singleton registration. |
I don't say that I'm against parameters. Actually especially for flutter this would enable to pass a BuildContext to a factory. I'm just not sure how it would fit to the singletons, but maybe I just miss something here. |
Ideally a scope to any instance. The way I would like to register instances is this:
Technically everything can be lazy (but maybe a parameter Regarding parameters - yes I think you should add support for them at least somewhere. I do believe that singletons should also support that, but it's a separate question - that's why I leave closing this issue to you - as this issue seems like 3 issues:
|
Scoping will have to be good through through. Parameter support I will include in the next release |
Included in #46 |
I would like to register factory that takes a parameter and use GetIt to get instance with parameter.
Hope it makes sense. If you want more info I can provide.
The text was updated successfully, but these errors were encountered: