46 votes

Modèle MVVM et startActivity

Récemment, j'ai décidé de regarder de plus près à la nouvelle Android des Composants de l'Architecture que Google a publié, en particulier à l'aide de leur ViewModel du cycle de vie-conscience de classe à une architecture MVVM, et LiveData.

Aussi longtemps que je m'occupe d'une Activité, ou un Fragment unique, tout va bien.

Cependant, je ne peux pas trouver une solution sympa pour gérer l'Activité de commutation. Dire, pour l'amour d'un court exemple, que l'Activité a un bouton pour lancer l'Activité B.

Où serait le startActivity() seront-ils traités?

Suivant le modèle MVVM, la logique de la clickListener devrait être dans le ViewModel. Cependant, nous voulons éviter d'avoir les références à l'Activité là-bas. Donc en passant le contexte de ce Dernier n'est pas une option.

J'ai rétréci vers le bas d'un couple d'options qui semblent "OK", mais n'a pas été en mesure de trouver la bonne réponse", voici comment le faire.".

Option 1 : vous Avez un enum dans le ViewModel avec les valeurs de la cartographie de routage possibles (ACTIVITY_B, ACTIVITY_C). Couplez cela avec une LiveData. L'activité serait d'observer ce LiveData, et lorsque ce Dernier décide que ACTIVITY_C devrait être lancé, il venait de postValue(ACTIVITY_C). L'activité peut alors appeler startActivity() normalement.

Option 2 : L'ordinaire de l'interface de modèle. Même principe que pour l'option 1, mais l'Activité devrait mettre en œuvre l'interface. Je me sens un peu plus le couplage avec cette même si.

Option 3 : option de Messagerie, tels que Otto ou similaire. ViewModel envoie une Diffusion, l'Activité reprend et lance ce qu'il a à la. Seul problème avec cette solution est que, par défaut, vous devez mettre le s'inscrire/se désinscrire de la Diffusion à l'intérieur du ViewModel. Si cela n'aide pas.

Option 4 : Avoir un gros Routage classe, quelque part, en tant que singleton ou similaires, qui pourraient être appelés à l'expédition pertinentes de routage à toute activité. Finalement via l'interface? Ainsi, chaque activité (ou un BaseActivity) serait de mettre en œuvre

IRouting { void requestLaunchActivity(ACTIVITY_B); }

Cette méthode m'inquiète un peu quand votre application commence à avoir un grand nombre de fragments/activités (parce que le Routage de la classe qui allait devenir gigantesque)

Donc, c'est ça. C'est ma question. Comment faites-vous pour gérer cela? Ne vous y allez avec une option que je n'ai pas pensé? Quelle option avez-vous jugent les plus pertinentes et pourquoi? Qu'est-ce que le recommandé Google?

PS : les Liens qui ne m'obtenez pas n'importe où 1 - Android ViewModel Activité des appels de méthodes 2 - Comment faire pour démarrer une activité à partir d'une plaine de non-activité de la classe java?

46voto

Emanuel Seibold Points 1242

NSimon, son grand que vous commencez à utiliser le format AAC.

J'ai écrit un problème dans l'aac est-github avant à ce sujet.

Il y a plusieurs façons de le faire.

Une solution pourrait être l'utilisation d'un

WeakReference à un NavigationController qui détient le Contexte de l'Activité. Il s'agit d'un modèle utilisé pour la manipulation du contexte lié trucs à l'intérieur d'un ViewModel.

J'ai très déclin de ce pour plusieurs raisons. La première: que signifie généralement que vous devez garder une référence à votre NavigationController qui fixe le cadre de fuite, mais ne pas résoudre l'architecture à tous.

Le meilleur moyen (à mon oppinion) est à l'aide de LiveData qui est du cycle de vie consciente et peut faire toutes les choses.

Exemple:

class YourVm : ViewModel() { 

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

Après que vous pouvez écouter à l'intérieur de votre point de vue pour les changements.

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 -> { .. } 
            }

        })
    }
}

Prenez garde à ce que ive a utilisé une version modifiée de MutableLiveData, car sinon il sera toujours émettent de la dernière conséquence pour les nouveaux Observateurs qui conduit à un mauvais comportement. Par exemple si vous changez d'activité et de revenir en arrière, il va finir dans une boucle.

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"
    }
}

Pourquoi est-ce que tenter de mieux que de l'aide WeakReferences, Interfaces, ou toute autre solution?

Parce que cet événement split logique de l'INTERFACE utilisateur avec une logique d'entreprise. Il est aussi possible d'avoir plusieurs observateurs. Il se soucie de la vie. Il n'ya pas de fuite rien.

Vous pouvez également résoudre en utilisant RxJava au lieu de LiveData à l'aide d'un PublishSubject. (addTo nécessite RxKotlin)

Prendre soin de ne pas la fuite d'un abonnement en le libérant dans 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()
       }
    }

Prenez également le soin qu'un ViewModel est lié à une Activité OU d'un Fragment. Vous ne pouvez pas partager un ViewModel entre de multiples Activités, ce qui permettrait de briser le "Livecycle Conscience".

Si vous avez besoin qui persistent vos données à l'aide d'une base de données comme chambre ou partager les données à l'aide de parcelles.

2voto

Majid Sadeghi Points 21

Vous devez appeler startActivity à partir d'activité, et non à partir de viewmodel. Si vous voulez l'ouvrir à partir de viewmodel, vous devez créer des données de vécu dans viewmodel avec un paramètre de navigation et observer les données de données à l'intérieur de l'activité.

-4voto

Vous pouvez étendre votre ViewModel à partir de AndroidViewModel , qui contient la référence de l'application, et démarrer l'activité en utilisant ce contexte.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X