HiltViewModel报NoSuchMethodException:无空构造方法求助
Android Hilt ViewModel 初始化错误排查与解决
错误信息
Caused by: java.lang.NoSuchMethodException: com.example.nameful.ui.main.MovieListViewModel.<init> [] at java.lang.Class.getConstructor0(Class.java:2332) at java.lang.Class.getDeclaredConstructor(Class.java:2170) at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.kt:202) ... 94 more
问题背景
开发电影列表应用时触发上述错误,提示MovieListViewModel缺少空构造方法,但该ViewModel依赖的GetMovieListUseCase无法通过空构造实例化。已尝试将Compose中获取ViewModel的viewModel()改为hiltViewModel(),问题仍未解决。
相关代码片段
MovieListViewModel.kt
package com.example.nameful.ui.main import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import androidx.paging.cachedIn import com.example.nameful.data.package000.GetMovieListUseCase import com.example.nameful.data.repository.MoviesRepository import com.example.nameful.data.repository.MoviesRepositoryImpl import com.example.nameful.model.basic.Movie import dagger.Provides import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class MovieListViewModel @Inject constructor( private val getMovieListUseCase: GetMovieListUseCase ) : ViewModel() { var movieList: Flow<PagingData<Movie>> var favoriteMovieList: Flow<List<Movie>> init { movieList = loadMovies() favoriteMovieList = loadFavoriteMovies() } /** * Gets a list of popular movies */ private fun loadMovies(): Flow<PagingData<Movie>> { var movieListFromRepo: Flow<PagingData<Movie>> = emptyFlow() viewModelScope.launch { movieListFromRepo = getMovieListUseCase.getMostPopularMovies() .cachedIn(viewModelScope) } return movieListFromRepo } /** * Gets favorite movie list */ private fun loadFavoriteMovies(): Flow<List<Movie>> { viewModelScope.launch { favoriteMovieList = getMovieListUseCase.getFavoriteMovies() } return favoriteMovieList } fun reloadMovies() { viewModelScope.launch { getMovieListUseCase.reloadMovies() } } private var movieListScrollState = 0 fun saveMovieListScrollState(scrollIndex: Int) { movieListScrollState = scrollIndex } fun getMovieListScrollState():Int { return movieListScrollState } }
GetMovieListUseCase.kt
package com.example.nameful.data.package000 import androidx.paging.PagingData import com.example.nameful.data.repository.MoviesRepository import com.example.nameful.data.repository.MoviesRepositoryImpl import com.example.nameful.model.basic.Movie import kotlinx.coroutines.flow.Flow import javax.inject.Inject class GetMovieListUseCase @Inject constructor( private val popularMoviesRepository: MoviesRepository ) { suspend fun getMostPopularMovies(): Flow<PagingData<Movie>> { return popularMoviesRepository.getMostPopularMovies() } suspend fun getFavoriteMovies(): Flow<List<Movie>> { return popularMoviesRepository.getFavoriteMovies() } suspend fun reloadMovies() { popularMoviesRepository.reloadMovies() } }
MoviesRepositoryImpl.kt
package com.example.nameful.data.repository import androidx.paging.ExperimentalPagingApi import androidx.paging.PagingData import androidx.paging.map import com.example.nameful.data.Variables import com.example.nameful.data.api.MovieRemoteMediator import com.example.nameful.data.database.LocalDataSourceModule import com.example.nameful.data.database.MovieDataSource import com.example.nameful.data.database.NetworkDataSourceModule import com.example.nameful.model.basic.Movie import com.example.nameful.model.basic.Provider import com.example.nameful.model.basic.Role import com.example.nameful.model.basic.Trailer import kotlinx.coroutines.flow.* import javax.inject.Inject class MoviesRepositoryImpl @Inject constructor( @LocalDataSourceModule private val movieLocalDataSource: MovieDataSource, @NetworkDataSourceModule private val movieNetworkDataSource: MovieDataSource, dataRefreshManagerImpl: DataRefreshManager ) : MoviesRepository { @ExperimentalPagingApi val moviesRemoteMediator = MovieRemoteMediator( movieLocalDataSource, movieNetworkDataSource, dataRefreshManagerImpl ) @ExperimentalPagingApi override suspend fun getMostPopularMovies(): Flow<PagingData<Movie>> { // return the paging source val mostPopularMovies = movieLocalDataSource.getPagedMovies(moviesRemoteMediator).map { pagingData -> pagingData.map { it.poster = Variables.imageMidUrl+ it.poster it.toDomain() } } return mostPopularMovies } @ExperimentalPagingApi override suspend fun reloadMovies() { moviesRemoteMediator.reload() } /** * Only local data source keeps favorite movies */ override suspend fun getFavoriteMovies(): Flow<List<Movie>> { return movieLocalDataSource.getFavoriteMovies() } override suspend fun getMovieById(movieId: Int): Flow<Movie?> { val fullMovieDataFlow = movieLocalDataSource.getMovieById(movieId) .distinctUntilChanged() .map { if (it == null) { insertFreshMovieDataToCache(movieId) } else { if (it.trailer == null) { insertTrailerToCache(it.movie_id) } } it } return fullMovieDataFlow } override suspend fun toggleFavorite(movieId: Int) { movieLocalDataSource.toggleFavorite(movieId) } private suspend fun insertTrailerToCache(movieId: Int) { val trailer: Flow<Trailer?> = movieNetworkDataSource.getTrailer(movieId) if (trailer.count() > 0) { movieLocalDataSource.insertTrailer(movieId, trailer.first()) } } private suspend fun insertFreshMovieDataToCache(movieId:Int) { val fullMovieData = movieNetworkDataSource.getMovieById(movieId) movieLocalDataSource.insertMovie(fullMovieData) } override suspend fun getMovieCast(movieId: Int): Flow<List<Role?>> { // get character list from the source of truth = database val movieCast = movieLocalDataSource.getMovieCast(movieId).map { when { it.isEmpty() -> { insertCastToCache(movieId) it } else -> { it } } } return movieCast } private suspend fun insertCastToCache(movieId: Int) { // get cast list from network data source val castList: List<Role?> = movieNetworkDataSource.getFreshMovieCast(movieId) // insert into local data source movieLocalDataSource.insertCast(castList) } override suspend fun getMovieProviders(movieId: Int): Flow<List<Provider?>> { // get providers for this movie from the source of truth = database val movieProviders = movieLocalDataSource.getProviders(movieId).map { when { it.isEmpty() -> { insertMovieProvidersToCache(movieId) it } else -> { it } } } return movieProviders } private suspend fun insertMovieProvidersToCache(movieId: Int) { // get providers from network data source val providerList: List<Provider?> = movieNetworkDataSource.getFreshProviders(movieId) // insert into local data source movieLocalDataSource.insertProviders(providerList) } }
核心DI模块节选
AppModule.kt
package com.example.nameful import android.app.Application import android.content.Context import com.example.nameful.data.repository.MoviesRepository import com.example.nameful.data.repository.MoviesRepositoryImpl import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) class AppModule { @Singleton @Provides fun provideContext(application: Application): Context = application.applicationContext @Singleton @Provides fun provideMoviesRepository(moviesRepositoryImpl: MoviesRepositoryImpl): MoviesRepository { return moviesRepositoryImpl } }
DataRefreshManagerBinding.kt
package com.example.nameful.data.datarefreshmanager import com.example.nameful.data.repository.DataRefreshManager import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) internal abstract class DataRefreshManagerBinding { @Singleton @Binds abstract fun bindDataRefreshManager(dataRefreshManagerIml: DataRefreshManagerImpl): DataRefreshManager }
排查与解决方法
1. 补全Hilt核心配置
- 必须为自定义Application添加
@HiltAndroidApp注解,这是Hilt初始化的入口:@HiltAndroidApp class MovieApplication : Application() {} - 在
AndroidManifest.xml中注册该Application:<application android:name=".MovieApplication" <!-- 其他配置项 --> >
2. 确保ViewModel获取方式正确
- 在Compose页面中,必须使用
hiltViewModel()获取Hilt注入的ViewModel,禁止使用普通viewModel():@Composable fun MovieListScreen( viewModel: MovieListViewModel = hiltViewModel() ) { // 页面逻辑 } - 若在Activity/Fragment中获取,需使用Hilt兼容的ViewModelProvider:
val viewModel = ViewModelProvider(this)[MovieListViewModel::class.java]
3. 验证依赖注入链路完整性
- 检查
DataRefreshManagerImpl是否添加了@Inject构造方法,否则DataRefreshManagerBinding无法完成绑定:class DataRefreshManagerImpl @Inject constructor() : DataRefreshManager { // 实现逻辑 } - 补充
MovieDataSource的Qualifier依赖提供模块,确保LocalDataSourceModule和NetworkDataSourceModule注解能正确对应实例:@Module @InstallIn(SingletonComponent::class) class DataSourceModule { @Provides @LocalDataSourceModule fun provideLocalMovieDataSource(movieDao: MovieDao): MovieDataSource { return LocalMovieDataSource(movieDao) } @Provides @NetworkDataSourceModule fun provideNetworkMovieDataSource(apiService: MoviesApiService): MovieDataSource { return NetworkMovieDataSource(apiService) } }
4. 清理编译缓存并重建项目
- 执行
Build > Clean Project和Build > Rebuild Project,清除Hilt生成代码的缓存。 - 若问题仍存在,执行
File > Invalidate Caches... > Invalidate and Restart重启Android Studio。
5. 检查依赖版本兼容性
确保Hilt、ViewModel、Compose相关依赖版本匹配,示例配置:
// Hilt核心依赖 implementation "com.google.dagger:hilt-android:2.44" kapt "com.google.dagger:hilt-android-compiler:2.44" implementation "androidx.hilt:hilt-navigation-compose:1.0.0" // ViewModel依赖 implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1" implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1"
内容的提问来源于stack exchange,提问作者Prido1024
相关产品推荐
相关产品推荐

