Ich habe mich kürzlich entschieden, mir die neuen Android-Architekturkomponenten, die Google veröffentlicht hat, genauer anzusehen, insbesondere die lebenszyklusbewusste Klasse ViewModel für eine MVVM-Architektur und LiveData.
Solange ich es mit einer einzelnen Aktivität oder einem einzelnen Fragment zu tun habe, ist alles in Ordnung.
Ich kann jedoch keine nette Lösung finden, um die Aktivitätsumschaltung zu handhaben. Nehmen wir für ein kurzes Beispiel an, dass Aktivität A eine Schaltfläche zum Starten von Aktivität B hat.
Wo würde die startActivity() behandelt werden?
Gemäß dem MVVM-Muster sollte sich die Logik des ClickListeners im ViewModel befinden. Wir möchten jedoch vermeiden, dort Verweise auf die Aktivität zu haben. Das Übergeben des Kontexts an das ViewModel ist also keine Option.
Ich habe ein paar Optionen eingegrenzt, die “OK” erscheinen, konnte aber keine richtige Antwort auf “So geht’s” finden.
Option 1 : Haben Sie eine Aufzählung im ViewModel mit Werten, die einem möglichen Routing (ACTIVITY_B, ACTIVITY_C) zugeordnet sind. Koppeln Sie dies mit einem LiveData. Die Aktivität würde diese LiveData beobachten, und wenn das ViewModel entscheidet, dass ACTIVITY_C gestartet werden soll, würde es einfach Value(ACTIVITY_C) posten. Activity kann dann startActivity() normal aufrufen.
Option 2 : Das reguläre Schnittstellenmuster. Gleiches Prinzip wie Option 1, aber Activity würde die Schnittstelle implementieren. Ich fühle mich jedoch ein bisschen mehr verbunden mit diesem.
Möglichkeit 3 : Messaging-Option, z. B. Otto oder ähnliches. ViewModel sendet einen Broadcast, Activity nimmt ihn auf und startet, was er soll. Das einzige Problem bei dieser Lösung ist, dass Sie die Registrierung/Abmeldung dieser Sendung standardmäßig in das ViewModel einfügen sollten. Hilft also nicht.
Möglichkeit 4 : Irgendwo eine große Routing-Klasse als Singleton oder ähnliches haben, die aufgerufen werden könnte, um relevantes Routing an jede Aktivität zu senden. Eventuell per Schnittstelle? So würde jede Aktivität (oder eine BaseActivity) implementiert werden
IRouting { void requestLaunchActivity(ACTIVITY_B); }
Diese Methode macht mir nur ein bisschen Sorgen, wenn Ihre App anfängt, viele Fragmente/Aktivitäten zu haben (weil die Routing-Klasse riesig werden würde).
Das war’s. Das ist meine Frage. Wie geht ihr damit um? Entscheiden Sie sich für eine Option, an die ich nicht gedacht habe? Welche Option halten Sie für die relevanteste und warum? Was ist der von Google empfohlene Ansatz?
PS: Links, die mich nicht weitergebracht haben 1 – Android ViewModel-Aufruf Aktivitätsmethoden 2 – Wie starte ich eine Aktivität aus einer einfachen Nicht-Aktivitäts-Java-Klasse?
NSimon, es ist großartig, dass du anfängst, Unterstützte Kommunikation zu verwenden.
Ich schrieb ein Ausgabe im aac’s-github vorher darüber.
Dafür gibt es mehrere Möglichkeiten.
Eine Lösung wäre die Verwendung von a
SchwachReferenz an einen NavigationController, der den Kontext der Aktivität enthält. Dies ist ein häufig verwendetes Muster für die Behandlung kontextgebundener Dinge in einem ViewModel.
Ich lehne dies aus mehreren Gründen stark ab. Erstens: Das bedeutet normalerweise, dass Sie einen Verweis auf Ihren NavigationController beibehalten müssen, der das Kontextleck behebt, aber die Architektur überhaupt nicht löst.
Der beste Weg (meiner Meinung nach) ist die Verwendung von LiveData, das den Lebenszyklus berücksichtigt und alle gewünschten Dinge tun kann.
Beispiel:
class YourVm : ViewModel() {
val uiEventLiveData = SingleLiveData<Pair<YourModel, Int>>()
fun onClick(item: YourModel) {
uiEventLiveData.value = item to 3 // can be predefined values
}
}
Danach können Sie in Ihrer Ansicht nach Änderungen lauschen.
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 -> { .. }
}
})
}
}
Achten Sie darauf, dass ich ein modifiziertes MutableLiveData verwendet habe, da es sonst immer das neueste Ergebnis für neue Beobachter ausgibt, was zu schlechtem Verhalten führt. Wenn Sie beispielsweise die Aktivität ändern und zurückgehen, endet dies in einer Schleife.
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
}
})
}
@MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
fun call() {
value = null
}
companion object {
private val TAG = "SingleLiveData"
}
}
Warum ist dieser Versuch besser als die Verwendung von WeakReferences, Interfaces oder einer anderen Lösung?
Weil dieses Ereignis die UI-Logik mit der Geschäftslogik geteilt hat. Es ist auch möglich, mehrere Beobachter zu haben. Es kümmert sich um den Lebenszyklus. Es leckt nichts.
Sie können es auch lösen, indem Sie RxJava anstelle von LiveData verwenden, indem Sie ein PublishSubject verwenden. (addTo
erfordert RxKotlin)
Achten Sie darauf, dass ein Abonnement nicht verloren geht, indem Sie es in onStop() freigeben.
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()
}
}
Achten Sie auch darauf, dass ein ViewModel an eine Aktivität ODER ein Fragment gebunden ist. Sie können ein ViewModel nicht zwischen mehreren Aktivitäten teilen, da dies das “Livecycle-Awareness” brechen würde.
Wenn Sie dies benötigen, speichern Sie Ihre Daten mithilfe einer Datenbank wie z Zimmer oder teilen Sie die Daten über Pakete.
Sie sollten startActivity aus der Aktivität heraus aufrufen, nicht aus dem Ansichtsmodell. Wenn Sie es aus Viewmodel öffnen möchten, müssen Sie Livedaten in Viewmodel mit einigen Navigationsparametern erstellen und Livedaten innerhalb der Aktivität beobachten
Sie können Ihr ViewModel von erweitern AndroidViewModeldie die Anwendungsreferenz enthält, und starten Sie die Aktivität mit diesem Kontext.