Issue
A client component that derives from MonoBehaviour<T…>, does not receive any services via its Init method.
Cause #1: using Awake
One possible reason for this, is that an Awake method has been defined in the client’s class. If this is is done, it will prevent the Awake method in the base class from being called.
If the error originates from an Awake method, this is likely to be the source of the problem:
NullReferenceException: Object reference not set to an instance of an object MyComponent.Awake () (at Assets/Scripts/MyComponent.cs:12)
Solution: use OnAwake
To fix the issue, change the void Awake() definition to protected override void OnAwake().
❌ Wrong
public class MyClient : MonoBehaviour<MyService> { MyService myService; protected override void Init(MyService myService) => this.myService = myService; void Awake() => myService.DoSomething(); }
✅ Correct
public class MyClient : MonoBehaviour<MyService> { MyService myService; protected override void Init(MyService myService) => this.myService = myService; protected override void OnAwake() => myService.DoSomething(); }
The OnAwake method is called at the end of the Awake event for the component, after the component has received the objects that it depends on via its Init method.
Cause #2: Missing Initializer
One possible reason for this is that the component requires one or more objects that are not registered as services using the ServiceAttribute or by a Service Tag.
Components that derive from MonoBehaviour<T…> will only autonomously receive all the objects that they depend on, if all the objects are registered services that are accessible to the client.
Solution: Add Initializer
To fix the issue, generate an Initializer for the client component, and attach it to it. You will then be able to configure all the required objects that are not registered services using the Inspector window.
Cause #3: Script Execution Order Issue
If you are instantiating some services manually in code during initialization of a scene, or when transitioning between scenes, then it could be that the issue is that the clients initialization logic gets executed before your code that creates the services does.
Solution: InitOrder
To fix the issue, you can change the script execution order setting for the class that creates the services to be very early, before any clients or their initializers are loaded.
This can be done by adding the InitOrder attribute to the class. It has identical behaviour to the built-in DefaultExecutionOrder attribute, but its parameters offer some more guidance on how different execution order values relate to those of other types of components.
using Sisus.Init; using UnityEngine; [InitOrder(Category.ServiceInitializer, Order.VeryEarly)] public class ServicesLoader : MonoBehaviour { [SerializeField] GameObject servicesPrefab; void Awake() => Instantiate(servicesPrefab); }
Cause #4: Service Type Mismatch
One possible reason is that one of the client’s Init parameters’ types do not exactly match any of the types with which the service in question has been registered.
If you do not explicitly specify the type to use for registering a service with the [Service] attribute, then the concrete type of the service is used by default.
❌ Wrong
[Service] public class MyService : IService { } public class MyClient : MonoBehaviour<IMyService> { protected override void Init(IMyService myService) => ... }
Solution: Change Service Type
To fix the issue, you can pass the correct type to [Service] attribute as its first argument.
✅ Correct
[Service(typeof(IService))] public class MyService : IService { } public class MyClient : MonoBehaviour<IMyService> { protected override void Init(IMyService myService) => ... }
NOTE: You can add more than one Service attribute to a class, to register it as a service using multiple different types.
Cause #5: Invalid Service Type
One possible reason is that a service is registered using a type, which the registered class can not be cast to.
The service class type should always either exactly match the type using which it is registered, derive from that type (in the case of base classes), or implement that type (in the case of interfaces).
❌ Wrong
[Service(typeof(IService))] public class MyService { } public class MyClient : MonoBehaviour<IMyService> { protected override void Init(IMyService myService) => ... }
Solution: Make Service Type Valid
To fix the issue, change either the [Service] attribute’s type argument, or the definition of the service class, so that the service class becomes castable to the type used to register it.
✅ Correct
[Service(typeof(IService))] public class MyService : IService { } public class MyClient : MonoBehaviour<IMyService> { IMyService myService; protected override void Init(IMyService myService) => this.myService = myService; }
Cause #6: Service Not Accessible To Client
One possible reason is that a service that has been registered using a Service Tag or a Services component has been configured to not be accessible to the client, due to its location in the scene hierarchies.
Solution: Change Service Tag Availability
To change which clients can receive a service registered using a Service Tag, click on the tag in the Inspector, and select an Availability level for the service which makes it available to the client.
Solution #2: Change Services Availability
To change which clients can receive the services registered using a Services component, click on the For Clients dropdown in the Inspector, and select an Availability level for the services which makes them available to the client.
Cause #7: Wrapped Object Is Null
One possible reason is that the service is a plain old C# object wrapped by a Wrapper component, but the wrapped object instance has not been created yet.
Solution: [Serializable]
One way to solve the issue is by adding the [Serializable] attribute to the plain old C# object’s class. This way Unity will automatically create the instance for you during the deserialization phase.
[AddComponentMenu("Wrapper/Player")] public class PlayerComponent : Wrapper<Player> { } [Serializable] public class Player { }
Solution #2: Parameterless Constructor
Another way to solve the issue is by creating the plain old C# object in the wrapper’s constructor:
- Define a parameterless constructor in the Wrapper class.
- Create the plain old C# object.
- Pass the plain old C# object to the base constructor.
[AddComponentMenu("Wrapper/Player")] public class PlayerComponent : Wrapper<Player> { public PlayerComponent() : base(CreatePlayer()) { } private Player CreatePlayer() => new Player(); }
Solution #3: WrapperInitializer
If you need some services to be able to construct the plain old C# object, then you can generate a WrapperInitializer, and implement the CreateWrappedObject method, and attach it to your wrapper.
public class PlayerInitializer : WrapperInitializer<PlayerComponent, Player, IInputManager> { protected override Player CreateWrappedObject(IInputManager inputManager) => new Player(inputManager); }