Ich versuche, Dagger 2 zu meinem Projekt hinzuzufügen. Ich konnte ViewModels (AndroidX Architecture-Komponente) für meine Fragmente einfügen.
Ich habe einen ViewPager mit 2 Instanzen desselben Fragments (nur eine geringfügige Änderung für jede Registerkarte) und in jeder Registerkarte beobachte ich eine LiveData
, um über Datenänderungen (von der API) aktualisiert zu werden.
Das Problem ist, dass, wenn die API-Antwort kommt und die aktualisiert LiveData
, dieselben Daten im aktuell sichtbaren Fragment an Beobachter auf allen Registerkarten gesendet werden. (Ich denke, das liegt wahrscheinlich am Umfang der ViewModel
).
So beobachte ich meine Daten:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
activityViewModel.expenseList.observe(this, Observer {
swipeToRefreshLayout.isRefreshing = false
viewAdapter.setData(it)
})
....
}
Ich benutze diese Klasse, um ViewModel
s bereitzustellen :
class ViewModelProviderFactory @Inject constructor(creators: MutableMap<Class<out ViewModel?>?, Provider<ViewModel?>?>?) :
ViewModelProvider.Factory {
private val creators: MutableMap<Class<out ViewModel?>?, Provider<ViewModel?>?>? = creators
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
var creator: Provider<out ViewModel?>? = creators!![modelClass]
if (creator == null) { // if the viewmodel has not been created
// loop through the allowable keys (aka allowed classes with the @ViewModelKey)
for (entry in creators.entries) { // if it's allowed, set the Provider<ViewModel>
if (modelClass.isAssignableFrom(entry.key!!)) {
creator = entry.value
break
}
}
}
// if this is not one of the allowed keys, throw exception
requireNotNull(creator) { "unknown model class $modelClass" }
// return the Provider
return try {
creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
companion object {
private val TAG: String? = "ViewModelProviderFactor"
}
}
Ich binde meine ViewModel
so:
@Module
abstract class ActivityViewModelModule {
@MainScope
@Binds
@IntoMap
@ViewModelKey(ActivityViewModel::class)
abstract fun bindActivityViewModel(viewModel: ActivityViewModel): ViewModel
}
Ich benutze @ContributesAndroidInjector
für mein Fragment Folgendes:
@Module
abstract class MainFragmentBuildersModule {
@ContributesAndroidInjector
abstract fun contributeActivityFragment(): ActivityFragment
}
Und ich füge diese Module meiner MainActivity
Unterkomponente folgendermaßen hinzu:
@Module
abstract class ActivityBuilderModule {
...
@ContributesAndroidInjector(
modules = [MainViewModelModule::class, ActivityViewModelModule::class,
AuthModule::class, MainFragmentBuildersModule::class]
)
abstract fun contributeMainActivity(): MainActivity
}
Hier ist mein AppComponent
:
@Singleton
@Component(
modules =
[AndroidSupportInjectionModule::class,
ActivityBuilderModule::class,
ViewModelFactoryModule::class,
AppModule::class]
)
interface AppComponent : AndroidInjector<SpenmoApplication> {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
}
Ich dehne DaggerFragment
und spritze ViewModelProviderFactory
so:
@Inject
lateinit var viewModelFactory: ViewModelProviderFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
....
activityViewModel =
ViewModelProviders.of(this, viewModelFactory).get(key, ActivityViewModel::class.java)
activityViewModel.restartFetch(hasReceipt)
}
Das key
wird für beide Fragmente unterschiedlich sein.
Wie kann ich sicherstellen, dass nur der Beobachter des aktuellen Fragments aktualisiert wird?
BEARBEITEN 1 ->
Ich habe ein Beispielprojekt mit dem Fehler hinzugefügt. Das Problem tritt anscheinend nur auf, wenn ein benutzerdefinierter Bereich hinzugefügt wird. Bitte sehen Sie sich das Beispielprojekt hier an: Github-Link
master
Zweig hat die App mit dem Problem. Wenn Sie eine Registerkarte aktualisieren (zum Aktualisieren wischen), wird der aktualisierte Wert in beiden Registerkarten angezeigt. Dies geschieht nur, wenn ich einen benutzerdefinierten Bereich hinzufüge ( @MainScope
).
working_fine
Branch hat die gleiche App ohne benutzerdefinierten Bereich und funktioniert einwandfrei.
Bitte lassen Sie mich wissen, wenn die Frage nicht klar ist.
quelle
working_fine
Branch nicht verwenden werden. Warum brauchen Sie den Umfang?Antworten:
Ich möchte die ursprüngliche Frage noch einmal zusammenfassen:
Nach meinem Verständnis haben Sie den Eindruck, dass Sie, nur weil Sie versuchen, eine Instanz der
ViewModel
Verwendung verschiedener Schlüssel zu erhalten, verschiedene Instanzen vonViewModel
:Die Realität sieht etwas anders aus. Wenn Sie das folgende Anmeldefragment einfügen, werden Sie feststellen, dass diese beiden Fragmente genau dieselbe Instanz von verwenden
PagerItemViewModel
:Lassen Sie uns eintauchen und verstehen, warum dies passiert.
Intern
ViewModelProvider#get()
wird versucht, eine InstanzPagerItemViewModel
von a zu erhalten,ViewModelStore
die im Grunde eine Karte vonString
to istViewModel
.Wenn
FirstFragment
für eine Instanz von fragtPagerItemViewModel
diemap
leer ist , damitmFactory.create(modelClass)
ausgeführt wird, welches in endetViewModelProviderFactory
.creator.get()
endetDoubleCheck
mit folgendem Code:Das
instance
ist jetztnull
, daher wird eine neue Instanz vonPagerItemViewModel
erstellt und in gespeichertinstance
(siehe // 2).Nun geschieht genau das gleiche Verfahren für
SecondFragment
:PagerItemViewModel
map
jetzt ist nicht leer, aber nicht nicht eine Instanz enthältPagerItemViewModel
mit Schlüsselnfalse
PagerItemViewModel
wird initiiert, um über erstellt zu werdenmFactory.create(modelClass)
ViewModelProviderFactory
Ausführung erreicht,creator.get()
wessen Implementierung istDoubleCheck
Nun, der Schlüsselmoment. Dies
DoubleCheck
ist die gleiche Instanz von ,DoubleCheck
dass für die Erstellung verwendet wurde ,ViewModel
beispielsweise , wennFirstFragment
danach gefragt. Warum ist es die gleiche Instanz? Weil Sie einen Bereich auf die Anbietermethode angewendet haben.Das
if (result == UNINITIALIZED)
(// 1) wird als falsch ausgewertet und genau dieselbe Instanz vonViewModel
wird an den Aufrufer zurückgegeben -SecondFragment
.Jetzt verwenden beide Fragmente dieselbe Instanz von,
ViewModel
daher ist es vollkommen in Ordnung, dass sie dieselben Daten anzeigen.quelle
ViewModel
wird mit dem Lebenszyklus der Aktivität / des Fragments erstellt und zerstört, sobald der Hosting-Lebenszyklus zerstört ist. Sie sollten den Lebenszyklus / die Zerstörung von ViewModel nicht selbst verwalten. Dies tun Architekturkomponenten für Sie als Client dieser API.Beide Fragmente erhalten das Update von Livedata, da Viewpager beide Fragmente im wiederaufgenommenen Zustand hält. Da Sie die Aktualisierung nur für das aktuelle Fragment benötigen, das im Viewpager angezeigt wird, wird der Kontext des aktuellen Fragments durch die Hostaktivität definiert. Die Aktivität sollte Aktualisierungen explizit auf das gewünschte Fragment richten.
Sie müssen eine Zuordnung von Fragment zu LiveData verwalten, die Einträge für alle Fragmente enthält (stellen Sie sicher, dass eine Kennung vorhanden ist, mit der zwei Fragmentinstanzen desselben Fragments unterschieden werden können), die dem Viewpager hinzugefügt wurde.
Jetzt hat die Aktivität eine MediatorLiveData, die die ursprünglichen Livedata beobachtet, die von den Fragmenten direkt beobachtet werden. Immer wenn die ursprünglichen Livedata ein Update veröffentlichen, wird es an mediatorLivedata gesendet, und die mediatorlivedata in Turen senden nur den Wert an Livedata des aktuell ausgewählten Fragments. Diese Livedata werden von der obigen Karte abgerufen.
Code impl würde aussehen wie -
quelle
FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)
also, wie hält der Viewpager beide Fragmente im wieder aufgenommenen Zustand? Dies geschah nicht, bevor ich dem Projekt Dolch 2 hinzufügte.