tutorial patterns example clean android mvvm architecture-components

patterns - mvvm android



PatrĂ³n MVVM y startActivity (2)

NSimon, es genial que comiences a usar AAC.

Escribí un issue en el aac''s-github antes de eso.

Hay varias maneras de hacer eso.

Una solución sería utilizar un

WeakReference a un WeakReference de navegación que contiene el contexto de la actividad. Este es un patrón utilizado comúnmente para manejar cosas vinculadas al contexto dentro de un ViewModel.

Lo rechazo altamente por varias razones. Primero: eso generalmente significa que debe mantener una referencia a su controlador de navegación que corrige la fuga de contexto, pero no resuelve la arquitectura en absoluto.

La mejor manera (en mi opinión) es usar LiveData, que es consciente del ciclo de vida y puede hacer todo lo que se busca.

Ejemplo:

class YourVm : ViewModel() { val uiEventLiveData = SingleLiveData<Pair<YourModel, Int>>() fun onClick(item: YourModel) { uiEventLiveData.value = item to 3 // can be predefined values } }

Después de eso puedes escuchar dentro de tu vista los cambios.

class YourFragmentOrActivity { //assign your vm whatever override fun onActivityCreated(savedInstanceState: Bundle?) { var context = this yourVm.uiEventLiveData.observe(this, Observer { when (it?.second) { 1 -> { context.startActivity( ... ) } 2 -> { .. } } }) } }

Tenga cuidado de que haya usado un MutableLiveData modificado, porque de lo contrario, siempre emitirá el último resultado para los nuevos Observadores, lo que conduce a un mal comportamiento. Por ejemplo, si cambia la actividad y vuelve, terminará en un bucle.

class SingleLiveData<T> : MutableLiveData<T>() { private val mPending = AtomicBoolean(false) @MainThread override fun observe(owner: LifecycleOwner, observer: Observer<T>) { if (hasActiveObservers()) { Log.w(TAG, "Multiple observers registered but only one will be notified of changes.") } // Observe the internal MutableLiveData super.observe(owner, Observer { t -> if (mPending.compareAndSet(true, false)) { observer.onChanged(t) } }) } @MainThread override fun setValue(t: T?) { mPending.set(true) super.setValue(t) } /** * Used for cases where T is Void, to make calls cleaner. */ @MainThread fun call() { value = null } companion object { private val TAG = "SingleLiveData" } }

¿Por qué es mejor ese intento que usar WeakReferences, Interfaces o cualquier otra solución?

Porque este evento divide la lógica de la interfaz de usuario con la lógica de negocios. También es posible tener múltiples observadores. Se preocupa por el ciclo de vida. No deja escapar nada.

También puede resolverlo usando RxJava en lugar de LiveData usando un PublishSubject. ( addTo requiere RxKotlin )

Tenga cuidado de no filtrar una suscripción lanzándola en onStop ().

class YourVm : ViewModel() { var subject : PublishSubject<YourItem> = PublishSubject.create(); } class YourFragmentOrActivityOrWhatever { var composite = CompositeDisposable() onStart() { YourVm.subject .subscribe( { Log.d("...", "Event emitted $it") }, { error("Error occured $it") }) .addTo(compositeDisposable) } onStop() { compositeDisposable.clear() } }

También tenga cuidado de que un ViewModel esté vinculado a una Actividad O a un Fragmento. No puede compartir un ViewModel entre varias Actividades ya que esto rompería el "Conocimiento del ciclo de vida".

Si necesita que sus datos permanezcan utilizando una base de datos como room o comparta los datos utilizando paquetes.

Hace poco decidí echar un vistazo más de cerca a los nuevos Componentes de la Arquitectura de Android que lanzó Google, especialmente al usar su clase ViewModel para el ciclo de vida con una arquitectura MVVM y LiveData.

Mientras esté tratando con una sola Actividad, o un solo Fragmento, todo está bien.

Sin embargo, no puedo encontrar una buena solución para manejar el cambio de actividad. Digamos, por un breve ejemplo, que la Actividad A tiene un botón para iniciar la Actividad B.

¿Dónde se manejaría el startActivity ()?

Siguiendo el patrón MVVM, la lógica del clickListener debe estar en el ViewModel. Sin embargo, queremos evitar tener referencias a la Actividad allí. Por lo tanto, pasar el contexto a ViewModel no es una opción.

Reduje un par de opciones que parecían "OK", pero no pude encontrar una respuesta adecuada de "aquí está cómo hacerlo".

Opción 1 : tener una enumeración en el ViewModel con valores asignados al enrutamiento posible (ACTIVITY_B, ACTIVITY_C). Junte esto con un LiveData. La actividad observaría este LiveData, y cuando ViewModel decida que debe iniciarse ACTIVITY_C, debería simplemente postValue (ACTIVITY_C). La actividad puede entonces llamar a startActivity () normalmente.

Opción 2 : El patrón de interfaz regular. El mismo principio que la opción 1, pero la Actividad implementaría la interfaz. Aunque siento un poco más de acoplamiento con esto.

Opción 3 : opción de mensajería, como Otto o similar. ViewModel envía un Broadcast, Activity lo recoge y lanza lo que tiene que hacer. El único problema con esta solución es que, de forma predeterminada, debe colocar el registro / anulación del registro de esa difusión dentro del ViewModel. Así que no ayuda.

Opción 4 : tener una gran clase de enrutamiento, en algún lugar, como singleton o similar, que podría llamarse para enviar enrutamiento relevante a cualquier actividad. Eventualmente a través de la interfaz? Así que cada actividad (o una BaseActivity) implementaría

IRouting { void requestLaunchActivity(ACTIVITY_B); }

Este método solo me preocupa un poco cuando la aplicación comienza a tener muchos fragmentos / actividades (porque la clase de Enrutamiento se volvería enorme)

Eso es todo. Esa es mi pregunta. ¿Cómo ustedes manejan esto? ¿Vas con una opción que no se me ocurrió? ¿Qué opción consideras más relevante y por qué? ¿Cuál es el enfoque recomendado de Google?

PD: Enlaces que no me llevaron a ninguna parte 1 - Llamada de ViewModel de Android Métodos de actividad 2 - ¿Cómo comenzar una actividad desde una clase java simple sin actividad?


Puede ampliar su ViewModel desde AndroidViewModel , que tiene la referencia de la aplicación, e iniciar la actividad usando este contexto.