【问题标题】:Unable to instantiate my ViewModel using ViewModelFactory无法使用 ViewModelFactory 实例化我的 ViewModel
【发布时间】:2020-05-22 20:24:03
【问题描述】:

我无法将 ViewModel 绑定到我的 Fragment(使用 lateinit),因为我在 Activity 中创建 ViewModel 的方式有误。我做错了什么?

原因:java.lang.RuntimeException:无法创建 com.example.foo.FooViewModel 类的实例

这是活动:

class FooActivity : AppCompatActivity() {

    private lateinit var viewModel: FooViewModel

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_foo)

        val id = intent.getLongExtra(FOO_ID, 1L)

        val viewModelFactory = FooViewModelFactory(
            id,
            FooDatabase.getInstance(application).fooDao,
            application)
        viewModel = ViewModelProviders.of(
            this, viewModelFactory).get(FooViewModel::class.java)    
    }
}

在 Fragment 尝试绑定视图模型时实例化视图模型时发生异常,请参见下面注释的代码行:

class BlankFragment : Fragment() {

    private val viewModel: FooViewModel by activityViewModels()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        super.onCreateView(inflater, container, savedInstanceState)
        val binding = FragmentBlankBinding.inflate(inflater)
        binding.setLifecycleOwner(this)

        // EXCEPTION OCCURS HERE
        binding.viewModel = viewModel

        return binding.root
    }
}

下面是 ViewModel 和 ViewModelFactory 类的代码:

class FooViewModelFactory (
    private val id: Long,
    private val fooDao: FooDao,
    private val application: Application) : ViewModelProvider.Factory {

    @Suppress("unchecked_cast")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(FooViewModel::class.java)) {
             return FooViewModel(
                  id,
                  fooDao,
                  application
             ) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

class FooViewModel(id: Long, fooDao : FooDao, app: Application ): AndroidViewModel(app) {

    private val _foo = MutableLiveData<Foo>()
    val foo: LiveData<Foo>
        get() = _foo

    // infrastructure needed to get the Foo from the database
    private val _database = fooDao
    private val _fooid = id
    private var viewModelJob = Job()
    // database queries in the IO thread to avoid locking up the UI
    private val ioScope = CoroutineScope(viewModelJob + Dispatchers.IO)

    init {
        // commenting this out and doing nothing doesn't affect exception in question.
        GlobalScope.launch{ getFoo()}
    }

    private fun getFoo() = ... // code to fetch Foo from database

}

编辑:堆栈跟踪。

2020-05-22 22:10:05.018 8599-8599/com.example.foo E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.foo, PID: 8599
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.foo/com.example.foo.FooActivity}: android.view.InflateException: Binary XML file line #19: Binary XML file line #19: Error inflating class fragment
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3037)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3172)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1906)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6863)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
     Caused by: android.view.InflateException: Binary XML file line #19: Binary XML file line #19: Error inflating class fragment
     Caused by: android.view.InflateException: Binary XML file line #19: Error inflating class fragment
     Caused by: java.lang.RuntimeException: Cannot create an instance of class com.example.foo.FooViewModel
        at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:269)
        at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.java:106)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:185)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
        at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:54)
        at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:41)
        at com.example.foo.BlankFragment.getViewModel(Unknown Source:2)
        at com.example.foo.BlankFragment.onCreateView(BlankFragment.kt:27)
        at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2698)
        at androidx.fragment.app.FragmentStateManager.ensureInflatedView(FragmentStateManager.java:218)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1183)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1356)
        at androidx.fragment.app.FragmentLayoutInflaterFactory.onCreateView(FragmentLayoutInflaterFactory.java:109)
        at androidx.fragment.app.FragmentController.onCreateView(FragmentController.java:135)
        at androidx.fragment.app.FragmentActivity.dispatchFragmentsOnCreateView(FragmentActivity.java:356)
        at androidx.fragment.app.FragmentActivity.onCreateView(FragmentActivity.java:335)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:780)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:730)
        at android.view.LayoutInflater.rInflate(LayoutInflater.java:863)
        at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:824)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:515)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:374)
        at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:469)
        at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:140)
        at com.example.foo.FooActivity.onCreate(FooActivity.kt:29)
        at android.app.Activity.performCreate(Activity.java:7149)
        at android.app.Activity.performCreate(Activity.java:7140)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1288)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3017)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3172)
2020-05-22 22:10:05.019 8599-8599/com.example.foo E/AndroidRuntime:     at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1906)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6863)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
     Caused by: java.lang.NoSuchMethodException: <init> [class android.app.Application]
        at java.lang.Class.getConstructor0(Class.java:2327)
        at java.lang.Class.getConstructor(Class.java:1725)
        at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:267)
            ... 40 more

【问题讨论】:

  • 你能发布完整的堆栈跟踪吗?
  • @CommonsWare 堆栈跟踪现已发布。
  • 堆栈跟踪表明它没有使用您的工厂。我怀疑您需要为您的activityViewModels() 电话 (activityViewModels { ... }) 提供工厂。
  • “你从哪里得到的?” -- 你引用的错误来自androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory,而不是你的班级。 “文档表明 activityViewModels() 委托不接受任何参数”——I see an argument
  • @CommonsWare 我知道了。问题是ViewModel by activityViewModels 在布局通过数据绑定膨胀时试图创建 ViewModel。但是,在声明 ViewModelFactory(和 viewModel)之前,我的活动类称为 setContentView,因此在 Fragment 尝试执行数据绑定时没有返回 ViewModel。回溯的神秘程度让我大吃一惊。

标签: android kotlin android-databinding


【解决方案1】:

问题是by activityViewModels() 没有获得能够创建您的视图模型的相同工厂。

应该是这样的:

class BlankFragment : Fragment() {
    private val viewModel: FooViewModel by activityViewModels {
        val application = requireActivity().application
        FooViewModelFactory(
            id,
            FooDatabase.getInstance(application).fooDao,
            application)
    }

编辑: 或者只提供来自 Activity 的工厂实例。无论哪种方式,您都需要它来可靠地获取FooViewModel 的实例。

class FooActivity : AppCompatActivity() {

    val viewModelFactory by lazy { 
        FooViewModelFactory(
            intent.getLongExtra(FOO_ID, 1L),
            FooDatabase.getInstance(application).fooDao,
            application)
    }

    private val viewModel: FooViewModel by viewModels { viewModelFactory }

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_foo)
    }
}

class BlankFragment : Fragment() {
    private val viewModel: FooViewModel by activityViewModels {
        (requireActivity() as FooActivity).viewModelFactory
    }

【讨论】:

  • 除了 Fragment 不应该知道idfooDaoapplication 的信息之外,这将是正确的,只有 Foo 仅作为访问的 ViewModel。 Activity 负责获取这些数据并通过共享的 ViewModel 与 Fragments 共享它们(请参阅我的答案)。但是,请在此处留下您的答案,因为它可能对其他情况下的其他人有用。谢谢!
  • 这只是演示创建具有相同行为的工厂的最原始技巧。没有什么能阻止你提供相同的工厂实例,事实上代码会更干净。如果需要,我可以将其添加为 EDIT。
  • 这是一个更好的原则,但我无法让它在实践中发挥作用:Property delegate must have a 'getValue(BlankFragment, KProperty*&gt;)' method. None of the following functions is suitable. Lazy&lt;FooViewModel&gt;.getValue(Any?, kProperty&lt;*&gt;) where T = FooViewModel for inline operator fun&lt;T&gt;Lazy&lt;T&gt;.getValue(thisRef: Any?, property: KPropertyM*&gt;): T defined in kotlin 我不知道为什么,它似乎应该工作。
  • 我想我忘记了活动代码上的: FooViewModel,想再试一次吗?
  • 不,不是那样的。 (我已经有了private val viewModel: FooViewModel by viewModels&lt;FooViewModel&gt; {viewModelFactory});编译器肯定知道它是FooViewModel。)
【解决方案2】:

问题在于ViewModel by activityViewModels 委托试图在布局通过数据绑定膨胀时创建 ViewModel。但是,在实际创建 ViewModelFactory(和 viewModel)之前,我的活动类调用了 setContentView,因此在 Fragment 尝试获取它时没有返回 ViewModel。

解决方法是将调用setContentView移动到Activity.onCreate()函数的末尾。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-10-24
    • 1970-01-01
    • 1970-01-01
    • 2021-05-17
    • 1970-01-01
    • 2020-07-17
    相关资源
    最近更新 更多