Unreal Engine - The Type Container and MVVM(S) Architecture
MVVM, Services, and Dependency Injection... kind of
Introduction
The Model View View Model (MVVM) architecture is a software design pattern to separate the user interface from the business logic of an application. In game terms, business logic would be the state of the simulation. View Models often use a service, or multiple, to interact with an API to the backend of an application in what James Montemagno refers to as MVVMS in his video here.
In games it is common to have screens that have a one-to-one or one-to-many relationship with services. Examples are a player profile, loadouts or a store. Integrating the use of a Type Container with Unreal Engine’s View Models allows for the user interface to interact with these services in a decoupled and testable manner.
Example
Below is a simplified example of a common pattern for a ProfileService. The service acts as the API and the implementation requires a ProfileRepo which does the actual retrieving of the profile.








Note the Expose_TNameOf lines after the declaration of the Interfaces. This is necessary. If forgotten, the GetInstance calls in the Type Container will not work and crash the program.
The Type Container
Given the above service and repo, the concrete versions can be associated with, stored, and retrieved by their interface in a Type Container.
The Type Container provides four ways to register a concrete class with its interface, two of which are demonstrated here:
RegisterClass takes the interface type and the concrete type as template parameters. It also takes a variable number of template parameters if the concrete implementation takes constructor arguments (not shown here).
RegisterInstance takes the interface type as a template parameter and then a concrete instance of that interface as an argument.
RegisterFactory and RegisterDelegate are the additional two ways to register types. See TypeContainerTests.cpp for examples.
Given a Type Container, call GetInstance with a template parameter of the interface type to retrieve a concrete implementation. Depending on the register method used, the object returned will differ in the following ways:
RegisterClass will return a new insanitation on each call to GetInstance.
RegisterInstance will return the one instance that is passed in at registration time.
RegisterFactory and RegisterDelegate must return an instance of the interface type; however what they return is implementation specific.
Where to Instantiate a Type Container
There are many options as to where the Type Container can be instantiated. Some common options are:
A Subsystem (Engine, GameInstance, LocalPlayer, etc.)
On a custom Engine class
In a Module
In this example, the Module class for a new ServicesModule has been chosen as the object to create and populate the Type Container as shown below:


The pros of putting the Type Container in the Module are:
During a normal run of the editor or game, StartupModule and ShutdownModule will be called exactly once
There is no need for a World Context Object, unlike a GameInstanceSubsystem
The Type Container in Action
The following is an example of a View Model using the Type Container to get access to, storing, and using the Profile Service.


The asynchronous call to GetProfileAsync and usage of Promises and Futures is explained more here.
The View Model stores the Profile Service in a TSharedRef which is non nullable version of a TSharedPtr. This has the benefit of preventing null pointer exceptions and the need to check for validity throughout its use. Since it can’t be null, it must be initialized in the Constructor member initializer list, including the version that takes the FVTableHelper which will be auto-generated and cause compiler errors if not explicitly written.
Unfortunately, Unreal Engine does not have a way to call a custom constructor with parameters on any class that derives from UObject; therefore real dependency injection can not be achieved. The alternatives are:
What is shown here - in the constructors, get the object desired from a global Type Container. The tradeoffs of this option are:
Pro - Guaranteed a valid object throughout the lifetime of the owning View Model.
Con - This code will crash if using a Type Container declared in the same Module where the View Model lives. This is because the Class Default Object (CDO) will be created before the StartupModule function is called, and the Type Container will not yet be populated with the desired type.
Don’t store the service as a member variable in the class, but instead get it every time it is needed. The tradeoffs of this option are:
Pro - This removes the need to declare the constructors and destructor in the example above.
Pro - The Type Container always returns a TSharedRef, so a valid object is still guaranteed at access time.
Con - Paying for access each time the type is needed
Con - More difficult to inspect the dependencies of the View Model while debugging.
Below is a video of this code in action:
Conclusion
If a project has a use for the MVVMS architecture, it may not be as nice as other frameworks, but it is doable using Unreal Engine’s View Model plugin coupled with a Type Container to store and retrieve services.
Example code can be found here.
Example assets can be found here.