【发布时间】:2021-04-01 10:20:35
【问题描述】:
错误的视频演示
首先观看视频了解什么是错误:https://imgur.com/a/X9OVraS
我已经模拟了自动切换到 SmistamentoGiroCercaGiroFragment 的代码,返回到 SmistamentoGiroTabFragment 并向数据库添加新记录。在第 5 次插入中,列表已正确更新,但标头计数器未更新并保持值为 4。发生这种情况是因为其特定的 LiveData 未正确触发。
架构
我将 MVVM 方法与 Navigator 一起用于片段导航。
活动
活动不是问题的一部分。它只是初始化导航图,它的生命周期方法 onPause、onDestroy... 永远不会触发,因为您可以从视频中推断出(显然 onCreate 和 onResume 在点击“SMISTAMENTO A GIRO”时被调用)
片段
在这个演示中使用了两个片段,SmistamentoGiroTabFragment(它有子片段作为选项卡,但对于这个问题的目的并不重要)和 SmistamentoGiroCercaGiroFragment。 要从一个切换到一个,需要使用两个操作:
- findNavController().navigate(SmistamentoGiroTabFragmentDirections.actionDashboardToCercaGiro...
- findNavController().navigate(SmistamentoGiroCartellaCarrelloFragmentDirections.actionGiroToDashboard...
所以每次我从一个切换到一个时,都会调用整个生命周期方法:
// Removed old fragment I am leaving
SmistamentoGiroCercaGiroFragment - onPause
SmistamentoGiroCercaGiroFragment - onDestroy
// New fragment just added
SmistamentoGiroTabFragment - onAttach
SmistamentoGiroTabFragment - onCreate
SmistamentoGiroTabFragment - onViewCreated
SmistamentoGiroTabFragment - onResume
反之亦然。
代码
SmistamentoGiroTabFragment
在 onCreateView 中观察到用于更新标头计数器的 LiveDate(出于调试目的,它在布局中使用):
smistamentoGiroTabViewModel.searchLVCount.observe(viewLifecycleOwner, Observer {
Logger.debug("TestGrafico", "searchLVCount $it")
})
SmistamentoGiroTabFragment xml 布局
app:bindInt 只是一个绑定适配器,它接受一个 Int 并转换为 String。为此目的没有什么重要的。
<TextView
android:id="@+id/oggetti"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
app:bindInt="@{viewModel.searchLVCount}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="18" />
SmistamentoGiroTabViewModel
val searchLVCount = smistamentoGiroRepository.searchLVCount()
直接在道中搜索LVCount
@Query("SELECT COUNT(codiceLdV) FROM SmistamentoGiroLV")
fun searchLVCount(): LiveData<Int>
插入的工作原理
片段 SmistamentoGiroTabFragment 从 SmistamentoGiroCercaGiroFragment 接收带有要插入的信息的对象。 所以在 SmistamentoGiroTabFragment.onCreateView 的末尾调用了一个方法:
smistamentoGiroTabViewModel.handleArgs(args, activity().currentView)
这会导致:
return smistamentoGiroLVDao.insert(smistamentoGiroLVEntity)
即在片段观察到的表中插入一条记录:
@Insert(onConflict = OnConflictStrategy.REPLACE)
Long insert(SmistamentoGiroLVEntity element);
调试日志:
您在视频中观看的测试对应的日志如下: "TabWM init" 是 SmistamentoGiroTabViewModel 中的以下日志:
init {
Logger.debug("TestGrafico", "TabWM init")
}
日志:
// First callback when opening the activity
TestGrafico - TabWM init
TestGrafico -
TestGrafico - searchLVCount 1
// First automatic insertion
TestGrafico - TabWM init
TestGrafico -
TestGrafico - searchLVCount 1
TestGrafico - searchLVCount 2
// Second automatic insertion
TestGrafico - TabWM init
TestGrafico -
TestGrafico - searchLVCount 2
TestGrafico - searchLVCount 3
// Third automatic insertion
TestGrafico - TabWM init
TestGrafico -
TestGrafico - searchLVCount 3
TestGrafico - searchLVCount 4
// Fourth automatic insertion
TestGrafico - TabWM init
TestGrafico -
TestGrafico - searchLVCount 4
如你所见,我的预期
TestGrafico - searchLVCount 5
但这并没有触发。
结论
我已经阅读了相关问题Why LiveData observer is being triggered twice for a newly attached observer 和每个答案,但这并没有帮助,因为他们谈论的原因是被触发了两次,而不是为什么有时,它不会被第二次触发。
更新
SmistamentoGiroTabFragment 片段生命周期所有者初始化:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
var currentFragment: Fragment? = null
val binding = FragmentSmistamentoGiroMainBinding.inflate(
inflater, container, false
).apply {
lifecycleOwner = viewLifecycleOwner
ViewModel 是如何初始化的:
SmistamentoGiroTabFragment
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private val smistamentoGiroTabViewModel: SmistamentoGiroTabViewModel by viewModels {
viewModelFactory
}
ViewModelModule(用于 Dagger 注入)
@Binds
abstract fun bindViewModelFactory(factory: MagazzinoViewModelFactory): ViewModelProvider.Factory
@Binds
@IntoMap
@ViewModelKey(SmistamentoGiroTabViewModel::class)
abstract fun bindSmistamentoGiroTabViewModel(smistamentoGiroTabViewModel: SmistamentoGiroTabViewModel): ViewModel
ViewModelKey
@MustBeDocumented
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
MagazzinoViewModelFactory
@Singleton
class MagazzinoViewModelFactory @Inject constructor(
private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val creator = creators[modelClass] ?: creators.entries.firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
@Suppress("UNCHECKED_CAST")
return creator.get() as T
}
}
更新 2
这里是带有 SmistamentoGiroTabViewModel 地址的错误日志
TestGrafico - TabWM init SmistamentoGiroTabViewModel@467a7a4
TestGrafico - searchLVCount 18
TestGrafico - searchLVCount 19
TestGrafico - TabWM init SmistamentoGiroTabViewModel@3585336
TestGrafico - searchLVCount 19
TestGrafico - searchLVCount 20
TestGrafico - TabWM init SmistamentoGiroTabViewModel@3351174
TestGrafico - searchLVCount 20
TestGrafico - searchLVCount 21
TestGrafico - TabWM init SmistamentoGiroTabViewModel@c6c785f
// Bug occoured
TestGrafico - searchLVCount 21
更新 3
observe触发两个值是正确的,因为旧值是fragment/VM初始化,新值是最后调用的handleArgs方法的结果(导致在db中插入记录) onCreateView 如上所述。
【问题讨论】:
-
你能展示你如何初始化视图模型吗?即视图模型的所有者生命周期是谁?
-
我现在就编辑问题。
-
@Y.Kakdas 用您要求的信息更新了问题。
-
您的片段是否被 viewpager 或可缓存的数据结构保存?您是否通过调试检查了viewmodel的地址?我认为当写入相同的值时,以前的片段中有活的视图模型。您可能会开始查看是否由同一视图模型实例引起的重复值的生产者。
-
另外,为了不更新重复值,您可以使用 Transformations.distinctUntilChange(
)
标签: android kotlin android-fragments android-livedata android-architecture-navigation