VContainer
VContainer copied to clipboard
[Inject] called after OnEnable
The following code works in Zenject, but not in VContainer:
[Inject]
private void Construct(IRepository repository)
_repository = repository;
private void OnEnable() =>
_repository.Add(item); // VContainer: NRE
private void OnDisable() =>
_repository.Remove(item);
Execution order of VContainer:
Awake
OnEnable
Construct
Execution order of Zenject:
Awake
Construct
OnEnable
VContainer uses the call order according to the unity documentation, but it is not convenient. It also makes it difficult to switch from Zenject.
To get around this, Zenject uses a hack: https://github.com/modesttree/Zenject/blob/31f06cf81043f04301b1072ec81922b64149ea34/UnityProject/Assets/Plugins/Zenject/Source/Main/DiContainer.cs#L1680
@YegorStepanov I made my own version of this hack as extension method:
public static GameObject InstantiateAndInject([NotNull] this IObjectResolver resolver, [NotNull] GameObject prefab, Transform parent = null)
{
if (prefab == null)
throw new NullReferenceException(nameof(prefab));
var prefabActive = prefab.activeSelf;
prefab.SetActive(false);
var instance = resolver.Instantiate(prefab, parent);
prefab.SetActive(prefabActive);
instance.SetActive(prefabActive);
return instance;
}
I have a few other versions as well
public static GameObject InstantiateWithScope([NotNull] this LifetimeScope scope, [NotNull] GameObject prefab, Transform parent = null)
{
if (prefab == null)
throw new NullReferenceException(nameof(prefab));
var prefabActive = prefab.activeSelf;
prefab.SetActive(false);
using var disposable = LifetimeScope.EnqueueParent(scope);
var instance = Object.Instantiate(prefab, parent);
try
{
if (!instance.TryGetComponent<LifetimeScope>(out _))
scope.Container.InjectGameObject(instance);
}
finally
{
instance.SetActive(prefabActive);
prefab.SetActive(prefabActive);
}
return instance;
}
public static T InstantiateWithScope<T>([NotNull] this LifetimeScope scope, [NotNull] T prefab, Transform parent = null)
where T : Component
{
if (prefab == null)
throw new NullReferenceException(nameof(prefab));
var prefabActive = prefab.gameObject.activeSelf;
prefab.gameObject.SetActive(false);
using var disposable = LifetimeScope.EnqueueParent(scope);
var instance = Object.Instantiate(prefab, parent);
prefab.gameObject.SetActive(prefabActive);
try
{
if (!instance.TryGetComponent<LifetimeScope>(out _))
scope.Container.InjectGameObject(instance.gameObject);
}
finally
{
instance.gameObject.SetActive(prefabActive);
}
return instance;
}
public static T InstantiateAndInject<T>(this IObjectResolver resolver, [NotNull] T prefab, Transform parent = null)
where T : Component
{
if (prefab == null)
throw new NullReferenceException(nameof(prefab));
var prefabActive = prefab.gameObject.activeSelf;
prefab.gameObject.SetActive(false);
var instance = resolver.Instantiate(prefab, parent);
instance.gameObject.SetActive(prefabActive);
prefab.gameObject.SetActive(prefabActive);
return instance;
}
public static T CreateComponentOnNewGameObjectAndInject<T>(this IObjectResolver resolver, string gameObjectName = null)
where T : Component
{
if (string.IsNullOrWhiteSpace(gameObjectName))
gameObjectName = typeof(T).Name;
var gameObject = new GameObject(gameObjectName);
gameObject.SetActive(false);
var component = gameObject.AddComponent<T>();
resolver.InjectGameObject(gameObject);
gameObject.SetActive(true);
return component;
}
public static RegistrationBuilder RegisterFromPrefabWithInject(this IContainerBuilder builder, GameObject prefab, Lifetime lifetime)
{
return builder.Register(Instantiate, lifetime);
GameObject Instantiate(IObjectResolver resolver) => resolver.InstantiateAndInject(prefab);
}
public static RegistrationBuilder RegisterFromPrefabWithInject<T>(this IContainerBuilder builder, T prefab, Lifetime lifetime)
where T : Component
{
return builder.Register(Instantiate, lifetime);
T Instantiate(IObjectResolver resolver) => resolver.InstantiateAndInject(prefab);
}
public static RegistrationBuilder RegisterFromPrefabWithInject(this IContainerBuilder builder, Func<IObjectResolver, GameObject> prefab, Lifetime lifetime)
{
return builder.Register(Instantiate, lifetime);
GameObject Instantiate(IObjectResolver resolver) => resolver.InstantiateAndInject(prefab(resolver));
}
public static RegistrationBuilder RegisterFromPrefabWithInject<T>(this IContainerBuilder builder, Func<IObjectResolver, T> prefab, Lifetime lifetime)
where T : Component
{
return builder.Register(Instantiate, lifetime);
T Instantiate(IObjectResolver resolver) => resolver.InstantiateAndInject(prefab(resolver));
}
They might need an update, I think there's too much boilerplate between this methods but you should get the gist, also I'm pretty sure the try-catch blocks arn't required
Thank you very much for the code!
It would be great if VСontainer did it itself.
I very much agree, it would be a very easy (yet important) fix, for people who lack the skill to dive into the VContainer code this behavior would feel very confusing and buggy.
@YegorStepanov I made my own version of this hack as extension method:
thanks for the example code @AlonTalmi. I have exactly the same issue when the spawn object from the factory method container makes a late injection.