Android - LiveData 随机不触发更新值(深度问题)

Android - LiveData randomly doesn't trigger with the updated value (in depth question)

错误的视频演示

首先观看视频以了解错误是什么:https://imgur.com/a/X9OVraS

我模拟了自动切换到 SmistamentoGiroCercaGiroFragment 的代码,返回到 SmistamentoGiroTabFragment 并向数据库添加一条新记录。在第 5 次插入中,列表被正确更新,但是 header 计数器没有更新并保持 4 的值。发生这种情况是因为它的特定 LiveData 没有正确触发。

建筑

我将 MVVM 方法与 Navigator 一起用于片段导航。

Activity

activity 不是问题的一部分。它只是初始化导航图及其生命周期方法 onPause、onDestroy... 永远不会被触发,正如您可以从视频中推断的那样(显然 onCreate 和 onResume 被称为单击“SMISTAMENTO A GIRO”的时刻)

片段

此演示中使用了两个片段,SmistamentoGiroTabFragment(已将 child 片段作为选项卡,但对于此问题的目的而言并不重要)和 SmistamentoGiroCercaGiroFragment。 要从一切换到一,使用两个操作:

所以每次我从一个切换到一个时,整个生命周期方法都会被调用:

// Removed old fragment I am leaving
SmistamentoGiroCercaGiroFragment - onPause
SmistamentoGiroCercaGiroFragment - onDestroy
// New fragment just added
SmistamentoGiroTabFragment - onAttach
SmistamentoGiroTabFragment - onCreate
SmistamentoGiroTabFragment - onViewCreated
SmistamentoGiroTabFragment - onResume

反之亦然。

代码

SmistamentoGiroTabFragment

在 onCreateView 中观察到用于更新 header 计数器的 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()

直接在Dao中搜索LVCount

@Query("SELECT COUNT(codiceLdV) FROM SmistamentoGiroLV")
fun searchLVCount(): LiveData<Int>

插入的工作原理

片段 SmistamentoGiroTabFragment 从 SmistamentoGiroCercaGiroFragment 接收到一个 object 以及要插入的信息。 所以在 SmistamentoGiroTabFragment.onCreateView 的末尾调用了一个方法:

smistamentoGiroTabViewModel.handleArgs(args, activity().currentView)

这导致:

return smistamentoGiroLVDao.insert(smistamentoGiroLVEntity)

在片段观察到的table中插入一条记录:

@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

但这并没有触发。

结论

我已经阅读了相关问题 和每个答案,但这没有帮助,因为他们谈论的是被触发两次的原因,而不是为什么有时,它不会被触发第二次。

更新

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

有两个值由观察触发是正确的,因为旧值是 fragment/VM 初始化,新值是 handleArgs 方法调用的结果(导致在 db 中插入记录) onCreateView 的结尾,如上文所述 in-depth。

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

的 Room 数据库的初始化中

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

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

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