Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

[RELEASED] Zenject Dependency Injection Framework

Discussion in 'Assets and Asset Store' started by eventropy, Jun 3, 2014.

  1. pretender

    pretender

    Joined:
    Mar 6, 2010
    Posts:
    815
    Ok here is the installer
    Code (CSharp):
    1.  
    2. using Zenject;
    3.  
    4. namespace Game.Race3D
    5. {
    6.     public class GameInstaller : MonoInstaller
    7.     {
    8.         private GameConfiguration _configuration;
    9.  
    10.         public override void InstallBindings()
    11.         {
    12.             Container.Bind<GameConfiguration>().FromInstance(GetComponent<GameConfiguration>()).AsSingle();
    13.  
    14.             Container.Bind<AudioController>().AsSingle();
    15.  
    16.             Container.Bind<GameStorage>().FromResource("GameStorage").AsSingle();
    17.             Container.Bind<IInitializable>().To<GameStorage>();
    18.  
    19.             Container.Bind<PoolController>().AsSingle();
    20.             Container.Bind<IInitializable>().To<PoolController>();
    21.  
    22.             InstallGameController();
    23.            
    24.             InstallUIController();
    25.            
    26.             InstallSignals();
    27.         }
    28.  
    29.         private void InstallSignals()
    30.         {
    31.             Container.BindSignal<OnGameInitializedSignal>();
    32.         }
    33.  
    34.         void InstallGameController()
    35.         {
    36.             Container.Bind<ITickable>().To<GameController>().AsSingle();
    37.             Container.Bind<IInitializable>().To<GameController>().AsSingle();
    38.             Container.Bind<GameController>().AsSingle();
    39.      
    40.             //Container.BindAllInterfacesAndSelf<GameController>().AsSingle();
    41.        
    42.             Container.Bind<INetwork>().To<NetworkController>().AsSingle();
    43.             Container.Bind<ILevel>().To<LevelManager>().AsSingle();        
    44.         }
    45.  
    46.         private void InstallUIController()
    47.         {
    48.             Container.BindAllInterfacesAndSelf<UI>().FromPrefabResource("UI/UI").WithGameObjectName("UI").AsSingle();
    49.             Container.Bind<IWindowTransition>().To<ScaleWindowTransition>().AsSingle();
    50.         }
    51.     }
    52.  
    53.     public class OnGameInitializedSignal : Signal<OnGameInitializedSignal>{}
    54. }
    55.  
    Also i have some few other issues, there are times when Initialize method is not called even though i bound interface, for example i have BindAllInterfacesAndSelf<UI> on UI controller that is loaded from resource but Initialize method is never called. I inject it into game controller and call it from there and then it works.

    Also I am beginner with zenject and i am not doing some things properly i guess, if you have suggestions looking at this code let me know! thanks!
     
  2. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    I think the problem that's causing all these issues is the way you are calling BindAllInterfacesAndSelf.

    It should always be of this format:

    Container.BindAllInterfacesAndSelf<Foo>().To<Foo>().etc.

    You need to add the To<Foo> since otherwise each interface will be bound separately. This is an extremely common mistake that beginners make. This makes sense once you understand the way Zenject works (that is, that there is always a "left side" and a "right side" to every binding) but always trips people up. I think I might change this next version even though it makes sense, since it's such a extremely common mistake
     
  3. Maeslezo

    Maeslezo

    Joined:
    Jun 16, 2015
    Posts:
    93
    EDIT: Forget about this. Our mistake ;)

    Hello,

    I have a very weird problem.

    I have had to change a method from one part to other (neither zenject code nor injection). Somehow, very far from this point, in another script, that script is not injected. Just this one. If I change the other method from its original position, the second one is injected again.
    The injected variables are null, but the exception that has not been possible to inject is not thrown.

    I now it is very difficult to know the problem, and I would like to add more details, but it's all that I do and I know.
    Any clue will be very welcome.

    For compatibility reason, I am using right now version 3.7 :oops:
     
    Last edited: Nov 11, 2016
  4. pretender

    pretender

    Joined:
    Mar 6, 2010
    Posts:
    815
    Hey, thank you for explaining this, it is a rookie mistake :D. To be honest all is coming together quite nicely, the only thing left that i don't understand what is the best way to be used are project and scene context and how to use subcontainers.
    Documentation is not that clear, and there are no clear examples for beginners. Can you put here something very basic, for example how to create project context, scene context, what I would put where and how to use reusable containers for my example?

    It would help me and others who are starting to use Zenject. (next logical step is Projeny :D)
     
  5. MaDDoX

    MaDDoX

    Joined:
    Nov 10, 2009
    Posts:
    754
    Talking of beginners, I did a super-short video with the basic setup and injection process using Zenject. It shows a simple (and many times not the most effective or recommended) way for dynamic binding a field to the type of an existing MonoBehaviour in your scene. Link:

     
    eventropy likes this.
  6. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    111
    I downloaded Zenject from the Asset Store today and immediately started getting a bunch of errors:



    And here is the class where the errors are occurring (UnityEventManager.cs):



    What I noticed was that there is an error on lines 21 and 22, but not 23. And on line 23, there is a type declared for the Action, i.e. 'bool'. On the previous two lines, there isn't. If I add a type of 'bool' on the Action on line 21, it fixes that error. My question is, why isn't the type declared properly for all actions? I can go in there and add the type in myself, but I shouldn't have to do that. I would think once I download something from the Asset Store, it should just work.

    I use Unity 5.5.1f with MonoDevelop, on a Windows 10 machine.
     
  7. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    111
    There are even more errors, in addition to the ones above:

     
  8. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Hi Raidenwins. I'm not sure why that's happening for you. I just tried downloading it from asset store on that same version of Unity and am not seeing the issue. Can you try it in an empty project?
     
  9. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    111
    I've been having problems with .NET Framework 3.5 ever since I installed Unity 5.5.1f. That is a very likely cause of the issue. I am trying to re-install it now to see if it'll fix this issue (among others).
     
  10. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Updated Zenject to version 5.0

    Big features in this release include MemoryPools and the Lazy<> construct for lazy resolve. Also includes more changes to make it more accessible to beginner.
     
  11. mespino

    mespino

    Joined:
    Nov 26, 2015
    Posts:
    15
    Hello @eventropy,

    We are using Zenject 4.7

    We have noticed that the scenes that we load with ZenjectSceneLoader.LoadSceneAsync(name, Additive) are not injected.
    I mean, we have a gameobject with a Monobehavior in the scene we are loading. The monobehaviour has an injection, and the injection is not working (not been called)

    I know that it is imposible that you know the problem, but,
    • In a normal behaviour, this monobehaviour should be injected, right?
    • Where do you start searching the problem?

    Other question, a little offtopic,

    Formerly, a gameobject was only injected if it was a child of the Scene Context. This is not longer like this, isn't it? Can I force this behaviour somehow? I have a big scene and I don't really want that all the gameobjects are checked

    Regards.
     
    Last edited: Mar 7, 2017
  12. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Does your new scene have a SceneContext in it? If not, you need to add one, even if you don't have installers there.

    Currently, it will inject on all game objects in the scene. In order to inject only on children of SceneContext you'd have to change the code a bit (there isn't a flag for this). Take a look at SceneContext.GetInjectableMonoBehaviours and change that to just call ZenUtilInternal.GetInjectableMonoBehaviours with the SceneContext gameobject. By the way, the new version of zenject is a bit more efficient in terms of avoiding the startup costs of injecting on scenes with many transforms
     
    mespino likes this.
  13. mespino

    mespino

    Joined:
    Nov 26, 2015
    Posts:
    15
    It was that, there was not SceneContext in the new scene. Moreover, I had to change _zenjectSceneLoader.LoadSceneAsync(sceneName, loadSceneMode); by
    _zenjectSceneLoader.LoadSceneAsync(sceneName, loadSceneMode, null, LoadSceneRelationship.Child);

    Regarding what gameobject could be injected, Ok, I will check the code you mention.

    In the future, it could be great that you can select in the scene context what gameobject should be checked, {All, childrens, array }, for example.

    Anyway, thank you very much!!
     
  14. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    No problem. Can you add a github issue for that? That way it's a feature we can consider adding next time we have some more time
     
  15. mespino

    mespino

    Joined:
    Nov 26, 2015
    Posts:
    15
    Done! ;-)
     
    eventropy likes this.
  16. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    111
    Hi @eventropy,

    I am no longer getting the errors I was getting above so I am now back to using Zenject! I am trying to use method injection, but I am not having any success, even though I've read through the documentation and looked at the examples included with Zenject. The class I am trying to inject into is a MonoBehaviour and looks like this:

    Code (CSharp):
    1. public class MyClass : MonoBehaviour
    2. {
    3.     private Animator _animator;
    4.  
    5.     [Inject]
    6.     public void Initialize(Animator animator)
    7.     {
    8.         _animator = animator;
    9.     }
    10.  
    11.     void Update()
    12.     {
    13.         _animator.GetCurrentAnimatorStateInfo(0); // _animator IS NULL HERE
    14.     }
    15. }
    And my installer looks like this:

    Code (CSharp):
    1. public class MyInstaller : MonoInstaller<MyInstaller>
    2. {
    3.     public override void InstallBindings()
    4.     {
    5.         Container.Bind<MyClass>().AsSingle();
    6.         Container.Bind<Animator>().FromComponentInHierarchy();
    7.     }
    8. }
    The problem I am having is that method Initialize of class MyClass is never called and, because of that, the field _animator in my Update method is null. Why is that happening?

    And also, a question. The Animator instance I am trying to inject is a component in a game object in my scene. Let's call the game object in question CharacterA. There is another game object though, let's call it CharacterB, which also has a component Animator (which I don't need). How can I specify in my bindings above that I want the Animator component from game object CharacterA?
     
    Last edited: Mar 26, 2017
  17. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    @Raidenwins
    First thing I noticed is that you are binding MyClass AsSingle. This doesn't make sense for MonoBehaviour classes. This will default to using FromNew which means that Zenject will attempt to create the MonoBehaviour using "new MyClass()" which Unity doesn't allow. You need to use one of the "From" methods that applies to MonoBehaviours instead.

    The other thought I had is that, is anything actually referencing MyClass? If not, you might have to explicitly mark it non lazy by doing the following

    Container.Bind<MyClass>().FromNewComponentOnNewGameObject().AsSingle().NonLazy();
     
  18. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    111
    @eventropy Thanks for getting back to me. I am out of town right now. I'll have to get back to you once I am back.
     
  19. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    111
    To answer the question above, nothing references MyClass. It's just attached to a game object.

    After I changed the binding for MyClass as suggested above, its Initialize method is called and the Animator dependency is initialized properly. So that problem is solved, but now there is another issue. I need to add another dependency to MyClass, ClassA, which is also a MonoBehavior and which depends on yet another MonoBehavior, ClassB:

    Code (CSharp):
    1. public class ClassA : MonoBehaviour
    2. {
    3.     private ClassB _classB;
    4.  
    5.     // This never gets called
    6.     [Inject]
    7.     public void Initialize(ClassB classB)
    8.     {
    9.          _classB = classB;
    10.     }
    11.  
    12.     void Update()
    13.     {
    14.         _classB.DoSomething(); // ERROR HERE. _classB is null.
    15.     }
    16. }
    MyClass now looks like this:

    Code (CSharp):
    1. public class MyClass : MonoBehaviour
    2. {
    3.     private Animator _animator;
    4.     private ClassA _classA
    5.  
    6.     [Inject]
    7.     public void Initialize(Animator animator, ClassA classA)
    8.     {
    9.         _animator = animator;
    10.         _classA = classA;
    11.     }
    12.  
    13.     void Update()
    14.     {
    15.         _animator.GetCurrentAnimatorStateInfo(0);
    16.  
    17.         _classA.DoSomething();
    18.     }
    19. }
    And MyInstaller looks like this:

    Code (CSharp):
    1. public class MyInstaller : MonoInstaller<MyInstaller>
    2. {
    3.     public override void InstallBindings()
    4.     {
    5.         Container.Bind<MyClass> ().FromNewComponentOnNewGameObject().AsSingle().NonLazy();
    6.         Container.Bind<Animator> ().FromInstance (FindObjectOfType<Animator> ());
    7.         Container.Bind<ClassA> ().FromInstance (FindObjectOfType<ClassA>());
    8.         Container.Bind<ClassB> ().FromInstance (FindObjectOfType<ClassB>());
    9.     }
    10. }
    ClassA and ClassB are both attached to an empty game object. The problem is that, while the ClassA instance gets initialized fine in MyClass, the Initialize method of ClassA never gets called and, because of that, the ClassB instance is null. I've used a "From" construction method for ClassA and ClassB, so I am not sure what the problem is this time. What am I doing wrong here?
     
  20. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    It doesn't sound like you're doing anything wrong. ClassA and ClassB are both MonoBehaviours that you've added to the scene at edit-time I am assuming? That is, they aren't created dynamically somewhere?
     
  21. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    111
    Yes, that's correct. They are both added to an empty game object in the scene at edit-time.
     
  22. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Then they should definitely be injected so I can't really explain it. Can you provide an MRE (minimal reproducible example) that shows the issue? I'd be happy to take a look at that
     
  23. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    111
    I actually converted ClassA and ClassB into non-Mono Behaviour classes, inheriting from custom interfaces, and now everything works fine. In my installer class I have something like this:

    Code (CSharp):
    1. Container.Bind<MyClass> ().FromNewComponentOnNewGameObject().AsSingle().NonLazy();
    2. Container.Bind<Animator> ().FromInstance (FindObjectOfType<Animator> ());
    3. Container.Bind<IClassAInterface>()
    4.             .To<ClassA>()
    5.             .AsSingle();
    6. Container.Bind<IClassBInterface>()
    7.             .To<ClassB>()
    8.             .AsSingle();
    And now all the dependencies of ClassA and ClassB get injected into their constructors.

    Thanks for your help though. If I get stuck on something else, I'll make sure to post here again.
     
  24. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Updated zenject to Version 5.3.0 which includes:

    - Performance and memory optimizations. In particular, startup time for scenes with many transforms should be significantly reduced.
    - Added new bind methods FromNewComponentOnNewPrefab and FromNewComponentOnNewPrefabResource
    - Added new bind methods for FromSubContainerResolve() bindings: ByNewPrefabMethod, ByNewPrefabResourceMethod, ByNewPrefabInstaller, and ByNewPrefabResourceInstaller
    - Added support for having multiple "parent contract names" for the same scene
    - Added better support for binding open generic types to open generic derived classes
    - Added support for declaring collections of dependencies using IList<> instead of just List<>
    - Changed to skip analyzing unity types (ie. those inside UnityEngine namespace) to minimize reflection costs
    - Fixed AutoMocking to work by upgrading to newer Moq dll
    - Bug fixes
     
  25. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    111
    What is the best way to re-create a GameObject containing a MonoBehaviour script in which I have method injection, after the GameObject in question has been destroyed?

    To elaborate on the question, here are the particulars of the set-up I have:

    --a MonoBehaviour script, let's call it MyMonoBehaviour.

    --in MyMonoBehavior, I have a method Initialize with the Inject attribute, i.e. method injection, which injects a regular, non-MonoBehaviour class, let's call it MyClass, into MyMonoBehaviour.

    --MyMonoBehaviour is attached to a game object, let's call it MyGameObject, which is also a prefab.

    --in the Update method of MyMonoBehaviour I perform some actions and then I destroy the game object by doing the following:

    Code (CSharp):
    1. Destroy(gameObject);
    --afterwards, I create (in a different MonoBehaviour script) a new instance of MyGameObject like so:

    Code (CSharp):
    1. GameObject prefab = (GameObject)Instantiate (MyGameObject, location, rotate);
    The problem is that, in the MyMonoBehaviour component of the newly instantiated MyGameObject, MyClass is null.

    What is the best way to instantiate MyGameObject so instances of MyClass are properly injected?
     
  26. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Easiest way to do it would be to call `Container.InjectGameObject(instantiatedGameObject)`. This will inject all the fields/methods in all the monobehaviours that are attached to the game object or it's children. There might be a better design for what you're doing but hard to suggest anything else with knowing more about the bigger picture

    Edit: Changed Inject() to InjectGameObject() which is what I meant
     
    Raidenwins likes this.
  27. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    111
    That seems to work well. A related question though, what is the best way to obtain the DI Container? What I am doing is this (I saw it somewhere in the Source folder under Zenject in Unity), but I don't know that's the best practice:

    Code (CSharp):
    1. private SceneContext _sceneContext;
    2.  
    3. void Start ()
    4. {
    5.     _sceneContext = GameObject.Find ("SceneContext").GetComponent<SceneContext> ();
    6. }
    7.  
    8. void MyMethod()
    9. {
    10.     _sceneContext.Container.InjectGameObject (myGameObject);
    11. }
    So basically, obtain a reference to the SceneContext component and from there access its "Container" property.
     
  28. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    No, that is not good practice. The DiContainer is always added to itself, so the best way is to just inject it into your class constructor or [Inject] method. Where did you see GameObject.Find("SceneContext")? I don't think we're doing that anywhere in the zenject source.
     
  29. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    111
    That works well too! The first time I needed to use the DiContainer I didn't know it was always added to itself, so in trying to figure out how to get a reference to it, I searched through the Zenject samples and source. I couldn't find it in the samples so I just did a text search for DiContainer (or Container) and one of the things that came up was this, in ZenjectIntegrationTestFixture:

    Code (CSharp):
    1. SceneContext _sceneContext;
    2.  
    3.         bool _hasStarted;
    4.         bool _isValidating;
    5.  
    6.         protected DiContainer Container
    7.         {
    8.             get { return _sceneContext.Container; }
    9.         }
    This gave me the idea to access the DiContainer from the scene context and from there, knowing the scene context was a component in my scene, I obtained a reference to it, just like other components. In retrospect, that was not a good way of doing it, but you know, live and learn.
     
    Last edited: Oct 1, 2017
  30. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Yeah I don't know if that's mentioned in the docs. It probably should be. Usually though it's bad practice to directly reference the DiContainer except for factories and installers. Usually there's another way to do it that's cleaner.

    SceneContext is also added to the DiContainer by the way, if you need it for some other reason.
     
    Raidenwins likes this.
  31. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    111
    That's good to know. Thanks.

    Since I am bugging you with questions anyway, having figured out a way to resolve the issue above, I am now trying to use a Project Context. I added it to my project, as per the Zenject documentation, and then I added one installer to it. It works fine in my first scene, but when I load a second scene from the first one, the binding (I only have one right now) in my Project Context is null in scene #2. I do have Scene Contexts in both scene #1 and scene #2. What could be the problem here?
     
  32. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    You mean that the binding you added to your ProjectContext gets injected properly in scene1 but in scene2 it gets injected as null?
     
  33. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    111
    Exactly. Specifically, the installer I have attached to my Project Context looks like this:

    Code (CSharp):
    1. public class GlobalInstaller : MonoInstaller<GlobalInstaller>
    2. {
    3.     public override void InstallBindings()
    4.     {
    5.         Container.Bind<InputHandler> ().FromInstance (FindObjectOfType<InputHandler> ());
    6.     }
    7. }
    Where InputHandler is a MonoBehaviour attached to empty game objects in scene #1 and scene #2. In both scenes I have MonoBehaviours with an Inject method that looks like this:

    Code (CSharp):
    1. private InputHandler _inputHandler;
    2.  
    3. [Inject]
    4. public void Initialize(InputHandler input)
    5. {
    6.  
    7.     _inputHandler = input;
    8. }
    In scene #1 the input argument has a valid value while in scene #2 it's null
     
  34. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    The issue is that the installers on ProjectContext are only run once - before scene1 starts. I would suggest placing those bindings in a scene installer instead.

    You could also do this in your project context:

    Container.Bind<InputHandler>().FromComponentInHierarchy().CopyIntoAllSubContainers()

    Which should work, but a bit of a strange set up compared to putting scene reference bindings inside scene installers. By the way, you can also use FromComponentInHierarchy instead of FindObjectByType
     
  35. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    111
    Thanks for the tip there!

    What I found out is that if I use what you suggested above in my GlobalInstaller, the binding is available in scene #2 as well.

    While that works, my other finding was that if I want a binding declared in an installer attached to a Project Context to be available in a certain scene, not only must there be a Scene Context in that scene, but there has to be some installer attached to it. This is kind of a problem, because if I have a scene in which I only need bindings from the Project Context, I would then need to create a dummy installer to attach to that scene's Scene Context, just to make the Project Context binding available to it. Any way around that?
     
    Last edited: Oct 25, 2017
  36. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Are you sure? I just ran a test and found that adding an empty SceneContext will correctly propagate the projectcontext bindings.

    I don't think there's an easy way to inject into the scene without having at least an empty SceneContext in there though.
     
  37. pretender

    pretender

    Joined:
    Mar 6, 2010
    Posts:
    815
    Can I use latest Zenject with unity 5.4.3? I am stuck because of the framework we are using in the company.
    It says on the asset store that it requires Unity 2017 and when I download and import latest Zenject it says NUnit is missing...if I can't use it with Unity 5.4.3 what is the Zenject version that I can use safely?
     
  38. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    111
    Sorry for the late response. Yes, I just tested it again and got the same errors. My set up is the following:
    • a ProjectContext, located in the Resources folder, with three installers, let's call them Installer1, Installer2, and Installer3.
    • a scene with an empty SceneContext.
    • an Initialize method marked with the [Inject] attribute, in a MonoBehaviour script.
    When I run the scene, the Initialize method is not called and all the dependencies for which I have declared bindings in Installer1, Installer2, and Installer3 are null (i.e., I get NullReferenceException for all of them). As soon as I add an installer to the empty SceneContext in the scene, my Initialize method is called, dependencies are initialized properly, and everything works fine. Tested it a couple of times and got the same result both times.

    I may not have the latest version of Zenject from the Asset story. I am not sure if that would make a difference.
     
  39. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    It's probably a bug with the older version of zenject you are using. I just tested it with the latest and worked correctly (inject method was called even with an empty scene context)
     
  40. xjjon

    xjjon

    Joined:
    Apr 15, 2016
    Posts:
    167
    Thanks for Zenject - the DI is working great.

    How can I mock a factory for a test?

    I am writing a unit test for spawner and want to Verify that Create is called.
     
  41. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    You shouldn't need to mock a factory, you can just use a different binding for it. For eg:

    Container.BindFactory<Foo, Foo.Factory>().FromMock()

    Or

    Container.BindFactory<IFoo, IFooFactory>().To<FooMock>().AsSingle()
     
  42. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    111
    What is the best practice for injecting dependencies into a MonoBehavior, which is initially disabled when the scene starts up and is enabled at some point later by another MonoBehavior?

    To illustrate my question, here is the set-up I am referring to:

    • in a scene, I have an empty game object called MyEmptyGameObject.
    • there are two MonoBehaviours attached to the empty game object above, MonoBehaviour1 and MonoBehaviour2.
    • MonoBehaviour2 is disabled by default.
    • MonoBehaviour2 depends on an implementation of interface IMyDependency, which is injected via method injection (in method Initialize).
    • when the scene starts, a breakpoint in method MonoBehaviour2.Initialize() is hit and a valid value for the dependency IMyDependency is passed.
    • at some point later, in MonoBehaviour1, MonoBehaviour2 is enabled like so:
      Code (CSharp):
      1. GameObject.Find("MyEmptyGameObject").AddComponent<MonoBehaviour2>();
    • at this point, a NullReferenceException is thrown in MonoBehaviour2 and the IMyDependency dependency is null.

    The relevant code in MonoBehaviour2 is the following:

    Code (CSharp):
    1. private IMyDependency _myDependency;
    2.  
    3. [Inject]
    4. public void Initialize(IMyDependency myDependency)
    5. {
    6.     // This is hit on scene start up with a valid value for myDependency.
    7.     _myDependency = myDependency;
    8. }
    9.  
    10. private void doSomeWork()
    11. {
    12.     _myDependency.DoSomething(); // NullReferenceException here
    13. }
    What is the problem here? According to the Zenject documentation injection can be performed in the Start() or Awake() methods of a MonoBehaviour, but I am not quite sure how to do that.
     
  43. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Calling AddComponent<X> is similar to calling new X in that Zenject will not run inject in that case. In order to get Zenject to perform injection on dynamically created objects you need to always create everything through factories or directly through the container. So in this case, you could call `_container.InstantiateComponent` instead of AddComponent to ensure injection happens
     
  44. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    111
    Does Zenject support injection into static classes?
     
  45. boysenberry

    boysenberry

    Joined:
    Jul 28, 2014
    Posts:
    335
    I figured I would share a solution to a problem I had with the group, since I asked here, but solved it before I got a response.

    I decided instead of using separate MonoBehaviors to embed TextAssets I needed in a IInitializable class, I'd just use the MonoInstaller I was binding everything up with. I couldn't find any examples doing exactly what I wanted, but figured it all out after reading a post above about newbie errors and MonoBehavior binders needing to use From binders to avoid the new constructor issues while instancing MonoBehavoir derived classes.

    So here is an example of my solution:
    Code (csharp):
    1.  
    2. public class MyInstaller : MonoInstaller<MyInstaller>
    3. {
    4.     [SerializeField]
    5.     public List<TextAsset> SQLiteDBS = new List<TextAsset>();
    6.  
    7.     public override void InstallBindings()
    8.     {
    9.         Container.BindInterfacesAndSelfTo<MyInstaller>().FromInstance(this);
    10.     }
    11. }
    12.  
    13. public class MyClass : IInitializable
    14. {
    15.     [Inject]
    16.     private readonly MyInstaller Installer;
    17.  
    18.     public void Initialize()
    19.     {
    20.         foreach (TextAsset db in Installer.SQLiteDBS)
    21.         {
    22.             Debug.Log("db: " + db.name);
    23.         }
    24.     }
    25. }
    26.  
    I hope it help someone else as well. ;)
     
  46. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Have you read this section of the docs on settings? An approach like that would probably be easier than directly referencing the MonoInstallers from your code. That would also probably be better to avoid being tightly coupled to the installer code

    Personally what I like to do is use Scriptable Object Installers for all settings (including settings for installers)
     
    boysenberry likes this.
  47. boysenberry

    boysenberry

    Joined:
    Jul 28, 2014
    Posts:
    335
    That's definitely a lot more elegant, thank you!

    I did read it before, but didn't really understand what it was doing. Now I understand it though, making the bound class build with the settings passed in that are exposed on the MonoInstaller.

    To make it even less tightly coupled I could probably use interfaces as well.
    Then there is the ScriptableObjectInstaller as well, hehe, that's probably what I want (as you suggested). :)
     
    Last edited: Mar 8, 2018
  48. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    111
    Is there a way to inject a MonoBehaviour class that also implements an interface? For example, something like this:


    Code (CSharp):
    1. public MyClass : MonoBehaviour, IMyInterface
    2. {
    3. }
    How would I inject this in another class?
     
  49. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    248
    Yes. The fact that it implements an interface does not affect whether it is injected. Is that not working?
     
    Last edited: Apr 15, 2018
  50. Raidenwins

    Raidenwins

    Joined:
    Dec 18, 2012
    Posts:
    111
    Nope. I tried the following and it didn't seem to work:

    Code (CSharp):
    1. Container.Bind<MySecondClass>().FromNewComponentOnNewGameObject().AsSingle().NonLazy();
    2.  
    3. Container.Bind<IMyInterface>()
    4.     .To<MyClass>()
    5.     .AsSingle()
    6.     .WhenInjectInto<MySecondClass>();
    Where MySecondClass is also a MonoBehaviour.