Questions / ideas for get_it

Did you ever check out watch_it?

Yes. I used it for a long time. But, when things begin to grow, get_it get in the way (pun intended).

It seems Dart has no dependency injection framework available (which makes sense, given its lack of introspection, something that is really needed for DI to work, adding the slooooooooowness of build_runner to that).

Service locators works, but when you have a big project with a lot of dependencies (and those are scoped), well, things get ugly very fast. No dart library handles registration order or circular dependencies (I don’t expect the tool to fix that for me, but at least warn me “hey, you messed up”) I guess because Dart is very limiting as a language to build tools (lack of introspection, lack of runtime information (for instance, which interfaces this class implements?), etc.) You can’t even build a class given its type >.<

One thing I really miss about watch_it is the ability of rebuilding only when a property of a ChangeNotifier changes. But, after I saw this, it just drop my confidence in it: Gradual Performance Degradation using watchPropertyValue on ChangeNotifier Member · Issue #32 · escamoteur/watch_it · GitHub

It seems like the best feature is not used by the author… wait… are you the owner of watch_it? O.o

* awkward moment *

So, how is the weather?

* try to change subject *

Actually tat issue got fixed a while ago. And it does exactly what you want.

Good to know.

I also highly recommend using it together with flutter_command.

Hmmm… Nice package, it remembers me the actual MVVM Command from XAML (just a bit more complicated). I like it.

Would love to get more input about how we could improve the registration process.

My issue was the amount of choices. For some things, I must use registerSingletonAsync, so then it complains that another dependency is not async singleton, then I need to change that, then it broke things in other places… I always feel like I’m disarming a bomb using get_it =(

Also, when I read dependsOn, my mind reads “dependencies that this dependency depends on”: it would be smart enough to initialize stuff in the correct order, but it actually doesn’t use dependsOn whatsoever O.o I don’t get why. For instance:

GetIt.I.registerSingletonWithDependencies<Interface2>(
  () => Class2(GetIt.I<Interface1>()),
  dependsOn: [Interface1],
);

GetIt.I.registerSingleton<Interface1>(Class1());

This will fail, because, even if the first registration is a factory, it will fail because the second registration is not there! This doesn’t make any sense (reading the docs now, because I wrote the code above as an example, trying to remember what I did in my projects some months ago, I see that dependsOn is about initialization, not dependency). But, even so, if the first registration is a factory, it does NOT need to be instantiated in that time and give me an error. It should behave like registerLazySingleton. It’s not intuitive at all, and now I have another way to register singletons >.< Too much.

I come from C# world (since 2002), so, I like simplicity:

  1. You either register a singleton, a scoped or a transient (it’s about intent, not implementation details, it abstracts for me).

  2. Then, you don’t need to to anything, except to use it, as you would use in any other class.

For 1) to work, get_it should have to:

a) register dependencies in any order and do a topology search to resolve the dependencies of a dependency.

b) have a build method to do a) above (in which case, that isFinal property in scope would make much more sense).

c) Be compatible or be Flutter-friendly (i.e.: use the widget tree, not exclusively, but additional to working with Dart alone, which IS a plus). Why? To make scoped registration possible. A scoped registration in C# ASP.net is a singleton for the duration of request. For Flutter, a scoped registration would be a singleton during the duration of a widget present in the tree (i.e.: when the widget that register the scope is disposed, so is the scoped instance and the next resolve would be a new instance).

For 2) to work, we would need macros, unfortunately. Dart is not capable of injecting parameters in constructors or properties and/or methods in classes. I know there is a helper for get_it that uses build_runner, but it eventually gets in the way the same way get_it (I need to change the type of attribute to change the registration code and we get back to that issue about changing from a singleton to a singleton async and breaking other stuff.)

For me, tools need to be smart. If I am the one who needs to be smart, then I’m fucked, because I am not.

To get_it be smart, it would need to be simpler (a singleton is a singleton, you deal with dependencies of dependencies, initialization, disposing, if it needs initialization or not, etc.). But, changing it now would break it, so, it is impossible. And that resumes pretty much why I try to use as little packages possible… they always have some issue, and I’m pretty locked in what I chose to use =\

Programming, for me, is about patterns. Dependency injection is kinda like a philosophy: it’s a guide for something and it establishes some jargon: transient, singleton, scoped. It does not tell how to do that, it only tells you that those kind of dependencies exist and each one serve its own purpose. When the tool ask ME to do more than that, then, I’m working for the tool, and it should be the other way around: DI is an abstraction, get_it should be an implementation. But it is not! I need to be very careful and very aware of how it works so I can write code that satisfy the package needs and biases.

Am I making any sense?

1 Like

Too tired to reply today, will do tomorrow

Have been using get_it/watch_it + injectable for a while now and I must say my experience with these have been surprisingly smooth.

I can’t really relate to the discussion around dependsOn, since I’m using injectable, so the only thing I do is slap a @singleton annotation on my services/models and do dart run build_runner build.

From there I get a properly initialized container, or an error if one of the dependencies can’t be resolved in which case I go and fix it.

Basically everything I’d expect it to do, it does.

One thing I was confused about in get_it is the scopes idea, and I initially thought I’d never use it, because… you know… who needs such a thing in a DI container.

Turns out this is a killer feature specifically when your app grows in complexity and you want better control of which services and features are available depending on the current state of your app. Amazing.

Combining this with having async factories is another step up. As it allows to properly initialize my stateful models and avoid the nasty “isLoaded” state checks with the usual initState/didUpdateWidget/dispose dance.

When my widgets get to their build phase I can just watchIt() my models and not bother about anything else. The amount of boilerplate code this have saved me already is kind of wild.

Overall I really like the fact that my app’s model/service (stateful) layer is completely decoupled from the widget tree, while allowing me handle my state in a very safe way and expose it to widgets the way I need.

And having to work with a familiar pattern like DI definitely helps in getting onboarded quicker. Just have to accept the fact that the container is a global singleton, which is a bit unusual, but in the hindsight makes a lot of sense here.

2 Likes

Hey, thanks for the nice feedback. And yes, Scopes are super powerful. Still haven’t found the time to reply on the original question. Currently traveling home.

1 Like