This is the complete code I wrote using artificial intelligence. Where do you think the problem comes from?
there is no error!
entity:
@Entity(tableName = "fixed_info")
data class FixedInfo(
@PrimaryKey val id: Int,
val description: String
)
and my Dao:
@Dao
interface FixedInfoDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(info: FixedInfo)
@Query("SELECT * FROM fixed_info")
suspend fun getAllFixedInfo(): List<FixedInfo>
}
this is my database:
@Database(entities = [FixedInfo::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun fixedInfoDao(): FixedInfoDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database"
)
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
INSTANCE?.let { database ->
CoroutineScope(Dispatchers.IO).launch {
// Add default fixed information
database.fixedInfoDao().insert(FixedInfo(id = 0, description = "Default info"))
database.fixedInfoDao().insert(FixedInfo(id = 2, description = "Default info"))
database.fixedInfoDao().insert(FixedInfo(id = 3, description = "Default info"))
database.fixedInfoDao().insert(FixedInfo(id = 4, description = "Default info"))
}
}
}
})
.build()
INSTANCE = instance
instance
}
}
}
}
and this is my viewModel:
class FixedInfoViewModel(private val fixedInfoDao: FixedInfoDao) : ViewModel() {
var fixedInfoList = mutableListOf<FixedInfo>()
private set
init {
loadFixedInfo()
}
private fun loadFixedInfo() {
viewModelScope.launch {
fixedInfoList = fixedInfoDao.getAllFixedInfo().toMutableList()
}
}
}
mainActivity:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp()
}
}
}
@Composable
fun MyApp() {
val context = LocalContext.current
val fixedInfoDao = AppDatabase.getDatabase(context).fixedInfoDao()
val viewModel: FixedInfoViewModel = viewModel(factory = FixedInfoViewModelFactory(fixedInfoDao))
FixedInfoList(viewModel = viewModel)
}
class FixedInfoViewModelFactory(private val fixedInfoDao: FixedInfoDao) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(FixedInfoViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return FixedInfoViewModel(fixedInfoDao) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
@Composable
fun FixedInfoList(viewModel: FixedInfoViewModel) {
Column(modifier = Modifier.padding(16.dp)) {
viewModel.fixedInfoList.forEach { info ->
Text(text = "Description: ${info.description}")
Spacer(modifier = Modifier.height(8.dp))
}
}
}
I have a simple program that aims to display simple data stored in a room library in compose but when i run the application, it just displays a white screen.
You expose the database entries as a MutableList
in your view model. On startup the UI displays the empty mutableListOf<FixedInfo>()
first, and only then, after some time, loadFixedInfo
replaces that list with a new one containing your database entries. The UI won't update, though, because Compose only detects changes in State
objects.
The solution is to expose data in the view model only as flows which can easily be converted to Compose State
objects.
The first thing to do would be to change your dao query to this:
fun getAllFixedInfo(): Flow<List<FixedInfo>>
The return type is now a flow and Room will automatically update the flow's content when anything changes in the database. When a function returns a flow it usually doesn't need to suspend anymore, so that keyword is removed here as well.
Next, the view model must be refactored. The only thing you need is this:
val fixedInfoList: StateFlow<List<FixedInfo>> = fixedInfoDao.getAllFixedInfo()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList(),
)
Everything else can be removed. What this does is to convert the database flow into a StateFlow
. That is a special kind of flow which always has a single, current value. The semantics are similar to an observable variable, like a LiveData or a MutableState.
That's also the reason why this flow can now easily be converted into a State object in your MyApp
composable:
val fixedInfoList: List<FixedInfo> by viewModel.fixedInfoList.collectAsStateWithLifecycle()
You need the gradle dependency androidx.lifecycle:lifecycle-runtime-compose
for this to work. Please note the by
keyword, that is a delegate that unwraps the State so fixedInfoList
is a List<FixedInfo>
and not a State<List<FixedInfo>>
.
This list can now be passed to the FixedInfoList
composable. Please keep in mind that you shouldn't pass view model instances around, only pass simple (data) objects and callbacks to composables, in this case the unwrapped list from the flow. FixedInfoList
should be declared like this:
@Composable
fun FixedInfoList(fixedInfoList: List<FixedInfo>)
And that's it: Because collectAsStateWithLifecycle
converts the view model data into a State
object, any changes to the data will now trigger a recomposition, updating the UI. This happens, for example, when the database provides the the data on app startup, but it also updates automatically when you would change the data in the database, like calling insert
to add a new entry.