【问题标题】:Android ROOM, insert won't return ID with first insert, but will return with 2nd insert onwardsAndroid ROOM,插入不会在第一次插入时返回 ID,但会在第二次插入时返回
【发布时间】:2020-10-19 11:14:24
【问题描述】:

学校项目,我对 Android 开发还很陌生。

问题

我在 保存人员片段 中有一个带有 onClick 侦听器的按钮,它将人员数据保存到数据库中。一切正常,除了出于某种原因第一次点击它不会返回插入的行 ID,但它会在第二次点击之后返回。

在继续下一个片段之前,我真的需要这个 ID。

不确定这是否重要,但每当我返回(重新加载)此保存人员片段时,行为始终相同,即第一次点击始终无法捕获插入的行 ID。 p>

输入数据:

first name = John
last name = Smith

仅出于演示目的,如果我尝试使用此按钮 3 次插入人员数据(返回的插入 ID 在日志中),我将在数据库中获取名称为 John Smith 的所有 3 行,但第一个插入的未捕获行ID(默认初始化值为0),请参阅下面的日志:

日志

2020-10-19 12:49:20.320 25927-25927/ee.taltech.mobile.contacts D/TEST_ADD_PERSON_ID: insertedPersonId: 0
2020-10-19 12:49:40.153 25927-25927/ee.taltech.mobile.contacts D/TEST_ADD_PERSON_ID: insertedPersonId: 5
2020-10-19 12:49:40.928 25927-25927/ee.taltech.mobile.contacts D/TEST_ADD_PERSON_ID: insertedPersonId: 6

已编辑的原始帖子

按照 cmets 的建议,我正在尝试使用 LiveData 和观察者的方式,但我仍然有点卡住。

设置

以下是当前设置。

实体

@Entity(tableName = "person")
data class Person(

    @PrimaryKey(autoGenerate = true)
    val id: Int,

DAO

@Dao
interface PersonDao {

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun addPerson(person: Person): Long

存储库

class PersonRepository(private val personDao: PersonDao) {

    val readAllPersonData: LiveData<List<Person>> = personDao.readAllPersonData()

    suspend fun addPerson(person: Person): Long {
        return personDao.addPerson(person)
    }

视图模型

我不确定我在这里做的是否正确。我在这里逐步分解并创建了单独的变量insertedPersonILiveDatainsertedPersonId。 如何将返回的行 id 传递给insertedPersonILiveData

class PersonViewModel(application: Application) : AndroidViewModel(application) {
        var insertedPersonILiveData: LiveData<Long> = MutableLiveData<Long>()
        var insertedPersonId: Long = 0L
    
        val readAllPersonData: LiveData<List<Person>>
        private val repository: PersonRepository
    
        init {
            val personDao = ContactDatabase.getDatabase(application).personDao()
            repository = PersonRepository(personDao)
            readAllPersonData = repository.readAllPersonData
        }
    
        suspend fun addPerson(person: Person) = viewModelScope.launch {
            insertedPersonId = repository.addPerson(person)
        // ****************************************************************       
        // insertedPersonILiveData = insertedPersonId (what to do here) ???
        // ****************************************************************       
        }

保存人物片段 这就是我通过 modelView 调用 addPerson 的方式。

val person = Person(0, firstName, lastName)

lifecycleScope.launch {
    personViewModel.addPerson(person)
}
Log.d("TEST_ADD_PERSON_ID","insertedPersonId: ${personViewModel.insertedPersonId}")

这就是我做观察者的方式(不确定它是否正确)。

val returnedIdListener: LiveData<Long> = personViewModel.insertedPersonILiveData
returnedIdListener.observe(viewLifecycleOwner, Observer<Long> { id: Long ->
    goToAddContactFragment(id)
})


private fun goToAddContactFragment(id: Long) {
    Log.d("TEST_ADD_PERSON_ID", "id: " + id)
}

创建数据库

@Database(
    entities = [Person::class, Contact::class, ContactType::class],
    views = [ContactDetails::class],
    version = 1,
    exportSchema = false
)
abstract class ContactDatabase : RoomDatabase() {

    abstract fun personDao(): PersonDao
    abstract fun contactTypeDao(): ContactTypeDao
    abstract fun contactDao(): ContactDao
    abstract fun contactDetailsDao(): ContactDetailsDao

    companion object {

        // For Singleton instantiation
        @Volatile
        private var instance: ContactDatabase? = null

        fun getDatabase(context: Context): ContactDatabase {
            return instance ?: synchronized(this) {
                instance ?: buildDatabase(context).also { instance = it }
            }
        }

        private fun buildDatabase(context: Context): ContactDatabase {
            return Room.databaseBuilder(context, ContactDatabase::class.java, "contacts_database")
                .addCallback(
                    object : RoomDatabase.Callback() {
                        override fun onCreate(db: SupportSQLiteDatabase) {
                            super.onCreate(db)
                            val request = OneTimeWorkRequestBuilder<SeedDatabaseWorker>().build()
                            WorkManager.getInstance(context).enqueue(request)
                        }
                    }
                )
                .build()
        }
    }
}

【问题讨论】:

  • 同一个Fragment好像有多个ViewModel,为什么会这样呢?如果您在同一个 ViewModel 中同时拥有人员和联系人存储库,则不需要通过 Fragment 传递 id
  • @Stachu 很好,我之前只是在玩一些东西,然后忘记了。我已将其从代码中删除。也相应地更新了帖子。作为记录,我每个片段只使用 1 个模型视图。不管怎样,问题依然存在。它不会在第一次点击时捕获 id。有什么想法吗?
  • 我的猜测是日志在insertedPersonId 更新之前运行,所以即使它实际上已经运行了两次,你也只能看到更新前的值。您可以将 insertedPersonId 设置为 LiveData 并使用观察者触发更改。另一种选择是在 viewmodel 中使 addPerson 成为挂起函数,然后从例如调用它。 addPersonAndProcessId。什么更适合你?
  • @Stachu 我真的很想学习并了解如何使用观察者方法(必须学习它)。现在我正在努力理解如何在PersonViewModel 类中设置insertedPersonId。我的意思是var insertedPersonId: LiveData&lt;Long&gt; = ... 这里发生了什么?如何初步评估它?
  • 会是这样吗? var insertedPersonId: LiveData&lt;Long&gt; = MutableLiveData&lt;Long&gt;()

标签: kotlin mvvm android-room sql-insert


【解决方案1】:

您正在启动一个协程来运行addPerson,然后立即在视图模型中使用insertedPersonId 的当前值调用Log。协程将运行,插入人员,并使用插入行的 ID 更新 VM,但这将在您的 Log 运行很久之后发生。可能所有结果实际上都是插入的最后一条记录的 ID。

我对这方面也很陌生,但根据你现在所拥有的,我认为你只需要添加

insertedPersonILiveData.value = insertedPersonId

在您的 addPerson 函数中。这样,您将使用新值更新 LiveData,该值将被推送给任何有效的观察者。您已经编写了一些代码来观察 LiveData 实例,因此它应该在您设置它时得到更新。


编辑您的问题是 insertedPersonILiveData 是不可变的 LiveData 类型,因此您无法在其上设置值 - 它是只读的。您正在创建一个MutableLiveData 对象,但您将其公开为LiveData 类型。

为此推荐的模式是将 mutable 创建为内部对象,将对其的引用公开为 immutable 类型,并创建一个更改的 setter 方法通过可变引用的值(它可以在内部访问)

class myViewModel : ViewModel() {
    // mutable version is private, all updates go through the setter function
    // (the _ prefix is a convention for "private versions" of data fields)
    private val _lastInsertedPersonId = MutableLiveData<Long>()
  
    // we're making the instance accessible (for observing etc), but as
    // the immutable LiveData supertype that doesn't allow setting values
    val lastInsertedPersonId: LiveData<Long> = _lastInsertedPersonId

    // setting the value on the MutableLiveData instance
    // is done through this public function
    fun setLastInsertedPersonId(id: Long) {
        _lastInsertedPersonId.value = id
    }
}

然后你的观察者只需调用lastInsertedPersonId.observe,你不需要复制LiveData并观察它(就像你对returnedIdListener所做的那样。

这就是那里的基本模式 - 内部 MutableLiveData,公开公开为不可变的 LiveData val,并使用 setter 方法更新值。视图模型之外的所有内容要么观察可见的LiveData,要么调用setter 方法进行更新。希望这是有道理的!一旦你了解了基本情况,这并不复杂

【讨论】:

  • 不幸的是,insertedPersonILiveData.value = insertedPersonId 是不允许的,因为根据消息:Cannot assign to 'value': the setter is protected/*protected and package*/ for synthetic extension in '&lt;library Gradle: androidx.lifecycle:lifecycle-livedata-core:2.2.0@aar&gt;' 还有其他想法吗?
  • 哦,这是因为您已将该字段设置为不可变的 LiveData 类型(即使您为其分配了 MutableLiveData 实例),因此该类型没有设置器。我发布了一个编辑,解释了所有这些是如何工作的
  • 嘿伙计。非常感谢。我非常感谢你花时间帮助我。我不知道该怎么感谢你才足够。我相信其他人也会觉得这很有帮助。干杯
猜你喜欢
  • 2020-03-30
  • 1970-01-01
  • 2023-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多