I think I already figured out that I need to save something on onSaveInstanceState
somewhere, but I don't know what and how. I'm guessing it's related to the database because Room was mentioned on the error.
My app is using NavGraph
to map the screens, so I don't know if I should save all my Fragment
s one by one or there's some NavGraph
related solution that I could use. And for the ViewModel
s, I'm already using something that looks like:
private val actDevInfVM: ActuatorDeviceInfoViewModel by viewModels {
ActuatorDeviceInfoViewModel.ActuatorDeviceInfoViewModelFactory((ctx.application as MyApp).actuatorDeviceInfoRepo)
}
to load the ViewModel
s on a Fragment
or an Activity
, I tried replacing it with:
private val actDevInfVM: ActuatorDeviceInfoViewModel by navGraphViewModels(R.id.main_nav_graph) {
defaultViewModelProviderFactory
}
But I got a different set of errors, but for another ViewModel
it seems. I got an error that looks like:
Cannot create an instance of class com.my.package.name.viewmodel.SensorViewModel
My ViewModel
s looks like:
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"
}
}
Then on MyApp
, I have this code:
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()
is called on MainActivity
s onDestroy()
Then this is what AppDatabase
looks like:
@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
}
}
}
}
Not sure if this answers your question, but to avoid activity to reload when screen rotates, add following in your Manifest:
<activity>
(your activity)
android:configChanges="keyboardHidden|orientation|screenSize">
</activity>
To do something when screen orientation changes:
@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();
} }