给定 UUID 参数时,房间查询总是 returns NULL

Room Query always returns NULL when given UUID argument

我目前正在通过 Big Nerd Ranch Android 编程教科书学习 Room。目前我能够成功查询我的 Crimes table 中的所有 Crimes,但是我只查询一个基于 UUID 的 Crime 总是 return 为 null。但是,我确实知道有四件事有效:

  1. 我可以 运行“select * from crime where id = '0f14d0ab-9605-4a62-a9e4-5ed26688389b'” 在我的数据库浏览器中用于 SQLite 和犯罪将 return 在我的数据库中成功。

  2. 我可以在我的 CrimeDao 和犯罪将 return 在我的应用程序中成功。

  3. 根据日志调试,我的 CrimeFragment 从片段参数中成功获取了 crimeId UUID。

  4. 我的 CrimeDetailViewModel 的 loadCrime() 成功从 CrimeFragment 获取了 crimeId UUID。

但是,从那里开始,@Query("SELECT * FROM crime WHERE id=:id") 总是 return 为空。这意味着我一定没有正确传递 :id,但我不确定哪里出错了。

这是我的设置:

Crime.kt

@Entity(tableName = "Crime")
data class Crime(
    @PrimaryKey val id: UUID = UUID.randomUUID(),
    var title: String = "",
    var date: Date = Date(),
    var isSolved: Boolean = false,
)

CrimeDao.kt

@Dao
interface CrimeDao {
    @Query("SELECT * FROM crime")
    fun getCrimes(): LiveData<List<Crime>>

    @Query("SELECT * FROM crime WHERE id=:id")
    fun getCrime(id: UUID): LiveData<Crime?>
}

CrimeDatabase.kt

@Database(entities = [Crime::class], version = 1)
@TypeConverters(CrimeTypeConverters::class)
abstract class CrimeDatabase : RoomDatabase() {

    abstract fun crimeDao(): CrimeDao
}

CrimeTypeConverters.kt

class CrimeTypeConverters {

    @TypeConverter
    fun fromDate(date: Date?): Long? {
        return date?.time
    }

    @TypeConverter
    fun toDate(millisSinceEpoch: Long?): Date? {
        return millisSinceEpoch?.let {
            Date(it)
        }
    }

    @TypeConverter
    fun toUUID(uuid: String?): UUID? {
        return UUID.fromString(uuid)
    }

    @TypeConverter
    fun fromUUID(uuid: String?): String? {
        return uuid?.toString()
    }
}

CrimeRepository.kt

private const val DATABASE_NAME = "crime-database"
class CrimeRepository private constructor(context: Context) {

    private val database : CrimeDatabase = Room.databaseBuilder(
        context.applicationContext,
        CrimeDatabase::class.java,
        DATABASE_NAME
    ).createFromAsset("database/crime-database.db")
        .fallbackToDestructiveMigration()
        .build()

    private val crimeDao = database.crimeDao()

    fun getCrimes(): LiveData<List<Crime>> = crimeDao.getCrimes()

    fun getCrime(id: UUID): LiveData<Crime?> = crimeDao.getCrime(id)

    ...
}

CrimeFragment.kt

private const val ARG_CRIME_ID = "crime_id"
class CrimeFragment : Fragment() {

    private lateinit var crime: Crime
    private lateinit var titleField: EditText
    private lateinit var dateButton: Button
    private lateinit var solvedCheckBox: CheckBox
    private val crimeDetailViewModel: CrimeDetailViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        crime = Crime()
        val crimeId: UUID = arguments?.getSerializable(ARG_CRIME_ID) as UUID
        Log.d(TAG, "args bundle crime ID: $crimeId") // this prints the crimeId successfully
        crimeDetailViewModel.loadCrime(crimeId)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_crime, container, false)

        titleField = view.findViewById(R.id.crime_title) as EditText
        dateButton = view.findViewById(R.id.crime_date) as Button
        solvedCheckBox = view.findViewById(R.id.crime_solved) as CheckBox

        dateButton.apply {
            text = CrimeDate.format(crime.date)
            isEnabled = false
        }

        return view
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        Log.d(TAG, "onViewCreated")
        crimeDetailViewModel.crimeLiveData.observe(
            viewLifecycleOwner,
            Observer { crime ->
                Log.d(TAG, "crime observed: $crime") // this claims that crime is null
                crime?.let {
                    Log.d(TAG, "found crime ${crime.title} ${crime.id}") // since crime is null, this let block does not run
                    this.crime = crime
                    updateUI()
                }
            }
        )
    }

    private fun updateUI() {
        titleField.setText(crime.title)
        dateButton.text = crime.date.toString()
        solvedCheckBox.apply {
            isChecked = crime.isSolved
            jumpDrawablesToCurrentState()
        }
        Log.d(TAG, "$crime update UI")
    }

    companion object {

        /**
         * Attach arguments to CrimeFragment before it is added to an activity.
         */
        fun newInstance(crimeId: UUID): CrimeFragment {
            val args = Bundle().apply {
                putSerializable(ARG_CRIME_ID, crimeId)
            }
            return CrimeFragment().apply {
                arguments = args
            }
        }
    }
}

CrimeDetailViewModel.kt

class CrimeDetailViewModel : ViewModel() {

    private val crimeRepository = CrimeRepository.get()
    private val crimeIdLiveData = MutableLiveData<UUID>()

    var crimeLiveData: LiveData<Crime?> =
        Transformations.switchMap(crimeIdLiveData) { crimeId ->
            crimeRepository.getCrime(crimeId)
        }

    fun loadCrime(crimeId: UUID) {
        crimeIdLiveData.value = crimeId
        Log.d(TAG, "${crimeId}") // this successfully prints out the crimeId
    }
}

您应该看到,存储库中的 getCrime 方法正在返回实时数据。在您的视图模型中,您正在使用

    var crimeLiveData: LiveData<Crime?> =
    Transformations.switchMap(crimeIdLiveData) { crimeId ->
        crimeRepository.getCrime(crimeId)
    }

执行此操作时,将调用您的存储库方法,但由于它是实时数据,因此您不会在那里获得值。

你可以做的是,你可以直接观察viewmodel方法,比如

fun loadCrime(crimeId: UUID) = crimeRepository.getCrime(crimeId)

然后像

一样直接观察片段中的函数
crimeDetailViewModel.loadCrime(crimeId).observe {
   // Whatever should go inside this.
}

此外,如果您使用的是 Kotlin 协同程序,您还可以在存储库和 dao 方法中使用挂起函数,在视图模型中,您可以获取值并将值设置为实时数据。 那会是这样的,

fun loadCrime(crimeId: UUID) {
    val response = crimeRepository.getCrime(crimeId)
    crimeLiveData.value = response
}

然后在你的片段中观察它。

还有一条信息:这本书显然使用了过时的做法(因为 Android 总是由 Google 更新),所以我学习的一部分是看看我是否可以改用最新的依赖项。我基本上只是继续使用所有依赖项的早期版本,这似乎奏效了。不幸的是,我无法以其他方式让它工作,但我想我会在阅读整本教科书然后搜索 Android 文档和代码实验室后弄明白。谢谢!