【问题标题】:Android - LiveData randomly doesn't trigger with the updated value (in depth question)Android - LiveData 不会随机触发更新值(深度问题)
【发布时间】: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


【解决方案1】:

在对项目的架构元素进行了整整一个月的测试和分析之后,我找到了解决方案。在 Room 数据库的初始化中,我使用的是 RoomDatabase.JournalMode.TRUNCATE。

Google LiveData 系统与 Room 数据库日志模式 TRUNCATE 不完全兼容。

这是模式的参考链接: https://developer.android.com/reference/android/arch/persistence/room/RoomDatabase.JournalMode

必须使用的是 WRITE_AHEAD_LOGGING(默认使用)。通过使用它,每一个随机的图形错误都消失了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-09-02
    • 1970-01-01
    • 1970-01-01
    • 2021-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多