Flexible Dependency Injection designed specifically for the Unity Engine. This lightweight and intuitive framework is designed to seamlessly integrate with Unity's native workflow, providing a straightforward approach to managing dependencies in your game.
The framework is heavily inspired by Zenject but reduces its complexity.
Open the Package Manager in Unity and choose Add package from git URL, then enter:
https://github.com/rehavvk/service-injection.git
from the Add package from git URL option.
To enable the service injection, you need to add a ProjectContext to your project.
-
The context menu entry
Create > Service Injection > Project Contextin the project window will create a new one for you. Move it somewhere in a Resources folder. This one is used loaded once when a scene with aSceneContextin it is loaded. -
Create a new Boostrapper class derived from
MonoBootstrapperand add it to the createdProjectContextas component. Drag your bootstrapper in the bootstrapper array on theProjectContext. -
Create a new
SceneContextin your desired scene. The context menu entryCreate > Service Injection > Project Contextin the inspector will create a new one for you. A scene without aSceneContextwill not start the bootstrapping if loaded as first scene. But such a scene would still have access to the registered services if theProjectContextwas initialized once.
You are ready to add bootstrapping to your newly created bootstrapper.
The bootstrapping is separated into multiple phases.
- Register Phase > Register how dependencies are resolved.
- Boot Completed Phase > Do other bootstrapping steps which require dependencies to be registered. From now on it is possible to resolve dependencies from the
ServiceLocator. - Resolve Phase > All registered and created instances receive their resolved dependencies.
βΉοΈ The system sorts the registered types by their dependencies to ensure all dependencies are created before they are resolved.
All Bootstrappers have to implement at least the Boot() to do registration. The OnBootCompleted() is optional.
public class MyBootstrapper : MonoBootstrapper
{
[SerializeField] private MyService myService;
protected override void Boot()
{
Register<MyService>()
.FromInstance(myService);
}
}You always have to define the contract type of the registration. The concrete type is optional. So you are able to define that other parts of your project have a inconcrete dependency like an interface or abstract class and define in some bootstrapper what kind of object is passed to them.
The framework provides different ways to register your resolvations and follows a builder pattern.
Register
If no dependency source is provided, the bootstrapping will create an instance of the concrete type as soon as the registration is completed.
protected override void Boot()
{
// Register concrete only. Concrete type is contract type too.
Register<MyService>();
// Register contract type and concrete type.
Register<IMyService, MyService>();
}Register Instance
Register a specific instance which is used to resolve the dependency.
[SerializeField] private MyService myService;
protected override void Boot()
{
Register<IMyService>()
.FromInstance(myService);
OR
RegisterInstance<IMyService>(myService);
}Register Factory
Register a factory which is used to resolve the dependency.
[SerializeField] private MyService myService;
protected override void Boot()
{
Register<IMyService>()
.FromFactory(() => new MyService());
OR
RegisterFactory<IMyService>(() => new MyService());
}By default, the dependency is resolved once and cached for later resolves.
As Singleton
You can clarify it by adding the builder method.
[SerializeField] private MyService myService;
protected override void Boot()
{
Register<IMyService, MyService>()
.AsSingleton();
}As Transient
You can alter this behavior so that a new resolvation is done when resolving this dependency.
[SerializeField] private MyService myService;
protected override void Boot()
{
Register<IMyService, MyService>()
.AsTransient();
}By default, all dependencies are available from their time of registration until the application is closed.
Global Scoped
You can clarify it by adding the builder method.
[SerializeField] private MyService myService;
protected override void Boot()
{
Register<IMyService, MyService>()
.GlobalScoped();
}Scene Scoped
You can alter this behavior so that the registration only is valid as long the scene the bootstrapper was executed in is loaded.
[SerializeField] private MyService myService;
protected override void Boot()
{
Register<IMyService, MyService>()
.SceneScoped();
}When an instance is created via ServiceLocator.CreateInstance<T>() or ResolveDependencies<T>(T instance) is called on it,
all types in their constructor are tried to get resolved from the injection registry.
public class MyExample
{
public MyExample(IMyService myService)
{
}
}In MonoBehaviours or where ever you like you can resolve dependencies by the Resolve method.
private MyService myService;
private void Awake()
{
myService = this.Resolve<IMyService>();
}When an instance is created via ServiceLocator.CreateInstance<T>() or ResolveDependencies<T>(T instance) is called on it,
all fields and properties with the [Inject] attribute are tried to get resolved from the injection registry.
[Inject]
private MyService myService;With Arguments
You can pass more non-registry parameters for resolving dependencies of an injection registration. They have priority over registry entries and get resolved by their type.
protected override void Boot()
{
Register<IMyService, MyService>()
.WithArguments("Test", true);
}
public class MyService : IMyService
{
public MyService(string name, bool isActive)
{
}
}With Callback
You can add a callback to do stuff as soon as the dependency instance is created for the first time.
[SerializeField] private MyService myService;
protected override void Boot()
{
RegisterInstance<IMyService>(myService)
.WithCallback((MyService myService) =>
{
myService.Init();
});
}Happy resolving with the Unity Service Injection Framework!