Login con MVP en Unity

Lalo Berro
10 min readSep 27, 2022

--

Hola Gente!

En esta guia vamos a estar aplicando lo visto en el post de MVP: https://medium.com/@laurencioberro/mvp-pattern-in-unity-c9631531caef

Les dejo el Git Hub Repository con todos los Sources:
https://github.com/LaloBerro/MVP-LoginExample

Dentro del Repository se encuestran dos carpetas, una con todo el Ejercicio resuelto y otra con los elementos basicos para poder seguir esta guia.

Que vamos a hacer

En este ejercio vamos a aplicar MVP en un login, para ello vamos a seguir los siguientes pasos:

  1. Crear un Unity Project y la scene de Login.
  2. Crear la estructura de folders que vamos a usar.
  3. Crear los scripts correspodientes al Dominio.
  4. Crear las Implementaciones Mocks de la capa de Gateways.
  5. Crear los scripts correspondientes al Presenter.
  6. Crear los scripts correspondientes a la View.
  7. Crear los Installers.
  8. Armar la escena y probarla.
  9. Crear los assemblies correspondientes.

Diagrama

Creando el Unity Project

Para esto puede descargarse el Repository que dejo arriba o empezar un project de cero en cualquier version de unity.

Dentro del Repository se encuentra una scene llamada Login Source, dentro hay un Canvas con dos inputFields uno para el Username y otro para la Password, y un boton para poder hacer login.

Creando la estructura de Folders

Aunque en un caso real no sea util crear las folders antes de codear si me parece lo correcto para esta guia asi se van dando una idea de en donde va cada cosa.

La estructura se veria asi:

Creando el Dominio

Entities

Primero vamos a crear las entities que vamos a necesitar, es este caso crearemos una clase llamada UserLoginData:

Fijense que tenemos un string para el Username y otro para el Password. Tambien que estan declaradas como readonly ya que a lo largo de la ejecucion los valores de las variables solo seran asignados por el constructor y accederemos a ellas a traves de una property.

Esta clase de Entity tambien es conocida como DTO (Data Transfer Object), esto significa que se encarga de contener data que va a ser traslada, ya sea dentro dentro del model o presenter y la caracteristica principal de un DTO es que se crea para su uso y se destruye cuando se termina de usar, entonces cada vez que los utilizamos creamos uno nuevo.

Para mas informacion sobre Entities y DTO les dejo este post que se explaya bastente en el tema:
https://www.linkedin.com/pulse/difference-between-entity-dto-what-use-instead-omar-ismail/?trk=pulse-article_more-articles_related-content-card

Una vez creado UserLoginData.cs nos quedaria asi en las folders:

UseCase

Ahora pasaremos a crear los UsesCases de nuestro login en donde se centrara el core de nuestro programa.

Primero creamos una interface llamada ILoginUseCase, esta contiene un metodo Login(UserLoginData), mas adelante esta interface sera la que se comunicara con el Presenter para que la pueda consumir:

Despues creamos una implementacion de ILoginUseCase llamada LoginUseCase, esta contiene una referencia a un IService<UserLoginData>, mas abajo explico de que se trata esta interface:

Por ultimo creamos una interface templatizada o generica llamada IService<DataType>, siendo DataType el type generico, esta interface va a ser implementada por las clases de Gateways para contener todas las llamadas al servidor:

Una vez tengamos esto listo nos quedarian las folders asi:

Gateways

Como explique antes los Gateways son implementaciones de IService<UserLoginData> que se conectan al servidor para poder hacer login. En nuestro caso no tenemos ningun server al que hacer login por lo que implementaremos Mocks que cumplan con las caracteristicas que necesitamos.

Creamos una clase llamada MockLoginService que hereda de IService<UserLoginData>, e implementamos nuestro substito de server, entonces este Mock recibe la data de LoginUseCase y comprueba si el username y password coincide con los que “Harcodeamos” en el mock, de ser asi printea “Login Success” sino “The password or the username is wrong”:

Una vez tengamos esto listo esto nos quedarian las folders asi:

Creando la capa de Presenter

Ahora crearemos los scripts de la Capa de Presenter, como pista sabemos que el ILoginUseCase necesita un UserLoginData entonces nuestro presenter tiene que encargarse de construirlo y pasarselo al ILoginUseCase.

Creamos una clase llamada LoginPresenter que tiene referencias a ILoginUseCase para poder pasarle el UserLoginData y una referencia a ILoginView a la cual nos subscribiremos a traves de un Action para que nos avise cuando el usuario clickeo el boton, ademas nos proporcionara el Username y Password ingresados por el usuario:

Ahora necesitamos crear la interface que mas adelante implementara la View, se llamara ILoginView:

Una vez todo esto creado las folders quedarian:

Creando la View

La View va a ser la encargada de obtener toda la data relacionada con el usuario a travez de elementos de la UI.

Creamos una clase llamada LoginView que implementa Monobehaviour e ILoginView, esta tendra las referencias a dos InputFields, uno para insertar el username y otro para la password, tambien tiene referencia a un Button que invocara el Action OnClickOnLogin cada vez que sea clickeado:

Creando esta clase las folders quedan:

Creando los Installers

Hasta ahora solo hemos creado clases e interfaces pero no hemos podido probar nada, por lo que ahora crearemos los Installers para que nuestro programa pueda inicializarse y funcionar. Ademas estos installers nos serviran para aplicar Dependecy injection manual.

Un Installer tiene la tarea de inicializar una clase, pero que pasa cuando una clase depende de otra para inicializarse, por ejemplo el caso de LoginUseCase si o si necesita una referencia a un ILoginPresenter, para solucionar esto vamos a crear un sistema de Installers que permita inicializar y pasar referencias a otros installers.

Empezamos creando una clase abstracta llamada MonoInstaller, esta hereda de Monobehaviour para poder usarla en el editor, y contiene un metodo Install() que sera el encargado de inicializar la clase que queremos, este metodo sera llamado externamente por una class que veremos mas adelante:

Luego creamos una clase abstracta llamada MonoInstallerGeneric, esta es una clase generica y TypeToInstall sera el type del cual queremos que los demas usen (si no entendiste muy bien esto no te preocupes, mas enseguida lo vas a entender con los ejemplos), esta clase contiene una property que sera la clase que inicializamos, esta hereda de MonoInstaller por que hace un override del metodo Install(), y tiene un metodo abstracto que debera implementar cada hijo dependiendo del tipo de dato que sea:

Luego creamos la class llamada MonoInstalator que sera la encargada de Instalar en orden los MonoInstallers que queramos, tambien hereda de MonoInstaller:

Por ultimo necesitamos iniciar de alguna forma estos installers, por que vamos a crear una class llamada MonoInstallerInitializer que inicializa un MonoInstaller en el awake:

Ahora que ya tenemos la base para poder inicializar las clases vamos a eso.

Primero vamos a crear todos los Installer:

View Installers

Vamos a crear un MonoInstallerGeneric para LoginView, entonces creamos una clase llamada LoginViewInstaller que hereda de MonoInstallerGeneric<ILoginView>, pero por que el dato generico es ILoginView y no LoginView, ya que lo que nosotros queremos es que las clases que necesitan esta referencia, como LoginPresenter, no necesitan la concrecion (LoginView) sino la abstraccion (ILoginView), de esta forma podemos implementar todos los ILoginView que queramos y solo tenemos que cambiar la refrencia al MonoInstaller:

Presenter Installers

Creamos el MonoInstallerGeneric para LoginPresenter, y fijense que tiene referencia a otros MonoInstallerGeneric, esto es para poder acceder a su class ya inicializada para poder inicializar LoginPresenter:

Model Installers

Creamos el MonoInstallerGeneric para LoginUse, notar que hereda de MonoInstallerGeneric<ILoginUseCase> y tiene referencia al installer del Service:

Ahora creamos el MonoInstallerGeneric para MockLoginService, y notar que hereda de MonoInstallerGeneric<IService<UserLoginData>> :

Con todo esto creado las folders quedarian:

Armando todo en unity

Ahora necesitamos armar todo en unity para que funcione

  1. Creamos un Empty Gameobject y lo llamamos Code.

2. Dentro de el creamos un Empty Gameobject lo llamamos Login.

3. Creamos un Empty Gameobject lo llamamos Login-MonoInstallerInitializer y le añadimos el component MonoInstallerInitializer.

4. Creamos otro Empty Gameobject lo llamamos Login-Instalator y le asignamos el component MonoInstalator.

5. Dentro de Login-MonoInstaller vamos creando un Gameobject y asignado cada MonoInstaller

View
LoginViewInstaller
MockLoginServiceInstaller
LoginUseCaseInstaller
LoginPresenterInstaller

6. Ahora arrastramos las referencias necesarias.

Para la view crear un Empty Gameobject llamado LoginView y asignarle componente de LoginView, ademas de arrastrarles las dependecias de la scene.

LoginView

Ahora arrastramos las dependencias necesarias para cada Installer:

LoginViewInstaller
LoginUseCaseInstaller
LoginPresenterInstaller

7. Ahora arrastramos los MonoInstallers al Login-MonoInstalator

MonoInstalator

Fijense que el orden en el que ponemos los MonoInstallers es muy importante, ya que se van instalando en orden, eso quiere decir primero se tiene que instalar la LoginView y LoginUseCase para poder instalar LoginPresenter, si este orden no se cumpliese entonces tiraria error de referencia nula.

8. Por ultimo arrastramos el Login-MonoInstalator al Login-MonoInstallerInitializer.

MonoInstallerInitializer

Listo! ahora nuestro programa esta listo para funcionar!

Ultimos retoques

Aunque nuestra aplicacion ya este funcionando correctamente falta hacer una cosa mas para que nuestros scripts queden completamente organizados, y para esto vamos a crear AssemblyDefinitions.

Assembly Definition

Un Assembly Definition es archivo que agrupa scripts, por defecto si no creamos ningun Assembly para nuestro proyecto nuestros scripts vanm a estar agrupados en un assembly por defecto creado por unity llamado Assembly-CSharp.asmdef, el problema de no trabajar con Assemblys es que hacemos que todos los scripts se puedan conocer sin ningun problema. Y lo que nosotros queremos lograr es que los scripts se conozcan cuando nosotros queramos. Por otro lado tener Assemblies va a hacer que respetemos La Regla de la Dependencia.

Para crear uno hay que darle click derecho en la carpeta del proyecto en donde queramos crearlo y luego click en Create>AssemblyDefinition:

La nomenclatura que se usa para los Assembies es:

LugarEnQueSeEjecuta.NombreDeLaFeatureOPackage.CapaAlQueCorresponde

LugarEnQueSeEjecuta: Este puede ser Runtime o Editor.
NombreDeLaFeatureOPackage: Este es el nombre de lo que sea en lo que estemos trabajando, en nuestro caso sera Login.
CapaAlQueCorresponde: hace referencia a la capa que corresponde ya sea View, Model o Presenter.

En nuestro caso un ejemplo quedaria:

Runtime.Login.Model

Entonces ahora vamos crear un Assembly para cada capa dentro los scripts de Login. Es normal que el proceso de crear los Assemblies unity tire mucho errores de referencias, estos errores desapareceran cuando terminemos de crear todos los Assemblies. Primero vamos a crearlos y luego vamos a asignar las referencias

View Assembly

Creamos un assembly en la folder View y lo llamamos:

Runtime.Login.Views

Presenter Assembly

Runtime.Login.Presenters

Model Assembly

Runtime.Login.Models

Installers Assembly

Runtime.Login.Installers

Ahora pasaremos a asignar las referencias, esta sirven para que un Assembly conozca los scripts de otro y no pueden ser ciclicas es decir solo un Assembly puede conocer al otro, no los dos a los dos.

View Assembly

Dentro de Assembly Definition References arrastramos el Assemby del TextMeshPro y el del Presenters

Presenter Assembly

Model Assembly

No necesita ninguna referencia.

Installers Assembly

Con esto ultimo retoque nuestro codigo queda completamente organizado y seguro a que ningun codigo externo pueda tocarlo sin antes tener una referencia a su Assembly.

Conclusion

Como pudimos ver implementar una arquitectura no es muy complicado y nos trae grandes beneficios.

Espero que el post te haya sido util! y cualquier consulta o duda no dudes en comentar o escribirme a mi mail: LaurencioBerro@gmail.com.

Nos re vimos!

--

--

Lalo Berro

Im Lalo a passionate videogame programmer that loves share quality and advanced content.