Jetpack Compose + Hilt:java.lang.RuntimeException:无法创建 class ViewModel 的实例
Jetpack Compose + Hilt: java.lang.RuntimeException: Cannot create an instance of class ViewModel
我最近开始尝试使用 jetpack Compose,并为我的应用程序使用了 hilt 和 hilt-navigation-compose。它适用于第一个 ViewModel。但是,当我在第二个屏幕的另一个 ViewModel 上尝试相同的代码时,它总是崩溃并显示 java.lang.RuntimeException:无法创建 class 的实例。但是如果我在它的构造函数中去掉参数repository(Injected using Hilt),就可以初始化成功了。
目前在我的导航图中有四个screens.HOME,RECORD,SETTING screen可以通过BottomBar导航到,它们的viewModel可以正常工作;NEW screen可以通过FloatingActionButton导航到,但是它的viewModel不能像其他人一样创造。我无法弄清楚问题所在。这是我的后台跟踪:
java.lang.RuntimeException: Cannot create an instance of class com.eynnzerr.cpbookkeeping_compose.ui.new.NewViewModel
...
Caused by: java.lang.InstantiationException: java.lang.Class<com.eynnzerr.cpbookkeeping_compose.ui.new.NewViewModel> has no zero argument constructor
似乎 Hilt 只知道如何使用无参数构造函数创建 NewViewModel 而忽略了
它的参数库?
这是我的代码:
(1)Hilt应用class:
@HiltAndroidApp
class CPApplication: Application() {
override fun onCreate() {
super.onCreate()
context = applicationContext
}
companion object {
@SuppressLint("StaticFieldLeak")
lateinit var context: Context
}
}
(2)主要活动:
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@RequiresApi(Build.VERSION_CODES.N)
@ExperimentalAnimationApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BookkeepingApp()
}
}
}
@ExperimentalAnimationApi
@Composable
fun BookkeepingApp() {
CPBookkeepingcomposeTheme {
val navController = rememberNavController()
BasicScreen(
navController = navController
)
}
}
@ExperimentalAnimationApi
@Composable
fun BasicScreen(
navController: NavController
) {
...
Scaffold(
topBar = {
//Change according to currentScreen in composable.
CPTopBar(currentScreen)
},
floatingActionButton = {
//only show up when it's HOME screen.
if(currentScreen.value == Destinations.HOME_ROUTE) {
DraggableFloatingButton(
onClick = {
navController.navigateTo(Destinations.NEW_ROUTE)
}
)
} else Unit
},
bottomBar = {
//only show up when it's HOME, RECORD, SETTING screens.
AnimatedVisibility(listState.isScrollingUp()) {
when(currentScreen.value) {
Destinations.HOME_ROUTE,
Destinations.RECORD_ROUTE,
Destinations.SETTING_ROUTE -> FlutterNavigation(navController = navController, items)
else -> Unit
}
}
}
) {
NavGraph(
navController = navController as NavHostController,
listState = listState
)
}
}
(3)导航图:
@Composable
fun NavGraph(
navController: NavHostController = rememberNavController(),
listState: LazyListState = rememberLazyListState(),
startDestination: String = Destinations.HOME_ROUTE
) {
NavHost(navController = navController, startDestination = "home") {
composable(Destinations.HOME_ROUTE){
//can create ViewModel.
val homeViewModel: HomeViewModel = hiltViewModel()
...
}
...
composable(Destinations.NEW_ROUTE){
//cannot create ViewModel?
val newViewModel: NewViewModel = hiltViewModel()
...
}
}
}
(4)HomeViewModel(效果很好):
data class HomeUiState(
val billsToday: List<Bill> = emptyList(),
val homeData: HomeData = HomeData()
)
@HiltViewModel
class HomeViewModel @Inject constructor(
private val billRepository: BillRepositoryImpl
): ViewModel() {
private val _uiState = MutableStateFlow(HomeUiState())
val uiState: StateFlow<HomeUiState> = _uiState
init {
viewModelScope.launch {
_uiState.update { it.copy(homeData = getAllData(0f)) }
billRepository.getBillsFlow().collect { bills ->
_uiState.update { it.copy(billsToday = bills) }
}
}
}
}
(5)NewViewModel(除了删除billRepository无法创建):
data class TabState(
val amount: String = "¥0",
val selectedIndex: Int = 0
)
@HiltViewModel
class NewViewModel @Inject constructor(
private val billRepository: BillRepositoryImpl //After deleting this, it can work properly
) : ViewModel() {
//uiState for expenseTab
private val _exTabState = MutableStateFlow(TabState())
val exTabState: StateFlow<TabState> = _exTabState.asStateFlow()
//uiState for revenueTab
private val _reTabState = MutableStateFlow(TabState())
val reTabState: StateFlow<TabState> = _reTabState.asStateFlow()
private val _remarkState = MutableStateFlow("add remark")
val remarkState: StateFlow<String> = _remarkState.asStateFlow()
fun updateRemark(remark: String) {
_remarkState.update { remark }
}
fun updateTab(category: Int, amount: String, selectedIndex: Int) {
when(category) {
-1 -> _exTabState.update { it.copy(amount = amount, selectedIndex = selectedIndex) }
1 -> _reTabState.update { it.copy(amount = amount, selectedIndex = selectedIndex) }
}
}
fun insertBill(bill: Bill) {
viewModelScope.launch {
billRepository.insertBills(bill)
}
}
}
(6)billRepository:
class BillRepositoryImpl @Inject constructor() : BillRepository {
private val billDao = BillDatabase.getInstance(context).getDao()
override suspend fun getBillsFlow(): Flow<List<Bill>> = billDao.getAllBills()
override suspend fun insertBills(vararg bill: Bill) = billDao.insertBills(*bill)
override suspend fun deleteBills(vararg bill: Bill) = billDao.deleteBills(*bill)
override suspend fun updateBills(vararg bill: Bill) = billDao.updateBills(*bill)
}
interface BillRepository {
suspend fun getBillsFlow(): Flow<List<Bill>>
suspend fun insertBills(vararg bill: Bill)
suspend fun deleteBills(vararg bill: Bill)
suspend fun updateBills(vararg bill: Bill)
}
(7)build.gradle(项目):
buildscript {
ext {
compose_version = '1.1.0-beta02'
hilt_version = "2.37"
work_version = "2.7.1"
datastore_version = "1.0.0"
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.4'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31'
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
(8)build.gradle(应用程序):
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-parcelize'
}
apply plugin: 'kotlin-kapt'//hilt support
apply plugin: 'dagger.hilt.android.plugin'//hilt support
android {
compileSdk 31
defaultConfig {
applicationId "com.eynnzerr.cpbookkeeping_compose"
minSdk 21
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
javaCompileOptions {
annotationProcessorOptions {
arguments += [
"room.schemaLocation":"$projectDir/schemas".toString(),
"room.incremental":"true",
"room.expandProjection":"true"]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
useIR = true
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
kotlinCompilerVersion '1.5.31'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}
dependencies {
implementation "io.github.vanpra.compose-material-dialogs:datetime:0.6.2"
implementation "androidx.datastore:datastore-preferences:$datastore_version"
implementation "androidx.datastore:datastore:$datastore_version"
implementation "androidx.work:work-runtime-ktx:$work_version"
def room_version = "2.4.0-beta02"
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
implementation "androidx.hilt:hilt-navigation-compose:1.0.0-beta01"
implementation "androidx.navigation:navigation-compose:2.4.0-beta02"
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.3.0'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.foundation:foundation:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.material:material-icons-core:$compose_version"
implementation "androidx.compose.material:material-icons-extended:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'androidx.activity:activity-compose:1.4.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
}
提出这个问题后我得到了答案...
似乎你不能用“新”来命名包,我在我的应用程序中这样做了:
com.eynnzerr.cpbookkeeping_compose.ui.new.NewViewModel
因为它是保留的,你甚至不能用它来命名你的包...编辑后我的问题就解决了...
我刚刚遇到了类似的问题,幸运的是我找到了这个主题。在我的例子中,我将命名空间命名为“messages”并重命名该命名空间有效
我最近开始尝试使用 jetpack Compose,并为我的应用程序使用了 hilt 和 hilt-navigation-compose。它适用于第一个 ViewModel。但是,当我在第二个屏幕的另一个 ViewModel 上尝试相同的代码时,它总是崩溃并显示 java.lang.RuntimeException:无法创建 class 的实例。但是如果我在它的构造函数中去掉参数repository(Injected using Hilt),就可以初始化成功了。
目前在我的导航图中有四个screens.HOME,RECORD,SETTING screen可以通过BottomBar导航到,它们的viewModel可以正常工作;NEW screen可以通过FloatingActionButton导航到,但是它的viewModel不能像其他人一样创造。我无法弄清楚问题所在。这是我的后台跟踪:
java.lang.RuntimeException: Cannot create an instance of class com.eynnzerr.cpbookkeeping_compose.ui.new.NewViewModel
...
Caused by: java.lang.InstantiationException: java.lang.Class<com.eynnzerr.cpbookkeeping_compose.ui.new.NewViewModel> has no zero argument constructor
似乎 Hilt 只知道如何使用无参数构造函数创建 NewViewModel 而忽略了 它的参数库?
这是我的代码: (1)Hilt应用class:
@HiltAndroidApp
class CPApplication: Application() {
override fun onCreate() {
super.onCreate()
context = applicationContext
}
companion object {
@SuppressLint("StaticFieldLeak")
lateinit var context: Context
}
}
(2)主要活动:
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@RequiresApi(Build.VERSION_CODES.N)
@ExperimentalAnimationApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BookkeepingApp()
}
}
}
@ExperimentalAnimationApi
@Composable
fun BookkeepingApp() {
CPBookkeepingcomposeTheme {
val navController = rememberNavController()
BasicScreen(
navController = navController
)
}
}
@ExperimentalAnimationApi
@Composable
fun BasicScreen(
navController: NavController
) {
...
Scaffold(
topBar = {
//Change according to currentScreen in composable.
CPTopBar(currentScreen)
},
floatingActionButton = {
//only show up when it's HOME screen.
if(currentScreen.value == Destinations.HOME_ROUTE) {
DraggableFloatingButton(
onClick = {
navController.navigateTo(Destinations.NEW_ROUTE)
}
)
} else Unit
},
bottomBar = {
//only show up when it's HOME, RECORD, SETTING screens.
AnimatedVisibility(listState.isScrollingUp()) {
when(currentScreen.value) {
Destinations.HOME_ROUTE,
Destinations.RECORD_ROUTE,
Destinations.SETTING_ROUTE -> FlutterNavigation(navController = navController, items)
else -> Unit
}
}
}
) {
NavGraph(
navController = navController as NavHostController,
listState = listState
)
}
}
(3)导航图:
@Composable
fun NavGraph(
navController: NavHostController = rememberNavController(),
listState: LazyListState = rememberLazyListState(),
startDestination: String = Destinations.HOME_ROUTE
) {
NavHost(navController = navController, startDestination = "home") {
composable(Destinations.HOME_ROUTE){
//can create ViewModel.
val homeViewModel: HomeViewModel = hiltViewModel()
...
}
...
composable(Destinations.NEW_ROUTE){
//cannot create ViewModel?
val newViewModel: NewViewModel = hiltViewModel()
...
}
}
}
(4)HomeViewModel(效果很好):
data class HomeUiState(
val billsToday: List<Bill> = emptyList(),
val homeData: HomeData = HomeData()
)
@HiltViewModel
class HomeViewModel @Inject constructor(
private val billRepository: BillRepositoryImpl
): ViewModel() {
private val _uiState = MutableStateFlow(HomeUiState())
val uiState: StateFlow<HomeUiState> = _uiState
init {
viewModelScope.launch {
_uiState.update { it.copy(homeData = getAllData(0f)) }
billRepository.getBillsFlow().collect { bills ->
_uiState.update { it.copy(billsToday = bills) }
}
}
}
}
(5)NewViewModel(除了删除billRepository无法创建):
data class TabState(
val amount: String = "¥0",
val selectedIndex: Int = 0
)
@HiltViewModel
class NewViewModel @Inject constructor(
private val billRepository: BillRepositoryImpl //After deleting this, it can work properly
) : ViewModel() {
//uiState for expenseTab
private val _exTabState = MutableStateFlow(TabState())
val exTabState: StateFlow<TabState> = _exTabState.asStateFlow()
//uiState for revenueTab
private val _reTabState = MutableStateFlow(TabState())
val reTabState: StateFlow<TabState> = _reTabState.asStateFlow()
private val _remarkState = MutableStateFlow("add remark")
val remarkState: StateFlow<String> = _remarkState.asStateFlow()
fun updateRemark(remark: String) {
_remarkState.update { remark }
}
fun updateTab(category: Int, amount: String, selectedIndex: Int) {
when(category) {
-1 -> _exTabState.update { it.copy(amount = amount, selectedIndex = selectedIndex) }
1 -> _reTabState.update { it.copy(amount = amount, selectedIndex = selectedIndex) }
}
}
fun insertBill(bill: Bill) {
viewModelScope.launch {
billRepository.insertBills(bill)
}
}
}
(6)billRepository:
class BillRepositoryImpl @Inject constructor() : BillRepository {
private val billDao = BillDatabase.getInstance(context).getDao()
override suspend fun getBillsFlow(): Flow<List<Bill>> = billDao.getAllBills()
override suspend fun insertBills(vararg bill: Bill) = billDao.insertBills(*bill)
override suspend fun deleteBills(vararg bill: Bill) = billDao.deleteBills(*bill)
override suspend fun updateBills(vararg bill: Bill) = billDao.updateBills(*bill)
}
interface BillRepository {
suspend fun getBillsFlow(): Flow<List<Bill>>
suspend fun insertBills(vararg bill: Bill)
suspend fun deleteBills(vararg bill: Bill)
suspend fun updateBills(vararg bill: Bill)
}
(7)build.gradle(项目):
buildscript {
ext {
compose_version = '1.1.0-beta02'
hilt_version = "2.37"
work_version = "2.7.1"
datastore_version = "1.0.0"
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.4'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31'
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
(8)build.gradle(应用程序):
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-parcelize'
}
apply plugin: 'kotlin-kapt'//hilt support
apply plugin: 'dagger.hilt.android.plugin'//hilt support
android {
compileSdk 31
defaultConfig {
applicationId "com.eynnzerr.cpbookkeeping_compose"
minSdk 21
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
javaCompileOptions {
annotationProcessorOptions {
arguments += [
"room.schemaLocation":"$projectDir/schemas".toString(),
"room.incremental":"true",
"room.expandProjection":"true"]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
useIR = true
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
kotlinCompilerVersion '1.5.31'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}
dependencies {
implementation "io.github.vanpra.compose-material-dialogs:datetime:0.6.2"
implementation "androidx.datastore:datastore-preferences:$datastore_version"
implementation "androidx.datastore:datastore:$datastore_version"
implementation "androidx.work:work-runtime-ktx:$work_version"
def room_version = "2.4.0-beta02"
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
implementation "androidx.hilt:hilt-navigation-compose:1.0.0-beta01"
implementation "androidx.navigation:navigation-compose:2.4.0-beta02"
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.3.0'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.foundation:foundation:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.material:material-icons-core:$compose_version"
implementation "androidx.compose.material:material-icons-extended:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'androidx.activity:activity-compose:1.4.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
}
提出这个问题后我得到了答案... 似乎你不能用“新”来命名包,我在我的应用程序中这样做了: com.eynnzerr.cpbookkeeping_compose.ui.new.NewViewModel 因为它是保留的,你甚至不能用它来命名你的包...编辑后我的问题就解决了...
我刚刚遇到了类似的问题,幸运的是我找到了这个主题。在我的例子中,我将命名空间命名为“messages”并重命名该命名空间有效