If you’re talking about View, it will of course be a MonoBehaviour class. Already this class has its own lifecycle callbacks called by the game engine, eg Start, Update etc. but this is not enough for us. In this section we will develop lifewcycle for View and ViewModel.

View Lifecycle

For example, let’s say View is responsible for the following operations.

  • Initialization process
  • Activating the current object
  • Display of current object, including localScale = Vector.one, alpha = 1 from 0
  • When the processes are finished, calling the relevant callbacks, OnCompleted or OnSuccess

As yet another example, let’s assume that the View is responsible for the following operations when hidden.

  • Hiding the current object, including localScale = Vector.zero and alpha = 0 from 1
  • Calling the corresponding callbacks when the View is hidden, OnCompleted or OnSuccess
  • Deactivating the current object
  • Destroy operation for the current object.

ViewModel Lifecycle

For View, things are not so complicated because View is only responsible for display. The ViewModel, on the other hand, is more complex. For example, at which stage the data will go to the database, which other places will receive data, these are not handled by the View. These are handled by the ViewModel. The ViewModel only needs to know at what stage of the View it will allow access to the data.

ViewModel must have lifecycle corresponding to View. These processes are listed below.

  • Initialization process
  • Related operations before view display
  • Operations after the display of the View
  • Operations before the view is hidden
  • View’s operations after hiding
  • Actions when destroying the View

Developing Lifecycle

A lifecycle should be developed for the View and the ViewModel with what we have obtained from the analysis we have done so far. The diagrammatic representation of this will be as follows:

MVVM Lifecycle

  • OnInitialize: Used by View. Thinking with previous learnings, this OnInitialize, OnBindingContextChanged event and attribute binding (Binder.Add) are called here.
  • OnAppear: It is used to activate the View.
  • OnReveal: Used to show the View. Animated or direct display.
  • OnRevealed: Additional operations are performed when the View is displayed.
  • OnHide: Called when the View is connected to hide.
  • OnHidden: Same as OnReveal. The hiding process is animated or direct.
  • OnDisapper: The job of deactivating the current object. OnDestroy: Destroy part that is also called for the ViewModel after the View is destroyed.

When we integrate this lifecycle into the UnityUIView we made earlier, it looks like this:

[RequireComponent(typeof(CanvasGroup))]
public abstract class UnityUIView<T>:MonoBehaviour,IView<T> where T:ViewModelBase
{
    private bool _isInitialized;
    public bool destroyOnHide;
    protected readonly PropertyBinder<T> Binder=new PropertyBinder<T>();
    public readonly BindableProperty<T> ViewModelProperty = new BindableProperty<T>();
    /// <summary>
    /// Return method after display
    /// </summary>
    public Action RevealedAction { get; set; }
    /// <summary>
    /// Return method after hiding
    /// </summary>
    public Action HiddenAction { get; set; }

    public T BindingContext
    {
        get { return ViewModelProperty.Value; }
        set
        {
            if (!_isInitialized)
            {
                OnInitialize();
                _isInitialized = true;
            }
            //trigger OnValueChanged
            ViewModelProperty.Value = value;
        }
    }

    public void Reveal(bool immediate = false, Action action = null)
    {
        if (action!=null)
        {
            RevealedAction += action;
        }
        OnAppear();
        OnReveal(immediate);
        OnRevealed();
    }

    public void Hide(bool immediate = false, Action action = null)
    {
        if (action!=null)
        {
            HiddenAction += action;
        }
        OnHide(immediate);
        OnHidden();
        OnDisappear();
    }

    /// <summary>
    /// Initialize the View, also execute when BindingContext changes
    /// </summary>
    protected virtual void OnInitialize()
    {
        //No matter how the Value of the ViewModel changes, only listen (bind) to the OnValueChanged event once
        ViewModelProperty.OnValueChanged += OnBindingContextChanged;
    }

    /// <summary>
    /// gameObject,Disable->Enable
    /// </summary>
    public virtual void OnAppear()
    {
        gameObject.SetActive(true);
        BindingContext.OnStartReveal();
    }
    /// <summary>
    /// Start showing
    /// </summary>
    /// <param name="immediate"></param>
    private void OnReveal(bool immediate)
    {
        if (immediate)
        {
            //Now, no animation
            transform.localScale = Vector3.one;
            GetComponent<CanvasGroup>().alpha = 1;
        }
        else
        {
            StartAnimatedReveal();
        }
    }
    /// <summary>
    /// alpha 0->1 execute after
    /// </summary>
    public virtual void OnRevealed()
    {
        BindingContext.OnFinishReveal();
        //Return function
        if (RevealedAction!=null)
        {
            RevealedAction();
        }
    }
  
    private void OnHide(bool immediate)
    {
        BindingContext.OnStartHide();
        if (immediate)
        {
            transform.localScale = Vector3.zero;
            GetComponent<CanvasGroup>().alpha = 0;
        }
        else
        {
            StartAnimatedHide();
        }
    }
    /// <summary>
    /// alpha 1->0
    /// </summary>
    public virtual void OnHidden()
    {
        //return function
        if (HiddenAction!=null)
        {
            HiddenAction();
        }
    }
    /// <summary>
    /// Enable->Disable
    /// </summary>
    public virtual void OnDisappear()
    {
        gameObject.SetActive(false);
        BindingContext.OnFinishHide();
        if (destroyOnHide)
        {
            //Destroy
            Destroy(this.gameObject);
        }

    }
    /// <summary>
    /// When the gameObject will be destroyed, this method is called
    /// </summary>
    public virtual void OnDestroy()
    {
        if (BindingContext.IsRevealed)
        {
            Hide(true);
        }
        BindingContext.OnDestory();
        BindingContext = null;
        ViewModelProperty.OnValueChanged = null;
    }

    /// <summary>
    /// scale:1,alpha:1
    /// </summary>
    protected virtual void StartAnimatedReveal()
    {
        var canvasGroup = GetComponent<CanvasGroup>();
        canvasGroup.interactable = false;
        transform.localScale = Vector3.one;

        canvasGroup.DOFade(1, 0.2f).SetDelay(0.2f).OnComplete(() =>
        {
            canvasGroup.interactable = true;
        });
    }
    /// <summary>
    /// alpha:0,scale:0
    /// </summary>
    protected virtual void StartAnimatedHide()
    {
        var canvasGroup = GetComponent<CanvasGroup>();
        canvasGroup.interactable = false;
        canvasGroup.DOFade(0, 0.2f).SetDelay(0.2f).OnComplete(() =>
        {
            transform.localScale = Vector3.zero;
            canvasGroup.interactable = true;
        });
    }
    /// <summary>
    /// The response method when the bound context changes
    /// using reflection to do this: +=/-=OnValuePropertyChanged
    /// </summary>
    private void OnBindingContextChanged(T oldValue, T newValue)
    {
        Binder.Unbind(oldValue);
        Binder.Bind(newValue);
    }
}

ViewModel’s lifecycle is much simpler, although it also does logic stuff that View can’t handle.

public virtual void OnStartReveal()
{
    IsRevealInProgress = true;
    //Initialize at the beginning of the display
    if (!_isInitialized)
    {
        OnInitialize();
        _isInitialized = true;
    }
}

public virtual void OnFinishReveal()
{
    IsRevealInProgress = false;
    IsRevealed = true;
}

public virtual void OnStartHide()
{
    IsHideInProgress = true;

}

public virtual void OnFinishHide()
{
    IsHideInProgress = false;
    IsRevealed = false;
}

public virtual void OnDestory()
{
    
}

It’s worth noting that all of the above methods are virtual because the subclass might want to customize some stuff.