Hello helpful community,
I have been working on an application where I am having difficulties understanding how to persist and retrieve a Flow<List>> in a room database. I believe what I wrote in the ViewModel and MainActivity classes is wrong and I cannot figure out the proper way to do it. I tried to persist from the main activity but I receive the following error :
kotlin.UninitializedPropertyAccessException: lateinit property appData has not been initialized at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3822) at
Model
@Entity(tableName="items")
data class (Item
@PrimaryKey @ColumnInfo(name = "id") val itemId: String,
@StringRes val title: Int,
@StringRes val description: Int,
@DrawableRes val drawable: Int
)
Dao
@Dao
interface ItemDao {
@Query("SELECT * FROM items")
fun getItems(): Flow<List<Item>>
@Query("SELECT * FROM items WHERE id=:itemId")
fun getItem(itemId: String): Flow<Item>
@Insert
fun insertItem(item: Item)
@Insert
suspend fun insertAll(items: List<Item>)
}
Repository
@Singleton
class ItemRepository @Inject constructor (
private val itemDao : ItemDao,
) {
fun getItems(): Flow<List<Item>> {
return itemDao.getItems()
}
fun getItem(itemId: String) = itemDao.getItem(itemId)
suspend fun insertItem(item : Item) {
return itemDao.insertItem(item)
}
suspend fun insertItems(item : List<Item>) {
return workoutDao.insertAll(item)
}
ViewModel
@HiltViewModel
class ItemListViewModel @Inject constructor(
private val itemRepository: ItemRepository
) : ViewModel() {
private val _items = MutableStateFlow<List<Item>>(emptyList())
val items = _items.asStateFlow()
init {
loadTask()
}
private fun loadTask() {
viewModelScope.launch() {
itemRepository.getItems()
}
}
DatabaseModule
@InstallIn(SingletonComponent::class)
@Module
object DatabaseModule {
@Singleton
@Provides
fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
DATABASE_NAME
)
.build()
}
@Provides
fun provideItemDao(appDatabase: AppDatabase): ItemDao
= appDatabase.itemDao()
AppDatabase
@Database(entities = [Item::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun itemDao(): ItemDao
ItemListScreen
@Composable
fun ItemListScreen(
onItemClick: (Item) -> Unit,
modifier: Modifier = Modifier,
viewModel: ItemListViewModel = hiltViewModel(),
) {
val items by viewModel.items.collectAsState(initial = emptyList())
ItemListScreen(items = items, modifier, onItemClick = onItemClick)
}
MainActivity
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
private lateinit var appData: AppDatabase
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
FitTestAppTheme {
ItemApp()
}
}
val item = Item("1",R.string.title,R.string.description, R.drawable.image)
if (::appData.isInitialized) {}
appData.itemDao().insertItem(item)
}
}
Error
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.fittestapp/com.example.fittestapp.MainActivity}: kotlin.UninitializedPropertyAccessException: lateinit property appData has not been initialized
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3822)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3963)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:139)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:96)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2468)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:294)
at android.app.ActivityThread.main(ActivityThread.java:8248)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property appData has not been initialized
at com.example.fittestapp.MainActivity.onCreate(MainActivity.kt:41)
at android.app.Activity.performCreate(Activity.java:8621)
at android.app.Activity.performCreate(Activity.java:8599)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1456)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3804)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3963)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:139)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:96)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2468)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:294)
at android.app.ActivityThread.main(ActivityThread.java:8248)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
You are missing
@Injectannotation on your appData, this will tell thehiltto inject AppDatabase from yourDatabaseModule.Edit: Also do not call database operations on main thread, you can allow it by
Room.databaseBuilder().allowMainThreadQueries()or wrap the insert with thread or coroutine.I am not sure if you want to use
FloworStateFlow, both ways are valid and it depends on context of your project, so i implemented both of them:Updated
ItemsDao:Updated
ItemsRepository:Updated
ItemViewModel:Now for the
MainActivityi removedappDataand using viewModel only. It's better to have clean structure of project. I made up collection for botflowandstateFlowitems. After the item is inserted, items are reloaded.Also keep in mind that you can't insert two items with same id, that's why it looks like item is not being inserted. You probably inserted it before.
Updated
MainActivity: