Android ROOM,insert不会在第一次插入时返回ID,但是会在第二次插入之后返回ID

时间:2020-10-19 11:14:24

标签: kotlin mvvm android-room sql-insert

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

问题

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

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

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

输入数据:

first name = John
last name = Smith

仅出于演示目的,如果我尝试使用此按钮3x插入人员数据(返回的插入ID在日志中),我将在数据库中获得所有3行,名称为John Smith,但第一个插入未捕获行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

已编辑的原始帖子

正如评论中所建议的那样,我正在尝试使用LiveData和Observer的方式,但是我仍然有点卡住。

设置

以下是当前设置。

实体

@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)
    }

ViewModel

我不确定我在这里做的事情是否正确。我在这里分步进行了分解,并创建了单独的变量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()
        }
    }
}

1 个答案:

答案 0 :(得分:1)

您正在启动协程以运行addPerson,然后立即在视图模型中使用当前值为Log调用insertedPersonId。协程将运行,插入人员,并使用插入行的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方法进行更新。希望有道理!一旦掌握了基本发生的情况,事情就没那么复杂了