v79
10/18/2022, 7:30 PMv79
10/18/2022, 7:30 PM@Entity
data class Vehicle(val manufacturer: String, val model: String, val odometerReading: Int) {
@PrimaryKey(autoGenerate = true)
var id: Int = 0
}
When I do an insert, I really really want Room to return to me the newly created Vehicle - but the DAO will only return Unit
or the SQLite rowId
. I've tried multiple things, most recently this:
class VehicleRepository(private val vehicleDao: VehicleDao) {
suspend fun insert(vehicle: Vehicle) {
val newVehicleRowId = vehicleDao.insert(vehicle)
vehicle.id = vehicleDao.getVehicleWithRowId(newVehicleRowId)
}
}
And even though I'm calling it from the ViewModel:
fun insert(vehicle: Vehicle): LiveData<Vehicle> {
val newVehicle = MutableLiveData<Vehicle>()
viewModelScope.launch {
repository.insert(vehicle)
selectedVehicle.postValue(vehicle)
}
return newVehicle
}
I get this awful error:
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
v79
10/18/2022, 7:31 PM@PrimaryKey
from an insert operation.v79
10/18/2022, 8:35 PMviewModelScope.launch{}
block, I get the same error:
fun insert(vehicle: Vehicle) {
viewModelScope.launch {
val rowId = repository.insert(vehicle)
val pk = repository.getVehicleIdFromRowId(rowId)
val newVehicle = repository.getVehicleById(pk)
selectedVehicle.postValue(newVehicle.value)
_getVehicleCount()
}
}
I have been through several Room tutorials, but they push me to playing with lists and Flows and so on too quickly, it seems, for my little brain.Peter Farlow
10/18/2022, 9:01 PMPeter Farlow
10/18/2022, 9:02 PMPeter Farlow
10/18/2022, 9:02 PMfun insert(vehicle: Vehicle): LiveData<Vehicle> {
val newVehicle = MutableLiveData<Vehicle>()
viewModelScope.launch(<http://Dispatchers.IO|Dispatchers.IO>) {
repository.insert(vehicle)
selectedVehicle.postValue(vehicle)
}
return newVehicle
}
v79
10/18/2022, 9:33 PMv79
10/19/2022, 5:47 AM@Database(entities = arrayOf(ChargeEvent::class, Vehicle::class), version = 5, exportSchema = false)
abstract class AmberDatabase : RoomDatabase() {
abstract fun chargeEventDao(): ChargeEventDao
abstract fun vehicleDao(): VehicleDao
companion object {
@Volatile
private var INSTANCE: AmberDatabase? = null
fun getDatabase(context: Context, scope: CoroutineScope) : AmberDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AmberDatabase::class.java,
"amber_database")
.addCallback(AmberDatabaseCallback(scope)
).fallbackToDestructiveMigration().build()
INSTANCE = instance
instance
}
}
}
private class AmberDatabaseCallback(private val scope: CoroutineScope) : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
INSTANCE?.let { database ->
scope.launch {
// populate database with fake data? Or other data setup tasks
}
}
}
}
}
v79
10/19/2022, 5:47 AM@Entity
data class Vehicle(val manufacturer: String, val model: String, val odometerReading: Int) {
@PrimaryKey(autoGenerate = true)
var id: Int = 0
}
@Dao
interface VehicleDao {
@Insert
suspend fun insert(vehicle: Vehicle): Long
@Delete
suspend fun delete(vehicle: Vehicle)
@Update
suspend fun update(vehicle: Vehicle)
@Query("SELECT * FROM Vehicle where id = :id LIMIT 1")
fun getVehicle(id: Int): LiveData<Vehicle>
@Query("SELECT COUNT(*) FROM Vehicle")
fun getVehicleCount(): LiveData<Int>
@Query("SELECT MAX(id) FROM Vehicle")
fun getMostRecentVehicleId(): LiveData<Int>
@Query("SELECT id FROM Vehicle WHERE rowId = :rowId")
fun getVehiclePkWithRowId(rowId: Long): LiveData<Int>
}
v79
10/19/2022, 5:47 AMclass VehicleRepository(private val vehicleDao: VehicleDao) {
val vehicleCount = vehicleDao.getVehicleCount()
suspend fun getVehicleById(id: Int): LiveData<Vehicle> = vehicleDao.getVehicle(id)
suspend fun insert(vehicle: Vehicle) = vehicleDao.insert(vehicle)
// vehicle.id = vehicleDao.getVehicleWithRowId(newVehicleRowId)
// }
suspend fun getVehicleCount(): LiveData<Int> = vehicleDao.getVehicleCount()
fun getVehicleIdFromRowId(rowId: Long): LiveData<Int> = vehicleDao.getVehiclePkWithRowId(rowId)
}
v79
10/19/2022, 5:48 AMclass VehicleDetailsViewModel(application: AmberApplication) : ViewModel() {
private val repository: VehicleRepository = application.vehicleRepo
private val _vehicleCount = MutableLiveData<Int>()
val vehicleCount: LiveData<Int>
get() = _vehicleCount
init {
_getVehicleCount()
}
var selectedVehicle: MutableLiveData<Vehicle> = MutableLiveData<Vehicle>()
fun insert(vehicle: Vehicle) {
viewModelScope.launch {
val rowId = repository.insert(vehicle)
val pk = repository.getVehicleIdFromRowId(rowId)
val newVehicle = repository.getVehicleById(pk.value!!)
selectedVehicle.postValue(newVehicle.value)
_getVehicleCount()
}
}
private fun _getVehicleCount() {
viewModelScope.launch {
val vCount = repository.getVehicleCount()
_vehicleCount.value = vCount.value
}
}
private fun getSelectedVehicle(id: Int): LiveData<Vehicle> = liveData {
repository.getVehicleById(id)
}
}
When I change it to viewModelScope.launch(<http://Dispatchers.IO|Dispatchers.IO>)
I get an NPE at val newVehicle = repository.getVehicleById(pk.value)
.v79
10/19/2022, 5:49 AM@Composable
fun VehicleDetailsScreen(navController: NavController, viewModel: VehicleDetailsViewModel) {
val context = LocalContext.current
val totalVehicles by viewModel.vehicleCount.observeAsState()
// layout stuff here.... then
if (totalVehicles == null || totalVehicles == 0) {
AddVehicle(context, viewModel)
} else {
ShowCurrentVehicle(context, viewModel)
}
}
}
}
v79
10/19/2022, 5:51 AMAddVehicle
layout. When I save, it should update the `vehicleCount`and recompose the layout, and it should select the new vehicle as the current vehicle to show it under ShowCurrentVehicle
. But the `vehicleCount`doesn't seem to get updated, and neither does the selected vehicle - both are null by the time it reaches the composable function.v79
10/19/2022, 8:12 PMMutableLiveData
object with the ease of creating a LiveData
object - because after it is created, I will need to update it:
// in ViewModel...
private var _selectedVehicleId: LiveData<Long> = liveData {
val vehicleId = repository.getMostRecentVehicleId()
emit(vehicleId)
}
fun insert(vehicle: Vehicle) {
viewModelScope.launch(<http://Dispatchers.IO|Dispatchers.IO>) {
val rowId = repository.insert(vehicle)
_selectedVehicleId = rowId // // *** THIS LINE ISN'T ALLOWED BECAUSE _selectedVehicleId isn't Mutable. But I've no idea how to create a Mutable object from the result of a suspend function call like repository.getMostRecentVehicleId()
}
}
Peter Farlow
10/19/2022, 11:54 PMprivate var _selectedVehicleId: MutableLiveData<Long> = MutableLiveData()
init {
viewModelScope.launch(<http://Dispatchers.IO|Dispatchers.IO>) {
_selectedVehicle.postValue(repository.getMostRecentVehicleId())
}
fun insert(vehicle: Vehicle) {
viewModelScope.launch(<http://Dispatchers.IO|Dispatchers.IO>) {
val rowId = repository.insert(vehicle)
_selectedVehicleId.postValue(rowId)
}
}
Peter Farlow
10/19/2022, 11:54 PMv79
10/20/2022, 6:21 AMlateinit var selectedVehicle: LiveData<Vehicle?>
private var _selectedVehicleId: MutableLiveData<Long> = MutableLiveData<Long>().also {
viewModelScope.launch(<http://Dispatchers.IO|Dispatchers.IO>) {
val vehicleId = repository.getMostRecentVehicleId()
Log.e("ALSO block","mostRecentVehicleId = $vehicleId")
it.postValue(vehicleId)
}
}
init {
viewModelScope.launch(<http://Dispatchers.IO|Dispatchers.IO>) {
val mostRecentVehicleId = repository.getMostRecentVehicleId()
Log.e("INIT block","mostRecentVehicleId: $mostRecentVehicleId")
_selectedVehicleId.postValue(mostRecentVehicleId)
selectedVehicle = repository.getVehicleById(mostRecentVehicleId)
Log.e("INIT block","selectedVehicle.value.id: ${selectedVehicle.value?.id}")
}
}