Dependency Injection with UI Toolkit

I’m still wrapping my head around UI Toolkit and so I’m wondering if I am approaching this problem from the wrong angle. I’ve created some custom controls by extending VisualElement and creating UxmlFactory and UxmlTraits classes, and now I’d like to inject some dependencies into my custom controls. I’m specifically using Zenject/Extenject and worrying about runtime controls, but I think this is more of a conceptual question.

My initial ideas were to create a UxmlFactory instance inside of the DI container and specify that as the factory for UIToolkit to use OR retrieve the existing factory instance and inject into it. Either way, the goal was to enable the factory to create all instances of my custom control in a DI-aware way. However, I think in order to do that I would need access to VisualElementFactoryRegistry, which is marked internal.

I think I can hack around this by foregoing custom controls entirely and just creating GOs with UIDocument components (‘fake’ custom controls) and a second component for DI, but that seems really ugly and like it would invalidate a lot of the reasons to use UI Toolkit in the first place. Is there a better approach that I am missing?

2 Likes

Hi ! I’m currently have some headache to trying to make a clean use of DI with UIToolkit…
Did you finaly find a solution that you like ?

Unfortunately no, I didn’t find a good solution. I haven’t touched this code in 6+ months, so maybe a better approach is available now. What I wound up doing as a temporary workaround was to hold references to my DiContainers in a static class, and in my UxmlFactory classes I would retrieve the DiContainer and then call Inject() on it, passing in the newly created VisualElement.

It’s a really messy approach and obviously negates some of the value of having DI, but it means my custom UI components are clean and DI-aware, and if/when a better approach is possible, it’s a matter of changing the UxmlFactories.

1 Like

I have the same problem. Has anyone found a good solution?

I’ve created a small package, UIComponents, that includes a dependency injection mechanism:

[Dependency(typeof(ICounterService), provide: typeof(CounterService))]
class MyComponent : UIComponent // UIComponent inherits from VisualElement
{
    private readonly ICounterService _counterService;
  
    public MyComponent()
    {
        // Will yield a CounterService.
        _counterService = Provide<ICounterService>();
    }
}
var container = new VisualElement();
container.Add(new MyComponent());

At the moment, it is limited. Dependencies are singletons (in this case, all classes that wish to receive CounterService get the same instance). You need to use interfaces. Your components must inherit from UIComponent. The system uses reflection behind the scenes. But if you’re not concerned about that, the package might be for you. It’s got a bunch of other features too, if you’re not interested in them then you could just rip out the dependency injection code and tailor it to your needs.

2 Likes

Thanks, this is really helpful. But what is the best way to integrate this with Zenject/Extenject?

I have never used Zenject before, but after reading through its documentation you should be able to use its Inject attribute if you’re using UIToolkit in runtime and you have your scene properly set up.

For editor windows, there seems to be a ZenjectEditorWindow class which sets up a DiContainer for you, you just need to implement the InstallBindings function. I don’t know if it works with the CreateGUI callback & UIToolkit, but it’s worth a shot.

1 Like