Kotlin中无法创建AndroidViewModel实例,能否在Fragment中使用?
问题背景
我参考Google示例实现了BookmarkViewModel,但在Fragment中尝试获取该ViewModel实例时,出现了无法创建实例的报错。
ViewModel代码
class BookmarkViewModel(app: Application) : AndroidViewModel(app) { private val dao = BookmarkDb.get(app).bookmarkDao() companion object { private const val PAGE_SIZE = PagingConstants.PERPAGE /** * If placeholders are enabled, PagedList will report the full size but some items might * be null in onBind method (PagedListAdapter triggers a rebind when data is loaded). * <p> * If placeholders are disabled, onBind will never receive null but as more pages are * loaded, the scrollbars will jitter as new pages are loaded. You should probably disable * scrollbars if you disable placeholders. */ private const val ENABLE_PLACEHOLDERS = true } val allBookmarks = LivePagedListBuilder(dao.allBookmarkByDatetime(), PagedList.Config.Builder() .setPageSize(PAGE_SIZE) .setEnablePlaceholders(ENABLE_PLACEHOLDERS) .build()).build() fun insert(title: String, description: String, datetime: String) = ioThread { dao.insert(Bookmark(id = 0, title = title, description = description, datetime = datetime)) } fun remove(bookmark: Bookmark) = ioThread { dao.delete(bookmark) } }
Fragment中使用代码
class BookmarkListFragment : FirstFragment() { private var viewModel: BookmarkViewModel? = null .. override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) viewModel = ViewModelProviders.of(activity!!).get(BookmarkViewModel::class.java) ...
报错信息
java.lang.RuntimeException: Cannot create an instance of class com.lacas.db.room.BookmarkViewModel
完整堆栈:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.lacas.asd/com.lacas.asd.ui.activities.testtabs.TestTab2Activity}: java.lang.RuntimeException: Cannot create an instance of class com.lacas.asd.db.room.BookmarkViewModel
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2924)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2985)
at android.app.ActivityThread.-wrap14(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1635)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6692)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1468)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1358)
Caused by: java.lang.RuntimeException: Cannot create an instance of class com.lacas.asd.db.room.BookmarkViewModel
at android.arch.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:207)
at android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:134)
at android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:102)
at com.lacas.asd.ui.activities.testtabs.TestTab2Activity$viewModel$2.invoke(TestTab2Activity.kt:34)
at com.lacas.asd.ui.activities.testtabs.TestTab2Activity$viewModel$2.invoke(TestTab2Activity.kt:29)
at kotlin.UnsafeLazyImpl.getValue(Lazy.kt:154)
at com.lacas.asd.ui.activities.testtabs.TestTab2Activity.getViewModel(TestTab2Activity.kt)
at com.lacas.asd.ui.activities.testtabs.TestTab2Activity.onCreated(TestTab2Activity.kt:45)
at com.lacas.asd.base.BasePermissionsActivity.onCreate(BasePermissionsActivity.kt:34)
at android.app.Activity.performCreate(Activity.java:6912)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1126)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2877)
... 11 more
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Constructor.newInstance0(Native Method)
at java.lang.reflect.Constructor.newInstance(Constructor.java:430)
at android.arch.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:199)
... 22 more
请问该ViewModel能否在Fragment中使用?如何解决无法创建实例的问题?
解答
首先明确:这个ViewModel完全可以在Fragment中使用,问题出在ViewModel的实例获取方式或者依赖初始化环节上。我来帮你一步步分析解决:
1. 分析报错根源
从堆栈信息看,核心异常是InvocationTargetException,这说明ViewModel的构造函数在通过反射创建时,内部执行的代码抛出了异常。结合你的ViewModel代码,最可能的问题点是BookmarkDb.get(app).bookmarkDao()这一行——如果数据库初始化失败,就会导致ViewModel创建失败。
另外,你使用的ViewModelProviders.of()是已经被废弃的API,这也可能带来兼容性问题。
2. 具体解决步骤
步骤一:检查数据库初始化是否正常
先单独验证BookmarkDb.get(app)能否成功获取数据库实例:
- 可以在Application类中提前初始化数据库,或者在ViewModel构造函数中添加日志/调试断点,确认
BookmarkDb.get(app)没有抛出异常(比如Room实体类注解错误、数据库版本冲突、缺少Room依赖等)。 - 如果数据库初始化有问题,先修复数据库相关的错误,比如检查Room的实体类是否正确使用
@Entity注解,Dao接口是否正确标注@Dao,数据库类是否继承RoomDatabase并正确配置实体类。
步骤二:替换废弃的ViewModel获取API
旧的ViewModelProviders已经被AndroidX废弃,推荐使用ViewModelProvider的新写法。根据你的需求(和Activity共享ViewModel),修改Fragment中的代码:
// 替换原来的ViewModelProviders.of(activity!!).get(...) viewModel = ViewModelProvider(requireActivity())[BookmarkViewModel::class.java]
如果你的Fragment需要独立的ViewModel实例(不和Activity共享),则改为:
viewModel = ViewModelProvider(this)[BookmarkViewModel::class.java]
注意:确保你的项目依赖了AndroidX的ViewModel库,比如在build.gradle中包含:
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.2"
步骤三:确认ViewModel构造函数的正确性
你的ViewModel继承AndroidViewModel是正确的(因为需要Application上下文),Kotlin默认的构造函数是public,所以访问权限没问题。如果之前手动修改过构造函数的访问修饰符,确保它是public的。
3. 验证修复
完成上述步骤后,重新运行项目,ViewModel应该能正常在Fragment中创建实例了。如果还是报错,可以进一步检查:
- 确认
requireActivity()返回的Activity实例是有效的(不要在Fragment未关联Activity时调用)。 - 检查
ioThread函数的实现是否正确,是否会导致线程问题(不过这个是在方法内部调用,不会影响ViewModel创建)。
内容的提问来源于stack exchange,提问作者lacas

