¿Cómo implementar el patrón de diseño Observer para manejar eventos de saldo disponible en Kotlin?
El patrón de diseño Observer es uno de los patrones de comportamiento más utilizados en el desarrollo de software por su capacidad para manejar la comunicación entre objetos de manera eficiente. En el contexto de una aplicación Android, su uso se destaca en el manejo de cambios de estado y la actualización automática de la interfaz de usuario. A continuación, revisaremos cómo puedes implementar este patrón para gestionar el saldo disponible.
¿Qué pasos seguir para crear el observador del saldo en Kotlin?
Creación de la clase del observador:
Comienza creando una nueva clase que manejará los eventos del saldo disponible. Esta clase será la observada y tendrá que implementar la interfaz Observer. Al implementar esta interfaz, se nos obligará a definir varios métodos esenciales como addObserver, removeObserver, y notifyChange.
class BalanceObserver :Observable(){// Implementación de métodos es requerida}
Definición de los métodos de la interfaz:
addObserver: Debes crear un método que registre un observador a nuestra lista de observadores, la cual puedes almacenar en una variable inmutable val observerList = mutableListOf<Observer>().
notifyChange: Por último, cuando se produzca un cambio, el método notifyChange informará a todos los observadores sobre el nuevo valor, que se distribuirá a través de este método por medio de un bucle for.
overridefunnotifyChange(newValue: Double){for(observer in observerList){ observer.update(newValue)}}
Creación de una función para cambiar el saldo disponible:
Necesitamos una función pública que permita cambiar el saldo y notificar a todos los observadores sobre este cambio.
En el método update que debemos implementar al crear el observador, podemos actualizar directamente la vista cada vez que se notifique un cambio en el saldo.
¿Cuáles son las mejores prácticas al usar el patrón Observer en Android?
Suscripción y desuscripción adecuada: Es crucial suscribirse y desuscribirse de los eventos en los momentos adecuados del ciclo de vida de la actividad o fragmento. La desuscripción debe realizarse cuando la actividad/fragmento se destruye para evitar fugas de memoria o excepciones de referencia nula.
Uso de genéricos: Considera implementar los observadores como genéricos para reutilizarlos en diferentes contextos y con distintos tipos de datos.
Eficiencia en la notificación de cambios: Asegúrate de que la notificación de cambios sea eficiente y se realice solo cuando sea necesario, para no afectar el rendimiento de la aplicación.
Al implementar correctamente el patrón Observer, facilitas la gestión del flujo de datos y mejoras la reactibilidad de tu aplicación, ofreciendo a los usuarios una experiencia más fluida y eficiente.
Me propuse como reto hacer lo que dice el profesor: que las interfaces sean genéricas para que nosotros podamos usarla con cualquier tipo de dato. Simplemente fue cuestión de modificarlas de esta manera:
interfaceObservable2<T>{ fun addObserver(observer:Observer2<T>) fun removeObserver(observer:Observer2<T>) fun notifyObservers(newValue:T?)}
interfaceObserver2<T>{ fun notifyChange(newValue:T?)}
También me propuse añadir la funcionalidad de eliminar todos los observers automáticamente cuando el Fragment se destruye, tal y como lo hace LiveData. La clase quedó de esta manera:
classAvailableBalanceObservable<T>(lifecycleOwner:LifecycleOwner):Observable2<T> init { lifecycleOwner.lifecycle.addObserver(object :LifecycleObserver{ @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun removeAllObservers(){ amountObserverList.clear() lifecycleOwner.lifecycle.removeObserver(this)}})}// Resto del código}
Un LifecycleOwner es una interfaz que implementan todas las clases que tienen un ciclo de vida, en este caso un Fragment, pero también puede ser una Activity. En mi implementación, al iniciar la clase, se añade un observador al ciclo de vida del Fragment. Cuando el Fragment ejecuta onDestroy(), se eliminan todos los observers de la lista y se elimina el propio observer del ciclo de vida.
Finalmente, para construir la clase, basta con llamarla así:
classHomeFragment:Fragment(),HomeContract.View{private lateinit varavailableBalanceObservable:AvailableBalanceObservable<Double>// Resto del código... override fun onViewCreated(view:View,savedInstanceState:Bundle?){// Resto del código... availableBalanceObservable =AvailableBalanceObservable(viewLifecycleOwner)}}
Con estos dos cambios, la clase tiene más funcionalidades y está más cerca a la implementación de LiveData
¿Por que es mas sano declararla interfaz directamente en el método addObserver que de implementar la interfaz a nivel clase en esta caso Fragment?
Son preferencias a nivel de lectura, si tu implementas 5, 6 interfaces en tu clase es un poco engorroso sobre-escribir tantos métodos, en el casi de darle .addObserver(y ver la interfaz y todo aquí), a nivel de legibilidad es mejor para ti por que sabes que estas agregando un observador y se ve cuando genera un cambio en la declaración del método.
Gracias, me quedo super claro.
Los eventos del observable deberían delegarse al Presenter ya que a la vista (fragment o activity) no debe tener conocimiento de esta lógica. Cuando se observe un cambio, este se notifica a la vista a través del mismo Presenter.
Es muy importante tener en cuenta que estas dos interfaces tienen una continua comunicacion en andorid nuestras activities tienen un ciclo de vida volatil (el OS destruye actividades que estan en segundo plano para optimizar recursos)al momento de llamar al metodo onDestroy es una muy buena practica desuscribirme de todos los eventos de ese observable.
No que la Vista se comunicaba con el Presentador y este con el Observable ? En este ejemplo se saltaron el Presenter jajaja
En este curso el docente ha explicado conceptos que en cursos anteriores fueron asumidos como ya conocidos por los estudiantes, lo cual fue un error, debido a que el curso se hacia confuso. Era como leer un libro sin conocer el idioma en el que está escrito.
El curso me ha sido de gran ayuda.
"$ $newValue" el primer simbolo de dolar es string para mostrarlo el segundo para mostrar el valor de la variable