Jetpack Compose+Hilt+Room开发问题:无法创建ViewModel实例及日期存储实现咨询
Hey there! Let's work through your two problems one by one—first resolving the ViewModel instantiation error, then getting your date storage working correctly with Room and Hilt.
1. Fixing the "Cannot create an instance of class ScheduleScreenVM" Error
This error usually happens when Hilt can't properly inject dependencies into your ViewModel. Let's fix the root causes:
a. Add @Inject constructor to Your Use Cases
Your AddDaysChoosen (and likely GetDaysChoosen/DeleteDaysChoosen) use cases are missing the @Inject constructor annotation. Hilt needs this to automatically inject the DaysChoosenRepository dependency:
class AddDaysChoosen @Inject constructor( private val repository: DaysChoosenRepository ) { suspend operator fun invoke(days: DaysChoosenEntity){ repository.insertDaysChoosen(days) } } // Repeat this for GetDaysChoosen and DeleteDaysChoosen too!
b. Ensure Your Application Class Uses @HiltAndroidApp
Hilt requires you to annotate your base Application class to initialize the dependency graph:
@HiltAndroidApp class MyApp : Application() { // No extra code needed here unless you have custom setup }
Don't forget to reference this class in your AndroidManifest.xml:
<application android:name=".MyApp" <!-- rest of your manifest code --> >
c. Fetch ViewModel Correctly in Compose
In your Compose screen, use hiltViewModel() to get the ViewModel instance (this ensures Hilt handles injection):
@Composable fun ScheduleScreen() { val viewModel: ScheduleScreenVM = hiltViewModel() // Rest of your UI code }
Avoid using ViewModelProvider directly—hiltViewModel() is the correct way with Hilt + Compose.
2. Correctly Implementing Date Data Storage
Your current Room setup stores individual 0/1 values for each day, which leads to messy, redundant data. Let's restructure this to store a single set of selected days:
a. Redesign the Room Entity
Instead of storing individual entries for each day, store one entity that holds all selected days. We'll use a fixed id to ensure we only have one entry in the database:
@Entity() data class DaysChoosenEntity( @PrimaryKey val id: Int = 1, // Fixed ID to maintain one entry val selectedDays: List<Int> // Stores indices of selected days (0-6 for Sun-Sat) )
b. Update DAO for Single Entry Access
Adjust your DAO to work with the single entity:
@Dao interface DaysChoosenDAO { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertSelectedDays(days: DaysChoosenEntity) @Query("SELECT * FROM dayschoosenentity WHERE id = 1") fun getSelectedDays(): Flow<DaysChoosenEntity?> // Nullable if no data exists yet }
c. Update Repository and Use Cases
Modify your repository to match the new DAO methods:
class DaysChoosenRepositoryImpl( private val dao: DaysChoosenDAO ): DaysChoosenRepository { override fun getDaysChoosen(): Flow<DaysChoosenEntity?> { return dao.getSelectedDays() } override suspend fun insertDaysChoosen(days: DaysChoosenEntity) { dao.insertSelectedDays(days) } // Update DeleteDaysChoosen if needed, or remove it if you don't need to delete the entry }
Update your use cases to work with the new entity type as well (don't forget @Inject constructor!).
d. Refactor ViewModel Logic
Update your ViewModel to load saved state from the database and automatically save changes when days are selected:
@HiltViewModel class ScheduleScreenVM @Inject constructor ( private val daysChoosenUseCase: DaysChoosenUseCase ): ViewModel() { // Track selected days state var rememem by mutableStateOf( SnapshotStateList<Boolean>().also { repeat(7) { it.add(false) } } ) // Expose saved days to UI as State val savedSelectedDays: State<DaysChoosenEntity?> = daysChoosenUseCase.getDaysChoosen() .collectAsStateWithLifecycle(null, viewModelScope) // Load saved state on ViewModel initialization init { viewModelScope.launch { savedSelectedDays.value?.selectedDays?.forEach { dayIndex -> if (dayIndex in 0..6) { rememem[dayIndex] = true } } } } // Handle day selection toggle fun snapChanged(dayIndex: Int) { rememem[dayIndex] = !rememem[dayIndex] // Auto-save changes to database saveSelectedDays() } private fun saveSelectedDays() = viewModelScope.launch { val selectedIndices = rememem.mapIndexed { index, isSelected -> index.takeIf { isSelected } }.filterNotNull() daysChoosenUseCase.addDaysChoosen( DaysChoosenEntity(selectedDays = selectedIndices) ) } }
e. Update Compose UI
Now your UI can interact with the ViewModel cleanly, and display saved days:
@Composable fun ScheduleScreen() { val viewModel: ScheduleScreenVM = hiltViewModel() // Display saved selected days val savedDays = viewModel.savedSelectedDays.value?.selectedDays ?: emptyList() Text("Saved Days: ${savedDays.joinToString(", ")}") // Example DaysOfWeekButton usage repeat(7) { day -> DaysOfWeekButton( onClick = { viewModel.snapChanged(day) }, isSelected = viewModel.rememem[day] ) } }
3. Additional Dependency Tweaks
Add the lifecycle-runtime-compose dependency to use collectAsStateWithLifecycle (it's the recommended way to collect flows in Compose):
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.6.1"
Also consider updating your coroutine and Room dependencies to the latest stable versions to avoid bugs.
内容的提问来源于stack exchange,提问作者Masson_R

