You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose+Hilt+Room开发问题:无法创建ViewModel实例及日期存储实现咨询

Fixing Your Jetpack Compose + Hilt + Room Issues

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 20:17:37