给定 UUID 参数时,房间查询总是 returns NULL
Room Query always returns NULL when given UUID argument
我目前正在通过 Big Nerd Ranch Android 编程教科书学习 Room。目前我能够成功查询我的 Crimes table 中的所有 Crimes,但是我只查询一个基于 UUID 的 Crime 总是 return 为 null。但是,我确实知道有四件事有效:
我可以 运行“select * from crime where id = '0f14d0ab-9605-4a62-a9e4-5ed26688389b'” 在我的数据库浏览器中用于 SQLite 和犯罪将 return 在我的数据库中成功。
我可以在我的 CrimeDao 和犯罪将 return 在我的应用程序中成功。
根据日志调试,我的 CrimeFragment 从片段参数中成功获取了 crimeId UUID。
我的 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 文档和代码实验室后弄明白。谢谢!
我目前正在通过 Big Nerd Ranch Android 编程教科书学习 Room。目前我能够成功查询我的 Crimes table 中的所有 Crimes,但是我只查询一个基于 UUID 的 Crime 总是 return 为 null。但是,我确实知道有四件事有效:
我可以 运行“select * from crime where id = '0f14d0ab-9605-4a62-a9e4-5ed26688389b'” 在我的数据库浏览器中用于 SQLite 和犯罪将 return 在我的数据库中成功。
我可以在我的 CrimeDao 和犯罪将 return 在我的应用程序中成功。
根据日志调试,我的 CrimeFragment 从片段参数中成功获取了 crimeId UUID。
我的 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 文档和代码实验室后弄明白。谢谢!