Models

Any object can be used as a model. Those objects should be as simple as possible and are therefore considered POCOs (Plain Old CLR Objects). Ideally, they even consist only of data, without much or any functionality, in which case they can be considered DTOs (Data Transfer Objects), as they only carry data between the different layers of your app (i.e.: from the network or disk). They might sometimes require special attributes for deserializing from JSON or XML and might have to make design trade-offs in order to be compatible with those serialization methods; that’s all right, as ViewModels will wrap those objects to provide a friendlier API for Views.

ViewModels

ViewModel classes must inherit from ViewModel<TModel>, where TModel is the type of the Model it wraps, and must also provide a public constructor with at least one parameter of type TModel, into which the model will be injected. Other dependencies, such as Services, can also be injected via other parameters of the constructor (this is what allows the ViewModel to interact with the rest of the application and the outside world).

The base class defines a handy Model property of type TModel, which you can use to access the underlying model’s data.

Views

View classes must inherit from View<TViewModel>, where TViewModel is the type of the ViewModel it wraps. Views are MonoBehaviours that you must attach to some GameObjects in your view Prefabs and, as such, cannot benefit from constructor injection like ViewModels do. They must instead rely on property injection.

The base class defines a handy ViewModel property of type TViewModel, which you can use to bind data to your UI GameObjects.

Typically, all views override the Load() method, which is where binding are made and where resources are loaded asynchronously:

public class FooView : View<FooViewModel>
{
  public Image Photo; // Must be assigned an Image game object in Inspector window
  public Text Title;  //  "          "    a Text          "           "
  
  public override IObservable<Unit> Load()
  {
    Bind(Title, ViewModel.Title);
    return BindAsync(Photo, ViewModel.PhotoUri); // View will be presented only *after* photo is finished loading.
  }
}

If no asynchronous binding are performed in the Load() method (or if you want the view to be presented immediately, without waiting for its content to load first), just return Observable.Empty<Unit> or null, like in the following example, where the view will be presented immediately, and the image it contains will load asynchronously and appear in a second time:

  public override IObservable<Unit> Load()
  {
    Bind(Title, ViewModel.Title);
    Bind(Photo, ViewModel.PhotoUri);
    return null; // View will be presented immediately, without waiting for photo to load.
  }

Prefabs

You define the visual representation of your views as simple prefabs. On the root game object of that prefab you must attach a view-derived mono behaviour that will be the link between your UI and code. In that prefab you create your layout and place different UI objects. You then drag your various UI objects onto the properties of the view in the Inspector window, in order to make them accessible to your view’s code.

All your view prefabs must be contained within your resources or asset bundles, so that they may be loaded at run-time (more on that in the Manifest section).

Tags: