kotlin 获取“没有这样的表:room_table_modification_log”应用程序旋转时

cig3rfwq  于 2023-10-23  发布在  Kotlin
关注(0)|答案(2)|浏览(141)

我想我已经知道我需要在onSaveInstanceState的某个地方保存一些东西,但是我不知道是什么以及如何保存。我猜它与数据库有关,因为错误中提到了房间。
我的应用程序使用NavGraph来Map屏幕,所以我不知道我是否应该逐个保存所有的Fragment,或者我可以使用一些NavGraph相关的解决方案。对于ViewModel s,我已经使用了类似的东西:

private val actDevInfVM: ActuatorDeviceInfoViewModel by viewModels {
    ActuatorDeviceInfoViewModel.ActuatorDeviceInfoViewModelFactory((ctx.application as MyApp).actuatorDeviceInfoRepo)
}

要在FragmentActivity上加载ViewModel s,我尝试将其替换为:

private val actDevInfVM: ActuatorDeviceInfoViewModel by navGraphViewModels(R.id.main_nav_graph) {
    defaultViewModelProviderFactory
}

但我得到了一组不同的错误,但对于另一个ViewModel似乎。我得到一个错误,看起来像:
无法创建类com.my.package.name.viewmodel.SensorViewModel的示例
我的ViewModel s看起来像:

class ActuatorDeviceInfoViewModel(private val repo: ActuatorDeviceInfoRepo) : ViewModel(),
    IViewModel<ActuatorDeviceInfo> {
    private val _items = MutableStateFlow<List<ActuatorDeviceInfo>>(listOf())
    override val items: StateFlow<List<ActuatorDeviceInfo>> = _items

    fun fetchAll() {
        viewModelScope.launch(Dispatchers.Main) {
            repo.getAllSub
                .flowOn(Dispatchers.IO)
                .catch { exception -> exception.localizedMessage?.let { Log.e("TAG", it) } }
                .stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
                .collect { _items.value = it }
        }
    }

    fun getAll(): StateFlow<List<ActuatorDeviceInfo>> {
        return _items
    }

    fun getAllLst(): List<ActuatorDeviceInfo> {
        return repo.getAllLst()
    }

    fun getAllWithEdgeDeviceId(edgeDeviceId: String): List<ActuatorDeviceInfo> {
        return runBlocking {
            repo.getAllWithEdgeDeviceId(edgeDeviceId)
        }
    }

    fun insert(item: ActuatorDeviceInfo) = viewModelScope.launch {
        repo.insert(item)
    }

    override fun insertReturnId(item: ActuatorDeviceInfo): Long {
        return runBlocking {
            repo.insertReturnId(item)
        }
    }

    override fun update(item: ActuatorDeviceInfo) = viewModelScope.launch {
        repo.update(item)
    }

    override fun insertOrUpdate(item: ActuatorDeviceInfo) = viewModelScope.launch {
        repo.insertOrUpdate(item)
    }

    fun delete(item: ActuatorDeviceInfo) = viewModelScope.launch {
        repo.delete(item)
    }

    class ActuatorDeviceInfoViewModelFactory(private val repo: ActuatorDeviceInfoRepo) :
        ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            if (modelClass.isAssignableFrom(ActuatorDeviceInfoViewModel::class.java)) {
                @Suppress("UNCHECKED_CAST")
                return ActuatorDeviceInfoViewModel(repo) as T
            }
            throw IllegalArgumentException("Unknown VieModel Class")
        }
    }

    companion object {
        const val TAG = "ActuatorDeviceInfoViewModel"
    }
}

然后在MyApp上,我有这样的代码:

class MyApp : Application() {
    private val applicationScope = CoroutineScope(SupervisorJob())
    
    val actuatorDeviceInfoRepo by lazy { ActuatorDeviceInfoRepo(database.actuatorDeviceInfoDao()) }
    val sensorRepo by lazy { SensorRepo(database.sensorDao()) }
    ...

    fun dbClose() {
        database.close()
    }
}

dbClose()MainActivity s onDestroy()上调用
下面是AppDatabase的样子:

@Database(
    entities = [
        ActuatorDeviceInfo::class,
        Sensor::class,
        ... a few more data classes ...
    ], version = 1, exportSchema = false
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
    abstract fun actuatorDeviceInfoDao(): ActuatorDeviceInfoDao
    abstract fun sensorDao(): SensorDao
    ... a few more dao ...

    companion object {

        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getDatabase(context: Context, scope: CoroutineScope): AppDatabase {
            val queryInterceptor = LoggingQueryInterceptor()
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "some_db_name"
                )
                    .addCallback(AppDbCallback(scope))
                    .setQueryCallback(queryInterceptor, Executors.newSingleThreadExecutor())
                    .build()

                INSTANCE = instance

                instance
            }
        }
    }
}
w6lpcovy

w6lpcovy1#

不确定这是否回答了你的问题,但为了避免屏幕旋转时重新加载活动,请在清单中添加以下内容:

<activity>
(your activity)
android:configChanges="keyboardHidden|orientation|screenSize">
</activity>

要在屏幕方向更改时执行某些操作,请执行以下操作:

@Override public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    Toast.makeText(this, "config changed", Toast.LENGTH_SHORT).show();

    // Checks the orientation of the screen
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
    } }
r6hnlfcb

r6hnlfcb2#

dbClose()在MainActivitys onDestroy()上调用
所以,你让no such table: room_table_modification_log旋转的原因是因为onDestroy()生命周期回调在设备方向改变时被系统触发;这意味着每次方向改变,数据库都将关闭,这将是很麻烦的,因为:

  • 打开和关闭数据库不是一个轻量级的操作,可能会消耗设备资源。
  • 如果数据库非常大,并且您试图在正确打开数据库之前访问数据库,则可能会出现异常;甚至在获取所需数据之前,这可能会导致延迟和糟糕的用户体验。
  • 方向是一个小的时间在整个应用程序的生命周期,以做繁重的操作。
  • 一般来说,没有必要担心关闭SQLite数据库(或其抽象,Room)。查看12问题。
    因此,根据您的情况推荐一种:
  • 保持数据库打开,不要担心,特别是如果它与应用程序的生命周期有关。
  • 如果数据库与组件生命周期(例如Activity、Fragment、Service..等)相关联,则在销毁数据库时将其关闭,但必须将其与使用isFinishing()的设备定向情况区别开来,例如:
override fun onDestroy() {
    if(isFinishing)
        Log.d("TAG", "onDestroy: Finishing... you can close the database")
    else
        Log.d("TAG", "onDestroy: something else like configuration change, keep the database open")
    super.onDestroy()
}

相关问题