¡Qué tal! Eduardo Rodríguez de este lado del monitor para hablarles un poco acerca de MVVM, que es y como funcionan, pero antes no olvides pasarte por mi articulo anterior en el cual te explico acerca de JTW.
Un poco de contexto
Todos los desarrolladores de software estamos en búsqueda del código perfecto. Lo cierto es que nunca es posible lograrlo, ya que por muchas razones, a veces externas, a veces por nuestra limitación de conocimiento o simplemente porque nuestra manera de codificar siempre está en constante evolución (o al menos debería), es imposible obtenerlo. De igual manera, otra cosa que es cierta es que siempre podemos intentarlo, siempre podemos tratar de aprender más, de usar técnicas para que sea más escalable, más fácil de leer, más eficiente, etc. Una de estas tantas maneras de hacer mejor nuestro código es utilizar patrones de diseño.
¿Que es Patron de diseño?
Por definición, los patrones de diseño son:
“Técnicas para resolver problemas comunes en el desarrollo de software y otros ámbitos referentes al diseño de interacción o interfaces.”
Existen muchos patrones de diseño, algunos se puede utilizar combinados entre sí y su uso dependerá del problema que queramos resolver con nuestro software.
Si bien la finalidad de este articulo no es explicar a fondo que son patrones de diseño, la idea es que tengan una base de que son, ya que en este caso hablaremos específicamente de uno enfocado a Android.
¿Que es MVVM?
MVVM, por sus siglas en ingles Model View ViewModel, es un patrón de diseño que tiene por finalidad separar la parte de la interfaz del usuario(de ahí la V de View) de la parte de la lógica del negocio(de ahí la M de Model), logrando así que la parte visual sea totalmente independiente. El otro componente es el ViewModel que es la parte que va a interactuar como puente entre la Vista y el Modelo.
¿Cómo aplica MVVM en Android?
Aqui se muestra graficamente como es que se implementa el MVVM en Android:
Android nos da una colección de librerías que nos pueden ser muy útiles a la hora de querer mejorar el desarrollo de nuestras apps llamadas Componentes de la arquitectura, en este caso nos centraremos en dos de las clases que estas librerías nos ofrecen
- ViewModel
Esta clase será el intermediario entre nuestra vista y nuestra lógica del negocio, es la encargada de almacenar la información de la interfaz gráfica, ya que una de sus ventajas más grandes es que no se destruye en el cambio de orientación de nuestra aplicación.
- LiveData
Esta clase nos sirve para compilar objetos de datos que nos permitirán notificar cuando algún valor sea modificado por la parte de la lógica del negocio, logrando con esto que la interfaz se entere y haga los ajustes necesarios.
Veamos algo de código
Lo que el siguiente código realiza es básicamente un login utilizando el patrón de diseño MVVM, veamos las clases que lo componen:
Modelos de datos:
Empecemos por un par de modelos que seran necesarios para poder llevar acabo nuestra accion de login, uno sera el modelo de la respuesta para hacer saber a la interfaz en que paso esta y otro sera el modelo de los datos de login que se enviaran al backend para validar las credenciales.
ApiResponse:
data class ApiResponse (var isDone: Boolean = false, var jsonResponse : JsonElement? = null, var msg: String? = null, var error: Boolean = false)
Login:
data class Login(var user: String, var password: String)
Modelo:
Ahora crearemos una clase repositorio, que es la que se encargara de proporcionarnos datos, en este caso la que hara la llamada al backend para validar las credenciales proporcionadas por el usuario en la interfaz grafica.
LoginRepository:
class LoginRepository { val tag: String = LoginRepository::class.java.simpleName var apiResponse = ApiResponse() fun performLogin(context: Context, loginRepositoryResponse: MutableLiveData<ApiResponse>, user: String, password:String) { try { Api.login(context, Login(user, password), object : Api.RequestListener<JsonElement?> { override fun onSuccess(response: JsonElement?) { Utils.log(tag, response.toString()) Utils.saveLoginObject(response?.asJsonObject!!) apiResponse.jsonResponse = response apiResponse.isDone = true loginRepositoryResponse.value = apiResponse } override fun onResponse() { } override fun onError(error: String?) { apiResponse.error = true if (error == Errors.ERROR_ENTITY_EXISTS) { apiResponse.msg = context.getString(R.string.error_bad_request_invalid_params) } if (error == Errors.ERROR_ENTITY_NOT_EXISTS) { apiResponse.msg = context.getString(R.string.client_not_exists_error) } apiResponse.isDone = true loginRepositoryResponse.value = apiResponse } }) } catch (e: Exception) { Utils.error(tag, e.message, e) } } }
Vista Modelo:
La siguiente clase es el puente entre nuestro repositorio y nuestra interfaz grafica:
LoginViewModel:
class LoginViewModel: ViewModel() { private val repository : LoginRepository = LoginRepository() var loginResponse: MutableLiveData<ApiResponse> = MutableLiveData<ApiResponse>() fun performLogin(context: Context, user: String, password:String) { loginResponse.value = ApiResponse() repository.performLogin(context, loginResponse, user, password) } }
Vista:
Por ultimo tenemos nuestra vista, que es la parte con la que interactua el usuario:
LoginActivity:
class LoginActivity : BaseActivity(), View.OnClickListener { override val tag = LoginActivity::class.java.simpleName override val layoutId = R.layout.activity_login private lateinit var loginViewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) loginViewModel = ViewModelProvider(this).get(LoginViewModel::class.java) loginBtn.setOnClickListener(this) } override fun onClick(view: View?) { when (view?.id) { R.id.loginBtn -> onLogin() } } private fun checkUser(): Boolean { if (TextUtils.isEmpty(usernameET.getText().toString().trim())) { Utils.buildOkDialog(this, getString(R.string.username_blank_error)) return false } else if (!Patterns.EMAIL_ADDRESS.matcher( usernameET.getText().toString().trim()).matches() ) { Utils.buildOkDialog(this, getString(R.string.username_not_email_error)) return false } return true } private fun checkData(): Boolean { if (!checkUser()) return false if (passwordET.text.toString().trim() == "") { Utils.buildOkDialog(this, getString(R.string.password_blank_error)) return false } if (passwordET.text.toString().trim().length < 8) { Utils.buildOkDialog(this, getString(R.string.password_length_error)) return false } return true } private fun onLogin() { if (checkData()) { isLoading() loginViewModel.loginResponse.observe(this, { if(it.isDone) { isDone() loginViewModel.loginResponse.removeObservers(this) if(!TextUtils.isEmpty(it.msg)) Utils.buildOkDialog(this, it.msg!!) startActivity(Intent(this@LoginActivity, MainActivity::class.java)) } }) loginViewModel.performLogin(this, usernameET.text.toString().trim(), Utils.getSHA1(passwordET.text.toString())) } } }
Como podrás observar en el código mostrado, la parte de la Vista(LoginActivity) esta consiente solamente de la parte de la VistaModelo(LoginViewModel), pero jamás interactúa con la parte de la lógica del negocio. A su vez, la parte de la VistaModelo(LoginViewModel) tiene consciencia de la parte del repositorio(LoginRepository) pero no de la Vista(LoginActivity).
Conclusion
MVVM es un patrón de diseño muy potente y fácil de implementar. Como pudiste observar en el ejemplo, al implementar MVVM todo está más encapsulado, permitiendo con esto que nuestra aplicación sea más escalable, más fácil de leer, de mantener, de probar y por tanto más segura.
Espero este post les haya ayudado.
Hasta la próxima.