Я пытаюсь добавить Dagger 2 в свой проект. Мне удалось ввести ViewModels (компонент AndroidX Architecture) для моих фрагментов.
У меня есть ViewPager, который имеет 2 экземпляра одного и того же фрагмента (только незначительное изменение для каждой вкладки), и на каждой вкладке я наблюдаю, LiveData
чтобы получать обновления об изменении данных (из API).
Проблема заключается в том, что когда приходит и обновляется ответ API LiveData
, одни и те же данные в видимом в данный момент фрагменте отправляются наблюдателям на всех вкладках. (Я думаю, что это, вероятно, из-за объема ViewModel
).
Вот как я наблюдаю за своими данными:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
activityViewModel.expenseList.observe(this, Observer {
swipeToRefreshLayout.isRefreshing = false
viewAdapter.setData(it)
})
....
}
Я использую этот класс для обеспечения ViewModel
s:
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"
}
}
Я связываю свою ViewModel
вот так:
@Module
abstract class ActivityViewModelModule {
@MainScope
@Binds
@IntoMap
@ViewModelKey(ActivityViewModel::class)
abstract fun bindActivityViewModel(viewModel: ActivityViewModel): ViewModel
}
Я использую @ContributesAndroidInjector
для моего фрагмента, как это:
@Module
abstract class MainFragmentBuildersModule {
@ContributesAndroidInjector
abstract fun contributeActivityFragment(): ActivityFragment
}
И я добавляю эти модули к моему MainActivity
подкомпоненту так:
@Module
abstract class ActivityBuilderModule {
...
@ContributesAndroidInjector(
modules = [MainViewModelModule::class, ActivityViewModelModule::class,
AuthModule::class, MainFragmentBuildersModule::class]
)
abstract fun contributeMainActivity(): MainActivity
}
Вот мой 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
}
}
Я расширяю DaggerFragment
и впрыскиваю ViewModelProviderFactory
так:
@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)
}
key
будет отличаться для обоих фрагментов.
Как я могу убедиться, что только наблюдатель текущего фрагмента обновляется.
РЕДАКТИРОВАТЬ 1 ->
Я добавил пример проекта с ошибкой. Похоже, проблема возникает только при добавлении пользовательской области. Пожалуйста, ознакомьтесь с примером проекта здесь: Github ссылка
master
В филиале есть приложение с проблемой. Если вы обновите какую-либо вкладку (проведите пальцем, чтобы обновить), обновленное значение будет отражено на обеих вкладках. Это происходит только тогда, когда я добавляю в него собственную область ( @MainScope
).
working_fine
Филиал имеет то же приложение без настраиваемой области и работает нормально.
Пожалуйста, дайте мне знать, если вопрос не ясен.
источник
working_fine
филиала? Зачем вам нужен прицел?Ответы:
Я хочу повторить оригинальный вопрос, вот он:
Насколько я понимаю, у вас сложилось впечатление, что только потому, что вы пытаетесь получить экземпляр
ViewModel
использования разных ключей, вам должны быть предоставлены разные экземплярыViewModel
:Реальность немного другая. Если вы добавите следующий фрагмент журнала, вы увидите, что эти два фрагмента используют один и тот же экземпляр
PagerItemViewModel
:Давайте окунемся и поймем, почему это происходит.
Внутренне
ViewModelProvider#get()
будет пытаться получить экземплярPagerItemViewModel
от a,ViewModelStore
который в основном является картойString
дляViewModel
.Когда
FirstFragment
прошу экземпляр пуст, следовательно , выполняются, который заканчивается . в итоге звонит со следующим кодом:PagerItemViewModel
map
mFactory.create(modelClass)
ViewModelProviderFactory
creator.get()
DoubleCheck
instance
Теперьnull
, следовательно , создается новый экземплярPagerItemViewModel
создается и сохраняется вinstance
(см // 2).Теперь точно такая же процедура происходит для
SecondFragment
:PagerItemViewModel
map
сейчас не пусто, но не содержит экземплярPagerItemViewModel
с ключомfalse
PagerItemViewModel
инициируется создание нового экземпляра черезmFactory.create(modelClass)
ViewModelProviderFactory
исполнения достигает,creator.get()
чья реализацияDoubleCheck
Теперь ключевой момент. Это
DoubleCheck
является и тот же экземпляр изDoubleCheck
который был использован для созданияViewModel
экземпляра , когдаFirstFragment
просили об этом. Почему это тот же экземпляр? Потому что вы применили область действия к методу провайдера.if (result == UNINITIALIZED)
(// 1) оценивает ложь , и точно такой же экземплярViewModel
возвращается к вызывающему -SecondFragment
.Теперь оба фрагмента используют один и тот же экземпляр,
ViewModel
поэтому совершенно нормально, что они отображают одни и те же данные.источник
ViewModel
создается с жизненным циклом действия / фрагмента и уничтожается, как только уничтожается его жизненный цикл. Вы не должны управлять жизненным циклом / созданием-разрушением ViewModel самостоятельно, это то, что компоненты архитектуры делают для вас как клиента этого API.Оба фрагмента получают обновление из liveata, потому что viewpager поддерживает оба фрагмента в возобновленном состоянии. Поскольку требуется обновление только для текущего фрагмента, видимого в пейджере, контекст текущего фрагмента определяется действием хоста, действие должно явно направлять обновления на нужный фрагмент.
Вам необходимо сохранить карту Fragment в LiveData, содержащую записи для всех фрагментов (убедитесь, что у вас есть идентификатор, который может различать два экземпляра фрагмента одного и того же фрагмента), добавленный в viewpager.
Теперь у действия будет MediatorLiveData, наблюдающий за исходными живыми данными, наблюдаемыми фрагментами напрямую. Всякий раз, когда исходные liveata публикуют обновление, оно будет доставлено в mediatorLivedata, а mediatorlivedata в turen будет публиковать только значение в liveata текущего выбранного фрагмента. Эта жилпата будет извлечена из карты выше.
Код Impl будет выглядеть так:
источник
FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)
так, как viewpager держит оба фрагмента в возобновленном состоянии? Этого не было до того, как я добавил кинжал 2 в проект.